Skip to content

Commit

Permalink
Add ClassDeclaration/ClassExpression scope
Browse files Browse the repository at this point in the history
ref #33
  • Loading branch information
Constellation committed Nov 16, 2014
1 parent a501d7f commit c3e23e2
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .jshintrc
Expand Up @@ -12,7 +12,8 @@
"unused": true,
"strict": true,
"trailing": true,
"white": true,
"validthis": true,

"onevar": true,

"node": true
Expand Down
45 changes: 40 additions & 5 deletions escope.js
Expand Up @@ -332,6 +332,7 @@
Variable.CatchClause = 'CatchClause';
Variable.Parameter = 'Parameter';
Variable.FunctionName = 'FunctionName';
Variable.ClassName = 'ClassName';
Variable.Variable = 'Variable';
Variable.ImplicitGlobalVariable = 'ImplicitGlobalVariable';

Expand All @@ -348,7 +349,9 @@
return true;
}

if (scope.type === 'function') {
if (scope.type === 'class') {
return true;
} else if (scope.type === 'function') {
body = block.body;
} else if (scope.type === 'global') {
body = block;
Expand Down Expand Up @@ -395,7 +398,7 @@
* @class Scope
*/
function Scope(scopeManager, block, opt) {
var variable, body;
var variable;

/**
* One of 'catch', 'with', 'function', 'global' or 'block'.
Expand All @@ -405,7 +408,8 @@
(block.type === Syntax.CatchClause) ? 'catch' :
(block.type === Syntax.WithStatement) ? 'with' :
(block.type === Syntax.Program) ? 'global' :
(block.type === Syntax.BlockStatement) ? 'block' : 'function';
(block.type === Syntax.BlockStatement) ? 'block' :
(block.type === Syntax.ClassDeclaration || block.type === Syntax.ClassExpression) ? 'class' : 'function';
/**
* The scoped {@link Variable}s of this scope, as <code>{ Variable.name
* : Variable }</code>.
Expand Down Expand Up @@ -478,7 +482,6 @@
* @member {boolean} Scope#thisFound
*/
this.thisFound = false;
body = this.type === 'function' ? block.body : block;

this.__left = [];

Expand Down Expand Up @@ -860,6 +863,9 @@
if (node.type === Syntax.ArrowFunctionExpression) {
return true;
}
if (node.type === Syntax.ClassDeclaration || (node.type === Syntax.ClassExpression && node.id)) {
return true;
}
}
return isScopeRequired(node);
};
Expand All @@ -876,7 +882,7 @@
* @return {ScopeManager}
*/
function analyze(tree, providedOptions) {
var resultScopes, scopeManager, variableTargetScope;
var resultScopes, scopeManager, variableTargetScope, classOuterScope;

options = updateDeeply(defaultOptions(), providedOptions);
resultScopes = scopes = [];
Expand Down Expand Up @@ -940,6 +946,35 @@
});
break;

case Syntax.ClassDeclaration:
// Outer block scope.
currentScope.upper.__define(node.id, {
type: Variable.ClassName,
name: node.id,
node: node
});
// Inner ClassBody scope.
currentScope.__define(node.id, {
type: Variable.ClassName,
name: node.id,
node: node
});
currentScope.upper.__referencing(node.superClass);
break;

case Syntax.ClassExpression:
classOuterScope = (node.id) ? currentScope.upper : currentScope;
if (node.id) {
// Inner ClassBody scope.
currentScope.__define(node.id, {
type: Variable.ClassName,
name: node.id,
node: node
});
}
classOuterScope.__referencing(node.superClass);
break;

case Syntax.ConditionalExpression:
currentScope.__referencing(node.test);
currentScope.__referencing(node.consequent);
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -22,7 +22,6 @@
"estraverse": ">=1.7.1"
},
"devDependencies": {
"jshint": "~2.5.10",
"coffee-script": "~1.8.0",
"chai": "~1.10.0",
"esprima": "~1.2.2",
Expand Down
113 changes: 113 additions & 0 deletions test/es6-class.coffee
@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014 Yusuke Suzuki <utatane.tea@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

expect = require('chai').expect
harmony = require '../third_party/esprima'
escope = require '..'

describe 'ES6 class', ->
it 'declaration name creates class scope', ->
ast = harmony.parse """
class Derived extends Base {
constructor() {
}
}
new Derived();
"""

scopeManager = escope.analyze ast, ecmaVersion: 6
expect(scopeManager.scopes).to.have.length 3

scope = scopeManager.scopes[0]
expect(scope.type).to.be.equal 'global'
expect(scope.block.type).to.be.equal 'Program'
expect(scope.isStrict).to.be.false
expect(scope.variables).to.have.length 1
expect(scope.variables[0].name).to.be.equal 'Derived'
expect(scope.references).to.have.length 2
expect(scope.references[0].identifier.name).to.be.equal 'Base'
expect(scope.references[1].identifier.name).to.be.equal 'Derived'

scope = scopeManager.scopes[1]
expect(scope.type).to.be.equal 'class'
expect(scope.block.type).to.be.equal 'ClassDeclaration'
expect(scope.isStrict).to.be.true
expect(scope.variables).to.have.length 1
expect(scope.variables[0].name).to.be.equal 'Derived'
expect(scope.references).to.have.length 0

it 'expression name creates class scope#1', ->
ast = harmony.parse """
(class Derived extends Base {
constructor() {
}
});
"""

scopeManager = escope.analyze ast, ecmaVersion: 6
expect(scopeManager.scopes).to.have.length 3

scope = scopeManager.scopes[0]
expect(scope.type).to.be.equal 'global'
expect(scope.block.type).to.be.equal 'Program'
expect(scope.isStrict).to.be.false
expect(scope.variables).to.have.length 0
expect(scope.references).to.have.length 1
expect(scope.references[0].identifier.name).to.be.equal 'Base'

scope = scopeManager.scopes[1]
expect(scope.type).to.be.equal 'class'
expect(scope.block.type).to.be.equal 'ClassExpression'
expect(scope.isStrict).to.be.true
expect(scope.variables).to.have.length 1
expect(scope.variables[0].name).to.be.equal 'Derived'
expect(scope.references).to.have.length 0

scope = scopeManager.scopes[2]
expect(scope.type).to.be.equal 'function'
expect(scope.block.type).to.be.equal 'FunctionExpression'

it 'expression name creates class scope#2', ->
ast = harmony.parse """
(class extends Base {
constructor() {
}
});
"""

scopeManager = escope.analyze ast, ecmaVersion: 6
expect(scopeManager.scopes).to.have.length 2

scope = scopeManager.scopes[0]
expect(scope.type).to.be.equal 'global'
expect(scope.block.type).to.be.equal 'Program'
expect(scope.isStrict).to.be.false
expect(scope.variables).to.have.length 0
expect(scope.references).to.have.length 1
expect(scope.references[0].identifier.name).to.be.equal 'Base'

scope = scopeManager.scopes[1]
expect(scope.type).to.be.equal 'function'
expect(scope.block.type).to.be.equal 'FunctionExpression'

# vim: set sw=4 ts=4 et tw=80 :

0 comments on commit c3e23e2

Please sign in to comment.