├── .babelrc ├── .npmignore ├── bower.json ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── .jshintrc ├── LICENSE.BSD ├── package.json ├── test ├── object-expression.js ├── global-increment.js ├── es6-new-target.js ├── arguments.js ├── fallback.js ├── catch-scope.js ├── with-scope.js ├── optimistic.js ├── function-expression-name.js ├── es6-super.js ├── label.js ├── es6-switch.js ├── es6-template-literal.js ├── es6-rest-args.js ├── es6-arrow-function-expression.js ├── child-visitor-keys.js ├── es6-object.js ├── nodejs-scope.js ├── es6-catch.js ├── implied-strict.js ├── es6-import.js ├── implicit-global-reference.js ├── es6-class.js ├── es6-block-scope.js ├── get-declared-variables.js ├── es6-export.js ├── es6-default-parameters.js └── es6-iteration-scope.js ├── third_party └── espree.js ├── src ├── definition.js ├── variable.js ├── pattern-visitor.js ├── reference.js ├── index.js ├── scope-manager.js └── referencer.js ├── README.md └── gulpfile.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | .DS_Store 3 | .vimrc.local 4 | t.js 5 | esprima.js 6 | .travis.yml 7 | .npmignore 8 | /tmp/ 9 | /.git/ 10 | /node_modules/ 11 | /tools/ 12 | /test/ 13 | /cover_html/ 14 | /.coverage_data/ 15 | /out/ 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escope", 3 | "version": "2.0.2-dev", 4 | "main": "escope.js", 5 | "dependencies": { 6 | "estraverse": ">= 0.0.2" 7 | }, 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "components" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Emacs 2 | *~ 3 | \#*\# 4 | 5 | # Node modules 6 | node_modules/ 7 | /build/ 8 | npm-debug.log 9 | 10 | # Cover 11 | .coverage_data/ 12 | cover_html/ 13 | 14 | npm-debug.log 15 | .vimrc.local 16 | 17 | # JSDoc 18 | /out/ 19 | 20 | /lib/ 21 | /powered-test/ 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | - "0.11" 6 | - "0.12" 7 | - "iojs-1" 8 | - "iojs-2" 9 | - "iojs-3" 10 | - "4" 11 | matrix: 12 | allow_failures: 13 | - node_js: 14 | - "0.11" 15 | - "iojs-1" 16 | - "iojs-2" 17 | - "iojs-3" 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Project license: \ 2 | 3 | - You will only Submit Contributions where You have authored 100% of the content. 4 | - You will only Submit Contributions to which You have the necessary rights. This means that if You are employed You have received the necessary permissions from Your employer to make the Contributions. 5 | - Whatever content You Contribute will be provided under the Project License. 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "indent": 4, 6 | "eqnull": true, 7 | "latedef": true, 8 | "noarg": true, 9 | "noempty": true, 10 | "quotmark": "single", 11 | "undef": true, 12 | "unused": true, 13 | "strict": true, 14 | "trailing": true, 15 | "validthis": true, 16 | 17 | "onevar": true, 18 | 19 | "node": true 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE.BSD: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions are met: 3 | 4 | * Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | * Redistributions in binary form must reproduce the above copyright 7 | notice, this list of conditions and the following disclaimer in the 8 | documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 11 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 13 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 14 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 15 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 16 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 17 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 18 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 19 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escope", 3 | "description": "ECMAScript scope analyzer", 4 | "homepage": "http://github.com/estools/escope", 5 | "main": "lib/index.js", 6 | "version": "3.6.0", 7 | "engines": { 8 | "node": ">=0.4.0" 9 | }, 10 | "maintainers": [ 11 | { 12 | "name": "Yusuke Suzuki", 13 | "email": "utatane.tea@gmail.com", 14 | "web": "http://github.com/Constellation" 15 | } 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/estools/escope.git" 20 | }, 21 | "dependencies": { 22 | "es6-map": "^0.1.3", 23 | "es6-weak-map": "^2.0.1", 24 | "esrecurse": "^4.1.0", 25 | "estraverse": "^4.1.1" 26 | }, 27 | "devDependencies": { 28 | "babel": "^6.3.26", 29 | "babel-preset-es2015": "^6.3.13", 30 | "babel-register": "^6.3.13", 31 | "browserify": "^13.0.0", 32 | "chai": "^3.4.1", 33 | "espree": "^3.1.1", 34 | "esprima": "^2.7.1", 35 | "gulp": "^3.9.0", 36 | "gulp-babel": "^6.1.1", 37 | "gulp-bump": "^1.0.0", 38 | "gulp-eslint": "^1.1.1", 39 | "gulp-espower": "^1.0.2", 40 | "gulp-filter": "^3.0.1", 41 | "gulp-git": "^1.6.1", 42 | "gulp-mocha": "^2.2.0", 43 | "gulp-plumber": "^1.0.1", 44 | "gulp-sourcemaps": "^1.6.0", 45 | "gulp-tag-version": "^1.3.0", 46 | "jsdoc": "^3.4.0", 47 | "lazypipe": "^1.0.1", 48 | "vinyl-source-stream": "^1.1.0" 49 | }, 50 | "license": "BSD-2-Clause", 51 | "scripts": { 52 | "test": "gulp travis", 53 | "unit-test": "gulp test", 54 | "lint": "gulp lint", 55 | "jsdoc": "jsdoc src/*.js README.md" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/object-expression.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { analyze } from '..'; 3 | 4 | describe('object expression', function() { 5 | it('doesn\'t require property type', function() { 6 | // Hardcoded AST. Esprima adds an extra 'Property' 7 | // key/value to ObjectExpressions, so we're not using 8 | // it parse a program string. 9 | const ast = { 10 | type: 'Program', 11 | body: [{ 12 | type: 'VariableDeclaration', 13 | declarations: [{ 14 | type: 'VariableDeclarator', 15 | id: { 16 | type: 'Identifier', 17 | name: 'a' 18 | }, 19 | init: { 20 | type: 'ObjectExpression', 21 | properties: [{ 22 | kind: 'init', 23 | key: { 24 | type: 'Identifier', 25 | name: 'foo' 26 | }, 27 | value: { 28 | type: 'Identifier', 29 | name: 'a' 30 | } 31 | }] 32 | } 33 | }] 34 | }] 35 | }; 36 | 37 | const scope = analyze(ast).scopes[0]; 38 | expect(scope.variables).to.have.length(1); 39 | expect(scope.references).to.have.length(2); 40 | expect(scope.variables[0].name).to.be.equal('a'); 41 | expect(scope.references[0].identifier.name).to.be.equal('a'); 42 | expect(scope.references[1].identifier.name).to.be.equal('a'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/global-increment.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('global increment', function() { 29 | it('becomes read/write', function() { 30 | const ast = parse(`b++;`); 31 | 32 | const scopeManager = analyze(ast); 33 | expect(scopeManager.scopes).to.have.length(1); 34 | const globalScope = scopeManager.scopes[0]; 35 | expect(globalScope.type).to.be.equal('global'); 36 | expect(globalScope.variables).to.have.length(0); 37 | expect(globalScope.references).to.have.length(1); 38 | expect(globalScope.references[0].isReadWrite()).to.be.true; 39 | }); 40 | }); 41 | 42 | // vim: set sw=4 ts=4 et tw=80 : 43 | -------------------------------------------------------------------------------- /third_party/espree.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | var espree = require('espree'); 26 | 27 | module.exports = function (code) { 28 | return espree.parse(code, { 29 | 30 | // attach range information to each node 31 | range: true, 32 | 33 | // attach line/column location information to each node 34 | loc: true, 35 | 36 | // create a top-level comments array containing all comments 37 | comments: true, 38 | 39 | // attach comments to the closest relevant node as leadingComments and 40 | // trailingComments 41 | attachComment: true, 42 | 43 | // create a top-level tokens array containing all tokens 44 | tokens: true, 45 | 46 | // try to continue parsing if an error is encountered, store errors in a 47 | // top-level errors array 48 | tolerant: true, 49 | 50 | // enable es6 features. 51 | ecmaVersion: 6, 52 | sourceType: "module" 53 | }); 54 | }; 55 | 56 | /* vim: set sw=4 ts=4 et tw=80 : */ 57 | -------------------------------------------------------------------------------- /test/es6-new-target.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { analyze } from '..'; 26 | 27 | const parse = require('../third_party/espree'); 28 | 29 | describe('ES6 new.target', function() { 30 | it('should not make references of new.target', function() { 31 | const ast = parse(` 32 | class A { 33 | constructor() { 34 | new.target; 35 | } 36 | } 37 | `); 38 | 39 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 40 | expect(scopeManager.scopes).to.have.length(3); 41 | 42 | const scope = scopeManager.scopes[2]; 43 | expect(scope.type).to.be.equal('function'); 44 | expect(scope.block.type).to.be.equal('FunctionExpression'); 45 | expect(scope.isStrict).to.be.true; 46 | expect(scope.variables).to.have.length(1); 47 | expect(scope.variables[0].name).to.be.equal('arguments'); 48 | expect(scope.references).to.have.length(0); 49 | }); 50 | }); 51 | 52 | // vim: set sw=4 ts=4 et tw=80 : 53 | -------------------------------------------------------------------------------- /test/arguments.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import esprima from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('arguments', function() { 29 | it('arguments are correctly materialized', function() { 30 | const ast = esprima.parse(` 31 | (function () { 32 | arguments; 33 | }()); 34 | `); 35 | 36 | const scopeManager = analyze(ast); 37 | expect(scopeManager.scopes).to.have.length(2); 38 | const globalScope = scopeManager.scopes[0]; 39 | expect(globalScope.type).to.be.equal('global'); 40 | expect(globalScope.variables).to.have.length(0); 41 | expect(globalScope.references).to.have.length(0); 42 | 43 | const scope = scopeManager.scopes[1]; 44 | expect(scope.type).to.be.equal('function'); 45 | expect(scope.variables).to.have.length(1); 46 | expect(scope.variables[0].name).to.be.equal('arguments'); 47 | expect(scope.isArgumentsMaterialized()).to.be.true; 48 | expect(scope.references).to.have.length(1); 49 | expect(scope.references[0].resolved).to.be.equal(scope.variables[0]); 50 | }); 51 | }); 52 | 53 | // vim: set sw=4 ts=4 et tw=80 : 54 | -------------------------------------------------------------------------------- /test/fallback.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2016 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import esprima from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('fallback option', function() { 29 | it('should raise an error when it encountered an unknown node if no fallback.', function() { 30 | const ast = esprima.parse(` 31 | var foo = 0; 32 | `); 33 | 34 | ast.body[0].declarations[0].init.type = 'NumericLiteral'; 35 | 36 | expect(function() { 37 | analyze(ast, {fallback: 'none'}); 38 | }).to.throw("Unknown node type NumericLiteral"); 39 | }); 40 | 41 | it('should not raise an error even if it encountered an unknown node when fallback is iteration.', function() { 42 | const ast = esprima.parse(` 43 | var foo = 0; 44 | `); 45 | 46 | ast.body[0].declarations[0].init.type = 'NumericLiteral'; 47 | 48 | analyze(ast); // default is `fallback: 'iteration'` 49 | analyze(ast, {fallback: 'iteration'}); 50 | }); 51 | 52 | it('should not raise an error even if it encountered an unknown node when fallback is a function.', function() { 53 | const ast = esprima.parse(` 54 | var foo = 0; 55 | `); 56 | 57 | ast.body[0].declarations[0].init.type = 'NumericLiteral'; 58 | 59 | analyze(ast, {fallback: node => Object.keys(node)}); 60 | }); 61 | }); 62 | 63 | // vim: set sw=4 ts=4 et tw=80 : 64 | -------------------------------------------------------------------------------- /test/catch-scope.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import esprima from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('catch', function() { 29 | it('creates scope', function() { 30 | const ast = esprima.parse(` 31 | (function () { 32 | try { 33 | } catch (e) { 34 | } 35 | }()); 36 | `); 37 | 38 | const scopeManager = analyze(ast); 39 | expect(scopeManager.scopes).to.have.length(3); 40 | const globalScope = scopeManager.scopes[0]; 41 | expect(globalScope.type).to.be.equal('global'); 42 | expect(globalScope.variables).to.have.length(0); 43 | expect(globalScope.references).to.have.length(0); 44 | 45 | let scope = scopeManager.scopes[1]; 46 | expect(scope.type).to.be.equal('function'); 47 | expect(scope.variables).to.have.length(1); 48 | expect(scope.variables[0].name).to.be.equal('arguments'); 49 | expect(scope.isArgumentsMaterialized()).to.be.false; 50 | expect(scope.references).to.have.length(0); 51 | 52 | scope = scopeManager.scopes[2]; 53 | expect(scope.type).to.be.equal('catch'); 54 | expect(scope.variables).to.have.length(1); 55 | expect(scope.variables[0].name).to.be.equal('e'); 56 | expect(scope.isArgumentsMaterialized()).to.be.true; 57 | expect(scope.references).to.have.length(0); 58 | }); 59 | }); 60 | 61 | // vim: set sw=4 ts=4 et tw=80 : 62 | -------------------------------------------------------------------------------- /test/with-scope.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('with', function() { 29 | it('creates scope', function() { 30 | const ast = parse(` 31 | (function () { 32 | with (obj) { 33 | testing; 34 | } 35 | }()); 36 | `); 37 | 38 | const scopeManager = analyze(ast); 39 | expect(scopeManager.scopes).to.have.length(3); 40 | const globalScope = scopeManager.scopes[0]; 41 | expect(globalScope.type).to.be.equal('global'); 42 | expect(globalScope.variables).to.have.length(0); 43 | expect(globalScope.references).to.have.length(0); 44 | 45 | let scope = scopeManager.scopes[1]; 46 | expect(scope.type).to.be.equal('function'); 47 | expect(scope.variables).to.have.length(1); 48 | expect(scope.variables[0].name).to.be.equal('arguments'); 49 | expect(scope.isArgumentsMaterialized()).to.be.false; 50 | expect(scope.references).to.have.length(1); 51 | expect(scope.references[0].resolved).to.be.null; 52 | 53 | scope = scopeManager.scopes[2]; 54 | expect(scope.type).to.be.equal('with'); 55 | expect(scope.variables).to.have.length(0); 56 | expect(scope.isArgumentsMaterialized()).to.be.true; 57 | expect(scope.references).to.have.length(1); 58 | expect(scope.references[0].resolved).to.be.null; 59 | }); 60 | }); 61 | 62 | // vim: set sw=4 ts=4 et tw=80 : 63 | -------------------------------------------------------------------------------- /src/definition.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | import Variable from './variable'; 26 | 27 | /** 28 | * @class Definition 29 | */ 30 | export default class Definition { 31 | constructor(type, name, node, parent, index, kind) { 32 | /** 33 | * @member {String} Definition#type - type of the occurrence (e.g. "Parameter", "Variable", ...). 34 | */ 35 | this.type = type; 36 | /** 37 | * @member {esprima.Identifier} Definition#name - the identifier AST node of the occurrence. 38 | */ 39 | this.name = name; 40 | /** 41 | * @member {esprima.Node} Definition#node - the enclosing node of the identifier. 42 | */ 43 | this.node = node; 44 | /** 45 | * @member {esprima.Node?} Definition#parent - the enclosing statement node of the identifier. 46 | */ 47 | this.parent = parent; 48 | /** 49 | * @member {Number?} Definition#index - the index in the declaration statement. 50 | */ 51 | this.index = index; 52 | /** 53 | * @member {String?} Definition#kind - the kind of the declaration statement. 54 | */ 55 | this.kind = kind; 56 | } 57 | } 58 | 59 | /** 60 | * @class ParameterDefinition 61 | */ 62 | class ParameterDefinition extends Definition { 63 | constructor(name, node, index, rest) { 64 | super(Variable.Parameter, name, node, null, index, null); 65 | /** 66 | * Whether the parameter definition is a part of a rest parameter. 67 | * @member {boolean} ParameterDefinition#rest 68 | */ 69 | this.rest = rest; 70 | } 71 | } 72 | 73 | export { 74 | ParameterDefinition, 75 | Definition 76 | } 77 | 78 | /* vim: set sw=4 ts=4 et tw=80 : */ 79 | -------------------------------------------------------------------------------- /test/optimistic.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Yusuke Suzuki 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 13 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 16 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 19 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 21 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | 24 | import { expect } from 'chai'; 25 | import { analyze } from '..'; 26 | import { parse } from 'esprima'; 27 | 28 | describe('optimistic', function() { 29 | it('direct call to eval', function() { 30 | const ast = parse(` 31 | function outer() { 32 | eval(str); 33 | var i = 20; 34 | function inner() { 35 | i; 36 | } 37 | } 38 | `); 39 | 40 | const { scopes } = analyze(ast, {optimistic: true}); 41 | 42 | expect(scopes.map(scope => scope.variables.map(variable => variable.name))).to.be.eql( 43 | [ 44 | [ 45 | 'outer' 46 | ], 47 | [ 48 | 'arguments', 49 | 'i', 50 | 'inner' 51 | ], 52 | [ 53 | 'arguments' 54 | ] 55 | ] 56 | ); 57 | }); 58 | 59 | it('with statement', function() { 60 | const ast = parse(` 61 | function outer() { 62 | eval(str); 63 | var i = 20; 64 | with (obj) { 65 | i; 66 | } 67 | } 68 | `); 69 | 70 | const { scopes } = analyze(ast, {optimistic: true}); 71 | 72 | expect(scopes.map(scope => scope.variables.map(variable => variable.name))).to.be.eql( 73 | [ 74 | [ 75 | 'outer' 76 | ], 77 | [ 78 | 'arguments', 79 | 'i' 80 | ], 81 | [ 82 | ] 83 | ] 84 | ); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/function-expression-name.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse as parse } from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('function name', function() { 29 | it('should create its special scope', function() { 30 | const ast = parse(` 31 | (function name() { 32 | }()); 33 | `); 34 | 35 | const scopeManager = analyze(ast); 36 | expect(scopeManager.scopes).to.have.length(3); 37 | const globalScope = scopeManager.scopes[0]; 38 | expect(globalScope.type).to.be.equal('global'); 39 | expect(globalScope.variables).to.have.length(0); 40 | expect(globalScope.references).to.have.length(0); 41 | expect(globalScope.isArgumentsMaterialized()).to.be.true; 42 | 43 | // Function expression name scope 44 | let scope = scopeManager.scopes[1]; 45 | expect(scope.type).to.be.equal('function-expression-name'); 46 | expect(scope.variables).to.have.length(1); 47 | expect(scope.variables[0].name).to.be.equal('name'); 48 | expect(scope.isArgumentsMaterialized()).to.be.true; 49 | expect(scope.references).to.have.length(0); 50 | expect(scope.upper === globalScope).to.be.true; 51 | 52 | // Function scope 53 | scope = scopeManager.scopes[2]; 54 | expect(scope.type).to.be.equal('function'); 55 | expect(scope.variables).to.have.length(1); 56 | expect(scope.variables[0].name).to.be.equal('arguments'); 57 | expect(scope.isArgumentsMaterialized()).to.be.false; 58 | expect(scope.references).to.have.length(0); 59 | expect(scope.upper === scopeManager.scopes[1]).to.be.true; 60 | }); 61 | }); 62 | 63 | 64 | // vim: set sw=4 ts=4 et tw=80 : 65 | -------------------------------------------------------------------------------- /test/es6-super.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 super', function() { 29 | it('is not handled as reference', function() { 30 | const ast = parse(` 31 | class Hello { 32 | constructor() { 33 | super(); 34 | } 35 | 36 | method() { 37 | super.method(); 38 | } 39 | } 40 | `); 41 | 42 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 43 | expect(scopeManager.scopes).to.have.length(4); 44 | 45 | let scope = scopeManager.scopes[0]; 46 | expect(scope.type).to.be.equal('global'); 47 | expect(scope.variables).to.have.length(1); 48 | expect(scope.variables[0].name).to.be.equal('Hello'); 49 | expect(scope.references).to.have.length(0); 50 | 51 | scope = scopeManager.scopes[1]; 52 | expect(scope.type).to.be.equal('class'); 53 | expect(scope.variables).to.have.length(1); 54 | expect(scope.variables[0].name).to.be.equal('Hello'); 55 | expect(scope.references).to.have.length(0); 56 | 57 | scope = scopeManager.scopes[2]; 58 | expect(scope.type).to.be.equal('function'); 59 | expect(scope.variables).to.have.length(1); 60 | expect(scope.variables[0].name).to.be.equal('arguments'); 61 | expect(scope.references).to.have.length(0); // super is specially handled like `this`. 62 | 63 | scope = scopeManager.scopes[3]; 64 | expect(scope.type).to.be.equal('function'); 65 | expect(scope.variables).to.have.length(1); 66 | expect(scope.variables[0].name).to.be.equal('arguments'); 67 | expect(scope.references).to.have.length(0); // super is specially handled like `this`. 68 | }); 69 | }); 70 | 71 | // vim: set sw=4 ts=4 et tw=80 : 72 | -------------------------------------------------------------------------------- /test/label.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('label', function() { 29 | it('should not create variables', function() { 30 | const ast = parse(`function bar() { q: for(;;) { break q; } }`); 31 | 32 | const scopeManager = analyze(ast); 33 | expect(scopeManager.scopes).to.have.length(2); 34 | const globalScope = scopeManager.scopes[0]; 35 | expect(globalScope.type).to.be.equal('global'); 36 | expect(globalScope.variables).to.have.length(1); 37 | expect(globalScope.variables[0].name).to.be.equal('bar'); 38 | expect(globalScope.references).to.have.length(0); 39 | 40 | const scope = scopeManager.scopes[1]; 41 | expect(scope.type).to.be.equal('function'); 42 | expect(scope.variables).to.have.length(1); 43 | expect(scope.variables[0].name).to.be.equal('arguments'); 44 | expect(scope.isArgumentsMaterialized()).to.be.false; 45 | expect(scope.references).to.have.length(0); 46 | }); 47 | 48 | it('should count child node references', function() { 49 | const ast = parse(` 50 | var foo = 5; 51 | 52 | label: while (true) { 53 | console.log(foo); 54 | break; 55 | } 56 | `); 57 | 58 | const scopeManager = analyze(ast); 59 | expect(scopeManager.scopes).to.have.length(1); 60 | const globalScope = scopeManager.scopes[0]; 61 | expect(globalScope.type).to.be.equal('global'); 62 | expect(globalScope.variables).to.have.length(1); 63 | expect(globalScope.variables[0].name).to.be.equal('foo'); 64 | expect(globalScope.through.length).to.be.equal(3); 65 | expect(globalScope.through[2].identifier.name).to.be.equal('foo'); 66 | expect(globalScope.through[2].isRead()).to.be.true; 67 | }); 68 | }); 69 | 70 | // vim: set sw=4 ts=4 et tw=80 : 71 | -------------------------------------------------------------------------------- /test/es6-switch.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 switch', function() { 29 | it('materialize scope', function() { 30 | const ast = parse(` 31 | switch (ok) { 32 | case hello: 33 | let i = 20; 34 | i; 35 | break; 36 | 37 | default: 38 | let test = 30; 39 | test; 40 | } 41 | `); 42 | 43 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 44 | expect(scopeManager.scopes).to.have.length(2); 45 | 46 | let scope = scopeManager.scopes[0]; 47 | expect(scope.type).to.be.equal('global'); 48 | expect(scope.block.type).to.be.equal('Program'); 49 | expect(scope.isStrict).to.be.false; 50 | expect(scope.variables).to.have.length(0); 51 | expect(scope.references).to.have.length(1); 52 | expect(scope.references[0].identifier.name).to.be.equal('ok'); 53 | 54 | scope = scopeManager.scopes[1]; 55 | expect(scope.type).to.be.equal('switch'); 56 | expect(scope.block.type).to.be.equal('SwitchStatement'); 57 | expect(scope.isStrict).to.be.false; 58 | expect(scope.variables).to.have.length(2); 59 | expect(scope.variables[0].name).to.be.equal('i'); 60 | expect(scope.variables[1].name).to.be.equal('test'); 61 | expect(scope.references).to.have.length(5); 62 | expect(scope.references[0].identifier.name).to.be.equal('hello'); 63 | expect(scope.references[1].identifier.name).to.be.equal('i'); 64 | expect(scope.references[2].identifier.name).to.be.equal('i'); 65 | expect(scope.references[3].identifier.name).to.be.equal('test'); 66 | expect(scope.references[4].identifier.name).to.be.equal('test'); 67 | }); 68 | }); 69 | 70 | // vim: set sw=4 ts=4 et tw=80 : 71 | -------------------------------------------------------------------------------- /src/variable.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | /** 26 | * A Variable represents a locally scoped identifier. These include arguments to 27 | * functions. 28 | * @class Variable 29 | */ 30 | export default class Variable { 31 | constructor(name, scope) { 32 | /** 33 | * The variable name, as given in the source code. 34 | * @member {String} Variable#name 35 | */ 36 | this.name = name; 37 | /** 38 | * List of defining occurrences of this variable (like in 'var ...' 39 | * statements or as parameter), as AST nodes. 40 | * @member {esprima.Identifier[]} Variable#identifiers 41 | */ 42 | this.identifiers = []; 43 | /** 44 | * List of {@link Reference|references} of this variable (excluding parameter entries) 45 | * in its defining scope and all nested scopes. For defining 46 | * occurrences only see {@link Variable#defs}. 47 | * @member {Reference[]} Variable#references 48 | */ 49 | this.references = []; 50 | 51 | /** 52 | * List of defining occurrences of this variable (like in 'var ...' 53 | * statements or as parameter), as custom objects. 54 | * @member {Definition[]} Variable#defs 55 | */ 56 | this.defs = []; 57 | 58 | this.tainted = false; 59 | /** 60 | * Whether this is a stack variable. 61 | * @member {boolean} Variable#stack 62 | */ 63 | this.stack = true; 64 | /** 65 | * Reference to the enclosing Scope. 66 | * @member {Scope} Variable#scope 67 | */ 68 | this.scope = scope; 69 | } 70 | } 71 | 72 | Variable.CatchClause = 'CatchClause'; 73 | Variable.Parameter = 'Parameter'; 74 | Variable.FunctionName = 'FunctionName'; 75 | Variable.ClassName = 'ClassName'; 76 | Variable.Variable = 'Variable'; 77 | Variable.ImportBinding = 'ImportBinding'; 78 | Variable.TDZ = 'TDZ'; 79 | Variable.ImplicitGlobalVariable = 'ImplicitGlobalVariable'; 80 | 81 | /* vim: set sw=4 ts=4 et tw=80 : */ 82 | -------------------------------------------------------------------------------- /test/es6-template-literal.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 template literal', function() { 29 | it('refer variables', function() { 30 | const ast = parse(` 31 | (function () { 32 | let i, j, k; 33 | function testing() { } 34 | let template = testing\`testing \${i} and \${j}\` 35 | return template; 36 | }()); 37 | `); 38 | 39 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 40 | expect(scopeManager.scopes).to.have.length(3); 41 | 42 | let scope = scopeManager.scopes[0]; 43 | expect(scope.type).to.be.equal('global'); 44 | expect(scope.block.type).to.be.equal('Program'); 45 | expect(scope.isStrict).to.be.false; 46 | expect(scope.variables).to.have.length(0); 47 | 48 | scope = scopeManager.scopes[1]; 49 | expect(scope.type).to.be.equal('function'); 50 | expect(scope.block.type).to.be.equal('FunctionExpression'); 51 | expect(scope.isStrict).to.be.false; 52 | expect(scope.variables).to.have.length(6); 53 | expect(scope.variables[0].name).to.be.equal('arguments'); 54 | expect(scope.variables[1].name).to.be.equal('i'); 55 | expect(scope.variables[2].name).to.be.equal('j'); 56 | expect(scope.variables[3].name).to.be.equal('k'); 57 | expect(scope.variables[4].name).to.be.equal('testing'); 58 | expect(scope.variables[5].name).to.be.equal('template'); 59 | expect(scope.references).to.have.length(5); 60 | expect(scope.references[0].identifier.name).to.be.equal('template'); 61 | expect(scope.references[1].identifier.name).to.be.equal('testing'); 62 | expect(scope.references[2].identifier.name).to.be.equal('i'); 63 | expect(scope.references[3].identifier.name).to.be.equal('j'); 64 | expect(scope.references[4].identifier.name).to.be.equal('template'); 65 | }); 66 | }); 67 | 68 | // vim: set sw=4 ts=4 et tw=80 : 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Escope ([escope](http://github.com/estools/escope)) is 2 | [ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm) 3 | scope analyzer extracted from [esmangle project](http://github.com/estools/esmangle). 4 | 5 | [![Build Status](https://travis-ci.org/estools/escope.png?branch=master)](https://travis-ci.org/estools/escope) 6 | 7 | ### Example 8 | 9 | ```js 10 | var escope = require('escope'); 11 | var esprima = require('esprima'); 12 | var estraverse = require('estraverse'); 13 | 14 | var ast = esprima.parse(code); 15 | var scopeManager = escope.analyze(ast); 16 | 17 | var currentScope = scopeManager.acquire(ast); // global scope 18 | 19 | estraverse.traverse(ast, { 20 | enter: function(node, parent) { 21 | // do stuff 22 | 23 | if (/Function/.test(node.type)) { 24 | currentScope = scopeManager.acquire(node); // get current function scope 25 | } 26 | }, 27 | leave: function(node, parent) { 28 | if (/Function/.test(node.type)) { 29 | currentScope = currentScope.upper; // set to parent scope 30 | } 31 | 32 | // do stuff 33 | } 34 | }); 35 | ``` 36 | 37 | ### Document 38 | 39 | Generated JSDoc is [here](http://estools.github.io/escope/). 40 | 41 | ### Demos and Tools 42 | 43 | Demonstration is [here](http://mazurov.github.io/escope-demo/) by [Sasha Mazurov](https://github.com/mazurov) (twitter: [@mazurov](http://twitter.com/mazurov)). [issue](https://github.com/estools/escope/issues/14) 44 | 45 | ![Demo](https://f.cloud.github.com/assets/75759/462920/7aa6dd40-b4f5-11e2-9f07-9f4e8d0415f9.gif) 46 | 47 | 48 | And there are tools constructed on Escope. 49 | 50 | - [Esmangle](https://github.com/estools/esmangle) is a minifier / mangler / optimizer. 51 | - [Eslevels](https://github.com/mazurov/eslevels) is a scope levels analyzer and [SublimeText plugin for scope context coloring](https://github.com/mazurov/sublime-levels) is constructed on it. 52 | - [Esgoggles](https://github.com/keeyipchan/esgoggles) is JavaScript code browser. 53 | 54 | 55 | ### License 56 | 57 | Copyright (C) 2012-2013 [Yusuke Suzuki](http://github.com/Constellation) 58 | (twitter: [@Constellation](http://twitter.com/Constellation)) and other contributors. 59 | 60 | Redistribution and use in source and binary forms, with or without 61 | modification, are permitted provided that the following conditions are met: 62 | 63 | * Redistributions of source code must retain the above copyright 64 | notice, this list of conditions and the following disclaimer. 65 | 66 | * Redistributions in binary form must reproduce the above copyright 67 | notice, this list of conditions and the following disclaimer in the 68 | documentation and/or other materials provided with the distribution. 69 | 70 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 71 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 72 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 73 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 74 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 75 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 76 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 77 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 78 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 79 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 80 | -------------------------------------------------------------------------------- /test/es6-rest-args.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse as esprima } from '../third_party/esprima'; 26 | import espree from '../third_party/espree'; 27 | import { analyze } from '..'; 28 | 29 | describe('ES6 rest arguments', function() { 30 | it('materialize rest argument in scope (esprima: rest property of FunctionDeclaration)', function() { 31 | const ast = esprima(` 32 | function foo(...bar) { 33 | return bar; 34 | } 35 | `); 36 | 37 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 38 | expect(scopeManager.scopes).to.have.length(2); 39 | 40 | let scope = scopeManager.scopes[0]; 41 | expect(scope.type).to.be.equal('global'); 42 | expect(scope.block.type).to.be.equal('Program'); 43 | expect(scope.isStrict).to.be.false; 44 | expect(scope.variables).to.have.length(1); 45 | 46 | scope = scopeManager.scopes[1]; 47 | expect(scope.type).to.be.equal('function'); 48 | expect(scope.variables).to.have.length(2); 49 | expect(scope.variables[0].name).to.be.equal('arguments'); 50 | expect(scope.variables[1].name).to.be.equal('bar'); 51 | expect(scope.variables[1].defs[0].name.name).to.be.equal('bar'); 52 | expect(scope.variables[1].defs[0].rest).to.be.true; 53 | }); 54 | 55 | it('materialize rest argument in scope (espree: RestElement)', function() { 56 | const ast = espree(` 57 | function foo(...bar) { 58 | return bar; 59 | } 60 | `); 61 | 62 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 63 | expect(scopeManager.scopes).to.have.length(2); 64 | 65 | let scope = scopeManager.scopes[0]; 66 | expect(scope.type).to.be.equal('global'); 67 | expect(scope.block.type).to.be.equal('Program'); 68 | expect(scope.isStrict).to.be.false; 69 | expect(scope.variables).to.have.length(1); 70 | 71 | scope = scopeManager.scopes[1]; 72 | expect(scope.type).to.be.equal('function'); 73 | expect(scope.variables).to.have.length(2); 74 | expect(scope.variables[0].name).to.be.equal('arguments'); 75 | expect(scope.variables[1].name).to.be.equal('bar'); 76 | expect(scope.variables[1].defs[0].name.name).to.be.equal('bar'); 77 | expect(scope.variables[1].defs[0].rest).to.be.true; 78 | }); 79 | }); 80 | 81 | // vim: set sw=4 ts=4 et tw=80 : 82 | -------------------------------------------------------------------------------- /test/es6-arrow-function-expression.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 arrow function expression', function() { 29 | it('materialize scope for arrow function expression', function() { 30 | const ast = parse(` 31 | var arrow = () => { 32 | let i = 0; 33 | var j = 20; 34 | console.log(i); 35 | } 36 | `); 37 | 38 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 39 | expect(scopeManager.scopes).to.have.length(2); 40 | 41 | let scope = scopeManager.scopes[0]; 42 | expect(scope.type).to.be.equal('global'); 43 | expect(scope.block.type).to.be.equal('Program'); 44 | expect(scope.isStrict).to.be.false; 45 | expect(scope.variables).to.have.length(1); 46 | 47 | scope = scopeManager.scopes[1]; 48 | expect(scope.type).to.be.equal('function'); 49 | expect(scope.block.type).to.be.equal('ArrowFunctionExpression'); 50 | expect(scope.isStrict).to.be.true; 51 | expect(scope.variables).to.have.length(2); 52 | // There's no "arguments" 53 | expect(scope.variables[0].name).to.be.equal('i'); 54 | expect(scope.variables[1].name).to.be.equal('j'); 55 | }); 56 | 57 | it('generate bindings for parameters', function() { 58 | const ast = parse(`var arrow = (a, b, c, d) => {}`); 59 | 60 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 61 | expect(scopeManager.scopes).to.have.length(2); 62 | 63 | let scope = scopeManager.scopes[0]; 64 | expect(scope.type).to.be.equal('global'); 65 | expect(scope.block.type).to.be.equal('Program'); 66 | expect(scope.isStrict).to.be.false; 67 | expect(scope.variables).to.have.length(1); 68 | 69 | scope = scopeManager.scopes[1]; 70 | expect(scope.type).to.be.equal('function'); 71 | expect(scope.block.type).to.be.equal('ArrowFunctionExpression'); 72 | expect(scope.isStrict).to.be.true; 73 | expect(scope.variables).to.have.length(4); 74 | // There's no "arguments" 75 | expect(scope.variables[0].name).to.be.equal('a'); 76 | expect(scope.variables[1].name).to.be.equal('b'); 77 | expect(scope.variables[2].name).to.be.equal('c'); 78 | expect(scope.variables[3].name).to.be.equal('d'); 79 | }); 80 | }); 81 | 82 | // vim: set sw=4 ts=4 et tw=80 : 83 | -------------------------------------------------------------------------------- /test/child-visitor-keys.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2016 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import esprima from 'esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('childVisitorKeys option', function() { 29 | it('should handle as a known node if the childVisitorKeys option was given.', function() { 30 | const ast = esprima.parse(` 31 | var foo = 0; 32 | `); 33 | 34 | ast.body[0].declarations[0].init.type = 'NumericLiteral'; 35 | 36 | // should no error 37 | analyze( 38 | ast, 39 | { 40 | fallback: 'none', 41 | childVisitorKeys: { 42 | NumericLiteral: [] 43 | } 44 | } 45 | ); 46 | }); 47 | 48 | it('should not visit to properties which are not given.', function() { 49 | const ast = esprima.parse(` 50 | let foo = bar; 51 | `); 52 | 53 | ast.body[0].declarations[0].init = { 54 | type: 'TestNode', 55 | argument: ast.body[0].declarations[0].init 56 | }; 57 | 58 | var result = analyze( 59 | ast, 60 | { 61 | childVisitorKeys: { 62 | TestNode: [] 63 | } 64 | } 65 | ); 66 | 67 | expect(result.scopes).to.have.length(1); 68 | const globalScope = result.scopes[0]; 69 | 70 | // `bar` in TestNode has not been visited. 71 | expect(globalScope.through).to.have.length(0); 72 | }); 73 | 74 | it('should visit to given properties.', function() { 75 | const ast = esprima.parse(` 76 | let foo = bar; 77 | `); 78 | 79 | ast.body[0].declarations[0].init = { 80 | type: 'TestNode', 81 | argument: ast.body[0].declarations[0].init 82 | }; 83 | 84 | var result = analyze( 85 | ast, 86 | { 87 | childVisitorKeys: { 88 | TestNode: ['argument'] 89 | } 90 | } 91 | ); 92 | 93 | expect(result.scopes).to.have.length(1); 94 | const globalScope = result.scopes[0]; 95 | 96 | // `bar` in TestNode has been visited. 97 | expect(globalScope.through).to.have.length(1); 98 | expect(globalScope.through[0].identifier.name).to.equal('bar'); 99 | }); 100 | }); 101 | 102 | // vim: set sw=4 ts=4 et tw=80 : 103 | -------------------------------------------------------------------------------- /test/es6-object.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 object', function() { 29 | it('method definition', function() { 30 | const ast = parse(` 31 | ({ 32 | constructor() { 33 | } 34 | })`); 35 | 36 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 37 | expect(scopeManager.scopes).to.have.length(2); 38 | 39 | let scope = scopeManager.scopes[0]; 40 | expect(scope.type).to.be.equal('global'); 41 | expect(scope.block.type).to.be.equal('Program'); 42 | expect(scope.isStrict).to.be.false; 43 | 44 | scope = scopeManager.scopes[1]; 45 | expect(scope.type).to.be.equal('function'); 46 | expect(scope.block.type).to.be.equal('FunctionExpression'); 47 | expect(scope.isStrict).to.be.false; 48 | expect(scope.variables).to.have.length(1); 49 | expect(scope.variables[0].name).to.be.equal('arguments'); 50 | expect(scope.references).to.have.length(0); 51 | }); 52 | 53 | it('computed property key may refer variables', function() { 54 | const ast = parse(` 55 | (function () { 56 | var yuyushiki = 42; 57 | ({ 58 | [yuyushiki]() { 59 | }, 60 | 61 | [yuyushiki + 40]() { 62 | } 63 | }) 64 | }()); 65 | `); 66 | 67 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 68 | expect(scopeManager.scopes).to.have.length(4); 69 | 70 | let scope = scopeManager.scopes[0]; 71 | expect(scope.type).to.be.equal('global'); 72 | expect(scope.block.type).to.be.equal('Program'); 73 | expect(scope.isStrict).to.be.false; 74 | 75 | scope = scopeManager.scopes[1]; 76 | expect(scope.type).to.be.equal('function'); 77 | expect(scope.block.type).to.be.equal('FunctionExpression'); 78 | expect(scope.isStrict).to.be.false; 79 | expect(scope.variables).to.have.length(2); 80 | expect(scope.variables[0].name).to.be.equal('arguments'); 81 | expect(scope.variables[1].name).to.be.equal('yuyushiki'); 82 | expect(scope.references).to.have.length(3); 83 | expect(scope.references[0].identifier.name).to.be.equal('yuyushiki'); 84 | expect(scope.references[1].identifier.name).to.be.equal('yuyushiki'); 85 | expect(scope.references[2].identifier.name).to.be.equal('yuyushiki'); 86 | }); 87 | }); 88 | 89 | // vim: set sw=4 ts=4 et tw=80 : 90 | -------------------------------------------------------------------------------- /test/nodejs-scope.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('nodejsScope option', function() { 29 | it('creates a function scope following the global scope immediately', function() { 30 | const ast = parse(` 31 | 'use strict'; 32 | var hello = 20; 33 | `); 34 | 35 | const scopeManager = analyze(ast, {ecmaVersion: 6, nodejsScope: true}); 36 | expect(scopeManager.scopes).to.have.length(2); 37 | 38 | let scope = scopeManager.scopes[0]; 39 | expect(scope.type).to.be.equal('global'); 40 | expect(scope.block.type).to.be.equal('Program'); 41 | expect(scope.isStrict).to.be.false; 42 | expect(scope.variables).to.have.length(0); 43 | 44 | scope = scopeManager.scopes[1]; 45 | expect(scope.type).to.be.equal('function'); 46 | expect(scope.block.type).to.be.equal('Program'); 47 | expect(scope.isStrict).to.be.true; 48 | expect(scope.variables).to.have.length(2); 49 | expect(scope.variables[0].name).to.be.equal('arguments'); 50 | expect(scope.variables[1].name).to.be.equal('hello'); 51 | }); 52 | 53 | it('creates a function scope following the global scope immediately and creates module scope', function() { 54 | const ast = parse(` 55 | import {x as v} from "mod";`, 56 | {sourceType: 'module' } 57 | ); 58 | 59 | const scopeManager = analyze(ast, {ecmaVersion: 6, nodejsScope: true, sourceType: 'module'}); 60 | expect(scopeManager.scopes).to.have.length(3); 61 | 62 | let scope = scopeManager.scopes[0]; 63 | expect(scope.type).to.be.equal('global'); 64 | expect(scope.block.type).to.be.equal('Program'); 65 | expect(scope.isStrict).to.be.false; 66 | expect(scope.variables).to.have.length(0); 67 | 68 | scope = scopeManager.scopes[1]; 69 | expect(scope.type).to.be.equal('function'); 70 | expect(scope.block.type).to.be.equal('Program'); 71 | expect(scope.isStrict).to.be.false; 72 | expect(scope.variables).to.have.length(1); 73 | expect(scope.variables[0].name).to.be.equal('arguments'); 74 | 75 | scope = scopeManager.scopes[2]; 76 | expect(scope.type).to.be.equal('module'); 77 | expect(scope.variables).to.have.length(1); 78 | expect(scope.variables[0].name).to.be.equal('v'); 79 | expect(scope.variables[0].defs[0].type).to.be.equal('ImportBinding'); 80 | expect(scope.references).to.have.length(0); 81 | }); 82 | }); 83 | 84 | // vim: set sw=4 ts=4 et tw=80 : 85 | -------------------------------------------------------------------------------- /test/es6-catch.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | import { expect } from 'chai'; 24 | import { parse } from '../third_party/esprima'; 25 | import { analyze } from '..'; 26 | 27 | describe('ES6 catch', function() { 28 | it('takes binding pattern', function() { 29 | const ast = parse(` 30 | try { 31 | } catch ({ a, b, c, d }) { 32 | let e = 20; 33 | a; 34 | b; 35 | let c = 30; 36 | c; 37 | d; 38 | } 39 | `); 40 | 41 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 42 | expect(scopeManager.scopes).to.have.length(4); 43 | 44 | let scope = scopeManager.scopes[0]; 45 | expect(scope.type).to.be.equal('global'); 46 | expect(scope.block.type).to.be.equal('Program'); 47 | expect(scope.isStrict).to.be.false; 48 | expect(scope.variables).to.have.length(0); 49 | expect(scope.references).to.have.length(0); 50 | 51 | scope = scopeManager.scopes[1]; 52 | expect(scope.type).to.be.equal('block'); 53 | expect(scope.block.type).to.be.equal('BlockStatement'); 54 | expect(scope.isStrict).to.be.false; 55 | expect(scope.variables).to.have.length(0); 56 | expect(scope.references).to.have.length(0); 57 | 58 | scope = scopeManager.scopes[2]; 59 | expect(scope.type).to.be.equal('catch'); 60 | expect(scope.block.type).to.be.equal('CatchClause'); 61 | expect(scope.isStrict).to.be.false; 62 | 63 | // FIXME After Esprima's bug is fixed, I'll add tests #33 64 | // https://github.com/estools/escope/issues/33#issuecomment-64135832 65 | // 66 | // expect(scope.variables).to.have.length(4); 67 | // expect(scope.variables[0].name).to.be.equal('a'); 68 | // expect(scope.variables[1].name).to.be.equal('b'); 69 | // expect(scope.variables[2].name).to.be.equal('c'); 70 | // expect(scope.variables[3].name).to.be.equal('d'); 71 | // expect(scope.references).to.have.length(0); 72 | 73 | scope = scopeManager.scopes[3]; 74 | expect(scope.type).to.be.equal('block'); 75 | expect(scope.block.type).to.be.equal('BlockStatement'); 76 | expect(scope.isStrict).to.be.false; 77 | expect(scope.variables).to.have.length(2); 78 | expect(scope.variables.map(variable => variable.name)).to.be.eql([ 79 | 'e', 80 | 'c' 81 | ]); 82 | expect(scope.references.map(ref => ref.identifier.name)).to.be.eql([ 83 | 'e', 84 | 'a', 85 | 'b', 86 | 'c', 87 | 'c', 88 | 'd' 89 | ]); 90 | }); 91 | }); 92 | 93 | // vim: set sw=4 ts=4 et tw=80 : 94 | -------------------------------------------------------------------------------- /src/pattern-visitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | import { Syntax } from 'estraverse'; 26 | import esrecurse from 'esrecurse'; 27 | 28 | function getLast(xs) { 29 | return xs[xs.length - 1] || null; 30 | } 31 | 32 | export default class PatternVisitor extends esrecurse.Visitor { 33 | static isPattern(node) { 34 | var nodeType = node.type; 35 | return ( 36 | nodeType === Syntax.Identifier || 37 | nodeType === Syntax.ObjectPattern || 38 | nodeType === Syntax.ArrayPattern || 39 | nodeType === Syntax.SpreadElement || 40 | nodeType === Syntax.RestElement || 41 | nodeType === Syntax.AssignmentPattern 42 | ); 43 | } 44 | 45 | constructor(options, rootPattern, callback) { 46 | super(null, options); 47 | this.rootPattern = rootPattern; 48 | this.callback = callback; 49 | this.assignments = []; 50 | this.rightHandNodes = []; 51 | this.restElements = []; 52 | } 53 | 54 | Identifier(pattern) { 55 | const lastRestElement = getLast(this.restElements); 56 | this.callback(pattern, { 57 | topLevel: pattern === this.rootPattern, 58 | rest: lastRestElement != null && lastRestElement.argument === pattern, 59 | assignments: this.assignments 60 | }); 61 | } 62 | 63 | Property(property) { 64 | // Computed property's key is a right hand node. 65 | if (property.computed) { 66 | this.rightHandNodes.push(property.key); 67 | } 68 | 69 | // If it's shorthand, its key is same as its value. 70 | // If it's shorthand and has its default value, its key is same as its value.left (the value is AssignmentPattern). 71 | // If it's not shorthand, the name of new variable is its value's. 72 | this.visit(property.value); 73 | } 74 | 75 | ArrayPattern(pattern) { 76 | var i, iz, element; 77 | for (i = 0, iz = pattern.elements.length; i < iz; ++i) { 78 | element = pattern.elements[i]; 79 | this.visit(element); 80 | } 81 | } 82 | 83 | AssignmentPattern(pattern) { 84 | this.assignments.push(pattern); 85 | this.visit(pattern.left); 86 | this.rightHandNodes.push(pattern.right); 87 | this.assignments.pop(); 88 | } 89 | 90 | RestElement(pattern) { 91 | this.restElements.push(pattern); 92 | this.visit(pattern.argument); 93 | this.restElements.pop(); 94 | } 95 | 96 | MemberExpression(node) { 97 | // Computed property's key is a right hand node. 98 | if (node.computed) { 99 | this.rightHandNodes.push(node.property); 100 | } 101 | // the object is only read, write to its property. 102 | this.rightHandNodes.push(node.object); 103 | } 104 | 105 | // 106 | // ForInStatement.left and AssignmentExpression.left are LeftHandSideExpression. 107 | // By spec, LeftHandSideExpression is Pattern or MemberExpression. 108 | // (see also: https://github.com/estree/estree/pull/20#issuecomment-74584758) 109 | // But espree 2.0 and esprima 2.0 parse to ArrayExpression, ObjectExpression, etc... 110 | // 111 | 112 | SpreadElement(node) { 113 | this.visit(node.argument); 114 | } 115 | 116 | ArrayExpression(node) { 117 | node.elements.forEach(this.visit, this); 118 | } 119 | 120 | AssignmentExpression(node) { 121 | this.assignments.push(node); 122 | this.visit(node.left); 123 | this.rightHandNodes.push(node.right); 124 | this.assignments.pop(); 125 | } 126 | 127 | CallExpression(node) { 128 | // arguments are right hand nodes. 129 | node.arguments.forEach(a => { this.rightHandNodes.push(a); }); 130 | this.visit(node.callee); 131 | } 132 | } 133 | 134 | /* vim: set sw=4 ts=4 et tw=80 : */ 135 | -------------------------------------------------------------------------------- /src/reference.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | const READ = 0x1; 26 | const WRITE = 0x2; 27 | const RW = READ | WRITE; 28 | 29 | /** 30 | * A Reference represents a single occurrence of an identifier in code. 31 | * @class Reference 32 | */ 33 | export default class Reference { 34 | constructor(ident, scope, flag, writeExpr, maybeImplicitGlobal, partial, init) { 35 | /** 36 | * Identifier syntax node. 37 | * @member {esprima#Identifier} Reference#identifier 38 | */ 39 | this.identifier = ident; 40 | /** 41 | * Reference to the enclosing Scope. 42 | * @member {Scope} Reference#from 43 | */ 44 | this.from = scope; 45 | /** 46 | * Whether the reference comes from a dynamic scope (such as 'eval', 47 | * 'with', etc.), and may be trapped by dynamic scopes. 48 | * @member {boolean} Reference#tainted 49 | */ 50 | this.tainted = false; 51 | /** 52 | * The variable this reference is resolved with. 53 | * @member {Variable} Reference#resolved 54 | */ 55 | this.resolved = null; 56 | /** 57 | * The read-write mode of the reference. (Value is one of {@link 58 | * Reference.READ}, {@link Reference.RW}, {@link Reference.WRITE}). 59 | * @member {number} Reference#flag 60 | * @private 61 | */ 62 | this.flag = flag; 63 | if (this.isWrite()) { 64 | /** 65 | * If reference is writeable, this is the tree being written to it. 66 | * @member {esprima#Node} Reference#writeExpr 67 | */ 68 | this.writeExpr = writeExpr; 69 | /** 70 | * Whether the Reference might refer to a partial value of writeExpr. 71 | * @member {boolean} Reference#partial 72 | */ 73 | this.partial = partial; 74 | /** 75 | * Whether the Reference is to write of initialization. 76 | * @member {boolean} Reference#init 77 | */ 78 | this.init = init; 79 | } 80 | this.__maybeImplicitGlobal = maybeImplicitGlobal; 81 | } 82 | 83 | /** 84 | * Whether the reference is static. 85 | * @method Reference#isStatic 86 | * @return {boolean} 87 | */ 88 | isStatic() { 89 | return !this.tainted && this.resolved && this.resolved.scope.isStatic(); 90 | } 91 | 92 | /** 93 | * Whether the reference is writeable. 94 | * @method Reference#isWrite 95 | * @return {boolean} 96 | */ 97 | isWrite() { 98 | return !!(this.flag & Reference.WRITE); 99 | } 100 | 101 | /** 102 | * Whether the reference is readable. 103 | * @method Reference#isRead 104 | * @return {boolean} 105 | */ 106 | isRead() { 107 | return !!(this.flag & Reference.READ); 108 | } 109 | 110 | /** 111 | * Whether the reference is read-only. 112 | * @method Reference#isReadOnly 113 | * @return {boolean} 114 | */ 115 | isReadOnly() { 116 | return this.flag === Reference.READ; 117 | } 118 | 119 | /** 120 | * Whether the reference is write-only. 121 | * @method Reference#isWriteOnly 122 | * @return {boolean} 123 | */ 124 | isWriteOnly() { 125 | return this.flag === Reference.WRITE; 126 | } 127 | 128 | /** 129 | * Whether the reference is read-write. 130 | * @method Reference#isReadWrite 131 | * @return {boolean} 132 | */ 133 | isReadWrite() { 134 | return this.flag === Reference.RW; 135 | } 136 | } 137 | 138 | /** 139 | * @constant Reference.READ 140 | * @private 141 | */ 142 | Reference.READ = READ; 143 | /** 144 | * @constant Reference.WRITE 145 | * @private 146 | */ 147 | Reference.WRITE = WRITE; 148 | /** 149 | * @constant Reference.RW 150 | * @private 151 | */ 152 | Reference.RW = RW; 153 | 154 | /* vim: set sw=4 ts=4 et tw=80 : */ 155 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | 'use strict'; 26 | 27 | var gulp = require('gulp'), 28 | mocha = require('gulp-mocha'), 29 | babel = require('gulp-babel'), 30 | git = require('gulp-git'), 31 | bump = require('gulp-bump'), 32 | filter = require('gulp-filter'), 33 | tagVersion = require('gulp-tag-version'), 34 | sourcemaps = require('gulp-sourcemaps'), 35 | plumber = require('gulp-plumber'), 36 | source = require('vinyl-source-stream'), 37 | browserify = require('browserify'), 38 | lazypipe = require('lazypipe'), 39 | eslint = require('gulp-eslint'), 40 | fs = require('fs'); 41 | 42 | require('babel-register')({ 43 | only: /escope\/(src|test)\// 44 | }); 45 | 46 | var TEST = [ 'test/*.js' ]; 47 | var SOURCE = [ 'src/**/*.js' ]; 48 | 49 | var ESLINT_OPTION = { 50 | rules: { 51 | 'quotes': 0, 52 | 'eqeqeq': 0, 53 | 'no-use-before-define': 0, 54 | 'no-shadow': 0, 55 | 'no-new': 0, 56 | 'no-underscore-dangle': 0, 57 | 'no-multi-spaces': 0, 58 | 'no-native-reassign': 0, 59 | 'no-loop-func': 0, 60 | 'no-lone-blocks': 0 61 | }, 62 | ecmaFeatures: { 63 | jsx: false, 64 | modules: true 65 | }, 66 | env: { 67 | node: true, 68 | es6: true 69 | } 70 | }; 71 | 72 | var BABEL_OPTIONS = JSON.parse(fs.readFileSync('.babelrc', { encoding: 'utf8' })); 73 | 74 | var build = lazypipe() 75 | .pipe(sourcemaps.init) 76 | .pipe(babel, BABEL_OPTIONS) 77 | .pipe(sourcemaps.write) 78 | .pipe(gulp.dest, 'lib'); 79 | 80 | gulp.task('build-for-watch', function () { 81 | return gulp.src(SOURCE).pipe(plumber()).pipe(build()); 82 | }); 83 | 84 | gulp.task('build', function () { 85 | return gulp.src(SOURCE).pipe(build()); 86 | }); 87 | 88 | gulp.task('browserify', [ 'build' ], function () { 89 | return browserify({ 90 | entries: [ './lib/index.js' ] 91 | }) 92 | .bundle() 93 | .pipe(source('bundle.js')) 94 | .pipe(gulp.dest('build')) 95 | }); 96 | 97 | gulp.task('test', [ 'build' ], function () { 98 | return gulp.src(TEST) 99 | .pipe(mocha({ 100 | reporter: 'spec', 101 | timeout: 100000 // 100s 102 | })); 103 | }); 104 | 105 | gulp.task('watch', [ 'build-for-watch' ], function () { 106 | gulp.watch(SOURCE, [ 'build-for-watch' ]); 107 | }); 108 | 109 | // Currently, not works for ES6. 110 | gulp.task('lint', function () { 111 | return gulp.src(SOURCE) 112 | .pipe(eslint(ESLINT_OPTION)) 113 | .pipe(eslint.formatEach('stylish', process.stderr)) 114 | .pipe(eslint.failOnError()); 115 | }); 116 | 117 | /** 118 | * Bumping version number and tagging the repository with it. 119 | * Please read http://semver.org/ 120 | * 121 | * You can use the commands 122 | * 123 | * gulp patch # makes v0.1.0 -> v0.1.1 124 | * gulp feature # makes v0.1.1 -> v0.2.0 125 | * gulp release # makes v0.2.1 -> v1.0.0 126 | * 127 | * To bump the version numbers accordingly after you did a patch, 128 | * introduced a feature or made a backwards-incompatible release. 129 | */ 130 | 131 | function inc(importance) { 132 | // get all the files to bump version in 133 | return gulp.src(['./package.json']) 134 | // bump the version number in those files 135 | .pipe(bump({type: importance})) 136 | // save it back to filesystem 137 | .pipe(gulp.dest('./')) 138 | // commit the changed version number 139 | .pipe(git.commit('Bumps package version')) 140 | // read only one file to get the version number 141 | .pipe(filter('package.json')) 142 | // **tag it in the repository** 143 | .pipe(tagVersion({ 144 | prefix: '' 145 | })); 146 | } 147 | 148 | gulp.task('patch', [ 'build' ], function () { return inc('patch'); }) 149 | gulp.task('minor', [ 'build' ], function () { return inc('minor'); }) 150 | gulp.task('major', [ 'build' ], function () { return inc('major'); }) 151 | 152 | gulp.task('travis', [ 'test' ]); 153 | gulp.task('default', [ 'travis' ]); 154 | -------------------------------------------------------------------------------- /test/implied-strict.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('impliedStrict option', function() { 29 | it('ensures all user scopes are strict if ecmaVersion >= 5', function() { 30 | const ast = parse(` 31 | function foo() { 32 | function bar() { 33 | 'use strict'; 34 | } 35 | } 36 | `); 37 | 38 | const scopeManager = analyze(ast, {ecmaVersion: 5, impliedStrict: true}); 39 | expect(scopeManager.scopes).to.have.length(3); 40 | 41 | let scope = scopeManager.scopes[0]; 42 | expect(scope.type).to.be.equal('global'); 43 | expect(scope.block.type).to.be.equal('Program'); 44 | expect(scope.isStrict).to.be.true; 45 | 46 | scope = scopeManager.scopes[1]; 47 | expect(scope.type).to.be.equal('function'); 48 | expect(scope.block.type).to.be.equal('FunctionDeclaration'); 49 | expect(scope.isStrict).to.be.true; 50 | 51 | scope = scopeManager.scopes[2]; 52 | expect(scope.type).to.be.equal('function'); 53 | expect(scope.block.type).to.be.equal('FunctionDeclaration'); 54 | expect(scope.isStrict).to.be.true; 55 | }); 56 | 57 | it('ensures impliedStrict option is only effective when ecmaVersion option >= 5', function() { 58 | const ast = parse(` 59 | function foo() {} 60 | `); 61 | 62 | const scopeManager = analyze(ast, {ecmaVersion: 3, impliedStrict: true}); 63 | expect(scopeManager.scopes).to.have.length(2); 64 | 65 | let scope = scopeManager.scopes[0]; 66 | expect(scope.type).to.be.equal('global'); 67 | expect(scope.block.type).to.be.equal('Program'); 68 | expect(scope.isStrict).to.be.false; 69 | 70 | scope = scopeManager.scopes[1]; 71 | expect(scope.type).to.be.equal('function'); 72 | expect(scope.block.type).to.be.equal('FunctionDeclaration'); 73 | expect(scope.isStrict).to.be.false; 74 | }); 75 | 76 | it('omits a nodejs global scope when ensuring all user scopes are strict', function() { 77 | const ast = parse(` 78 | function foo() {} 79 | `); 80 | 81 | let scopeManager = analyze(ast, {ecmaVersion: 5, nodejsScope: true, impliedStrict: true}); 82 | expect(scopeManager.scopes).to.have.length(3); 83 | 84 | let scope = scopeManager.scopes[0]; 85 | expect(scope.type).to.be.equal('global'); 86 | expect(scope.block.type).to.be.equal('Program'); 87 | expect(scope.isStrict).to.be.false; 88 | 89 | scope = scopeManager.scopes[1]; 90 | expect(scope.type).to.be.equal('function'); 91 | expect(scope.block.type).to.be.equal('Program'); 92 | expect(scope.isStrict).to.be.true; 93 | 94 | scope = scopeManager.scopes[2]; 95 | expect(scope.type).to.be.equal('function'); 96 | expect(scope.block.type).to.be.equal('FunctionDeclaration'); 97 | expect(scope.isStrict).to.be.true; 98 | }); 99 | 100 | it('omits a module global scope when ensuring all user scopes are strict', function() { 101 | const ast = parse(` 102 | function foo() {}`, 103 | {sourceType: 'module'} 104 | ); 105 | 106 | let scopeManager = analyze(ast, {ecmaVersion: 6, impliedStrict: true, sourceType: 'module'}); 107 | expect(scopeManager.scopes).to.have.length(3); 108 | 109 | let scope = scopeManager.scopes[0]; 110 | expect(scope.type).to.be.equal('global'); 111 | expect(scope.block.type).to.be.equal('Program'); 112 | expect(scope.isStrict).to.be.false; 113 | 114 | scope = scopeManager.scopes[1]; 115 | expect(scope.type).to.be.equal('module'); 116 | expect(scope.isStrict).to.be.true; 117 | 118 | scope = scopeManager.scopes[2]; 119 | expect(scope.type).to.be.equal('function'); 120 | expect(scope.block.type).to.be.equal('FunctionDeclaration'); 121 | expect(scope.isStrict).to.be.true; 122 | }); 123 | }); 124 | 125 | // vim: set sw=4 ts=4 et tw=80 : 126 | -------------------------------------------------------------------------------- /test/es6-import.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import espree from '../third_party/espree'; 26 | import { analyze } from '..'; 27 | 28 | describe('import declaration', function() { 29 | // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-static-and-runtme-semantics-module-records 30 | it('should import names from source', function() { 31 | const ast = espree(`import v from "mod";`, {sourceType: 'module'}); 32 | 33 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 34 | expect(scopeManager.scopes).to.have.length(2); 35 | const globalScope = scopeManager.scopes[0]; 36 | expect(globalScope.type).to.be.equal('global'); 37 | expect(globalScope.variables).to.have.length(0); 38 | expect(globalScope.references).to.have.length(0); 39 | 40 | const scope = scopeManager.scopes[1]; 41 | expect(scope.type).to.be.equal('module'); 42 | expect(scope.isStrict).to.be.true; 43 | expect(scope.variables).to.have.length(1); 44 | expect(scope.variables[0].name).to.be.equal('v'); 45 | expect(scope.variables[0].defs[0].type).to.be.equal('ImportBinding'); 46 | expect(scope.references).to.have.length(0); 47 | }); 48 | 49 | it('should import namespaces', function() { 50 | const ast = espree( `import * as ns from "mod";`, {sourceType: 'module' 51 | }); 52 | 53 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 54 | expect(scopeManager.scopes).to.have.length(2); 55 | const globalScope = scopeManager.scopes[0]; 56 | expect(globalScope.type).to.be.equal('global'); 57 | expect(globalScope.variables).to.have.length(0); 58 | expect(globalScope.references).to.have.length(0); 59 | 60 | const scope = scopeManager.scopes[1]; 61 | expect(scope.type).to.be.equal('module'); 62 | expect(scope.isStrict).to.be.true; 63 | expect(scope.variables).to.have.length(1); 64 | expect(scope.variables[0].name).to.be.equal('ns'); 65 | expect(scope.variables[0].defs[0].type).to.be.equal('ImportBinding'); 66 | expect(scope.references).to.have.length(0); 67 | }); 68 | 69 | it('should import insided names#1', function() { 70 | const ast = espree(`import {x} from "mod";`, {sourceType: 'module' 71 | }); 72 | 73 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 74 | expect(scopeManager.scopes).to.have.length(2); 75 | const globalScope = scopeManager.scopes[0]; 76 | expect(globalScope.type).to.be.equal('global'); 77 | expect(globalScope.variables).to.have.length(0); 78 | expect(globalScope.references).to.have.length(0); 79 | 80 | const scope = scopeManager.scopes[1]; 81 | expect(scope.type).to.be.equal('module'); 82 | expect(scope.isStrict).to.be.true; 83 | expect(scope.variables).to.have.length(1); 84 | expect(scope.variables[0].name).to.be.equal('x'); 85 | expect(scope.variables[0].defs[0].type).to.be.equal('ImportBinding'); 86 | expect(scope.references).to.have.length(0); 87 | }); 88 | 89 | it('should import insided names#2', function() { 90 | const ast = espree(`import {x as v} from "mod";`, {sourceType: 'module'}); 91 | 92 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 93 | expect(scopeManager.scopes).to.have.length(2); 94 | const globalScope = scopeManager.scopes[0]; 95 | expect(globalScope.type).to.be.equal('global'); 96 | expect(globalScope.variables).to.have.length(0); 97 | expect(globalScope.references).to.have.length(0); 98 | 99 | const scope = scopeManager.scopes[1]; 100 | expect(scope.type).to.be.equal('module'); 101 | expect(scope.isStrict).to.be.true; 102 | expect(scope.variables).to.have.length(1); 103 | expect(scope.variables[0].name).to.be.equal('v'); 104 | expect(scope.variables[0].defs[0].type).to.be.equal('ImportBinding'); 105 | expect(scope.references).to.have.length(0); 106 | }); 107 | 108 | // TODO: Should parse it. 109 | // import from "mod"; 110 | }); 111 | 112 | // vim: set sw=4 ts=4 et tw=80 : 113 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2012-2014 Yusuke Suzuki 3 | Copyright (C) 2013 Alex Seville 4 | Copyright (C) 2014 Thiago de Arruda 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | /** 28 | * Escope (escope) is an ECMAScript 30 | * scope analyzer extracted from the esmangle project. 32 | *

33 | * escope finds lexical scopes in a source program, i.e. areas of that 34 | * program where different occurrences of the same identifier refer to the same 35 | * variable. With each scope the contained variables are collected, and each 36 | * identifier reference in code is linked to its corresponding variable (if 37 | * possible). 38 | *

39 | * escope works on a syntax tree of the parsed source code which has 40 | * to adhere to the 42 | * Mozilla Parser API. E.g. esprima is a parser 43 | * that produces such syntax trees. 44 | *

45 | * The main interface is the {@link analyze} function. 46 | * @module escope 47 | */ 48 | 49 | /*jslint bitwise:true */ 50 | 51 | import assert from 'assert'; 52 | 53 | import ScopeManager from './scope-manager'; 54 | import Referencer from './referencer'; 55 | import Reference from './reference'; 56 | import Variable from './variable'; 57 | import Scope from './scope'; 58 | import { version } from '../package.json'; 59 | 60 | function defaultOptions() { 61 | return { 62 | optimistic: false, 63 | directive: false, 64 | nodejsScope: false, 65 | impliedStrict: false, 66 | sourceType: 'script', // one of ['script', 'module'] 67 | ecmaVersion: 5, 68 | childVisitorKeys: null, 69 | fallback: 'iteration' 70 | }; 71 | } 72 | 73 | function updateDeeply(target, override) { 74 | var key, val; 75 | 76 | function isHashObject(target) { 77 | return typeof target === 'object' && target instanceof Object && !(target instanceof Array) && !(target instanceof RegExp); 78 | } 79 | 80 | for (key in override) { 81 | if (override.hasOwnProperty(key)) { 82 | val = override[key]; 83 | if (isHashObject(val)) { 84 | if (isHashObject(target[key])) { 85 | updateDeeply(target[key], val); 86 | } else { 87 | target[key] = updateDeeply({}, val); 88 | } 89 | } else { 90 | target[key] = val; 91 | } 92 | } 93 | } 94 | return target; 95 | } 96 | 97 | /** 98 | * Main interface function. Takes an Esprima syntax tree and returns the 99 | * analyzed scopes. 100 | * @function analyze 101 | * @param {esprima.Tree} tree 102 | * @param {Object} providedOptions - Options that tailor the scope analysis 103 | * @param {boolean} [providedOptions.optimistic=false] - the optimistic flag 104 | * @param {boolean} [providedOptions.directive=false]- the directive flag 105 | * @param {boolean} [providedOptions.ignoreEval=false]- whether to check 'eval()' calls 106 | * @param {boolean} [providedOptions.nodejsScope=false]- whether the whole 107 | * script is executed under node.js environment. When enabled, escope adds 108 | * a function scope immediately following the global scope. 109 | * @param {boolean} [providedOptions.impliedStrict=false]- implied strict mode 110 | * (if ecmaVersion >= 5). 111 | * @param {string} [providedOptions.sourceType='script']- the source type of the script. one of 'script' and 'module' 112 | * @param {number} [providedOptions.ecmaVersion=5]- which ECMAScript version is considered 113 | * @param {Object} [providedOptions.childVisitorKeys=null] - Additional known visitor keys. See [esrecurse](https://github.com/estools/esrecurse)'s the `childVisitorKeys` option. 114 | * @param {string} [providedOptions.fallback='iteration'] - A kind of the fallback in order to encounter with unknown node. See [esrecurse](https://github.com/estools/esrecurse)'s the `fallback` option. 115 | * @return {ScopeManager} 116 | */ 117 | export function analyze(tree, providedOptions) { 118 | var scopeManager, referencer, options; 119 | 120 | options = updateDeeply(defaultOptions(), providedOptions); 121 | 122 | scopeManager = new ScopeManager(options); 123 | 124 | referencer = new Referencer(options, scopeManager); 125 | referencer.visit(tree); 126 | 127 | assert(scopeManager.__currentScope === null, 'currentScope should be null.'); 128 | 129 | return scopeManager; 130 | } 131 | 132 | export { 133 | /** @name module:escope.version */ 134 | version, 135 | /** @name module:escope.Reference */ 136 | Reference, 137 | /** @name module:escope.Variable */ 138 | Variable, 139 | /** @name module:escope.Scope */ 140 | Scope, 141 | /** @name module:escope.ScopeManager */ 142 | ScopeManager 143 | }; 144 | 145 | 146 | /* vim: set sw=4 ts=4 et tw=80 : */ 147 | -------------------------------------------------------------------------------- /test/implicit-global-reference.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Yusuke Suzuki 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 13 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 16 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 19 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 21 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | 24 | import { expect } from 'chai'; 25 | import { analyze } from '..'; 26 | import { parse } from 'esprima'; 27 | 28 | describe('implicit global reference', function() { 29 | it('assignments global scope', function() { 30 | const ast = parse(` 31 | var x = 20; 32 | x = 300; 33 | `); 34 | 35 | const { scopes } = analyze(ast); 36 | 37 | expect(scopes.map(scope => scope.variables.map(variable => variable.defs.map(def => def.type)))).to.be.eql( 38 | [ 39 | [ 40 | [ 41 | 'Variable' 42 | ] 43 | ] 44 | ] 45 | ); 46 | 47 | expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql([]); 48 | }); 49 | 50 | it('assignments global scope without definition', function() { 51 | const ast = parse(` 52 | x = 300; 53 | x = 300; 54 | `); 55 | 56 | const { scopes } = analyze(ast); 57 | 58 | expect(scopes.map(scope => scope.variables.map(variable => variable.defs.map(def => def.type)))).to.be.eql( 59 | [ 60 | [ 61 | ] 62 | ] 63 | ); 64 | 65 | expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql( 66 | [ 67 | 'x' 68 | ] 69 | ); 70 | }); 71 | 72 | it('assignments global scope without definition eval', function() { 73 | const ast = parse(` 74 | function inner() { 75 | eval(str); 76 | x = 300; 77 | } 78 | `); 79 | 80 | const { scopes } = analyze(ast); 81 | 82 | expect(scopes.map(scope => scope.variables.map(variable => variable.defs.map(def => def.type)))).to.be.eql( 83 | [ 84 | [ 85 | [ 86 | 'FunctionName' 87 | ] 88 | ], 89 | [ 90 | [ 91 | ] 92 | ] 93 | ] 94 | ); 95 | 96 | expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql([]); 97 | }); 98 | 99 | it('assignment leaks', function() { 100 | const ast = parse(` 101 | function outer() { 102 | x = 20; 103 | } 104 | `); 105 | 106 | const { scopes } = analyze(ast); 107 | 108 | expect(scopes.map(scope => scope.variables.map(variable => variable.name))).to.be.eql( 109 | [ 110 | [ 111 | 'outer' 112 | ], 113 | [ 114 | 'arguments' 115 | ] 116 | ] 117 | ); 118 | 119 | expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql( 120 | [ 121 | 'x' 122 | ] 123 | ); 124 | }); 125 | 126 | it('assignment doesn\'t leak', function() { 127 | const ast = parse(` 128 | function outer() { 129 | function inner() { 130 | x = 20; 131 | } 132 | var x; 133 | } 134 | `); 135 | 136 | const { scopes } = analyze(ast); 137 | 138 | expect(scopes.map(scope => scope.variables.map(variable => variable.name))).to.be.eql( 139 | [ 140 | [ 141 | 'outer' 142 | ], 143 | [ 144 | 'arguments', 145 | 'inner', 146 | 'x' 147 | ], 148 | [ 149 | 'arguments' 150 | ] 151 | ] 152 | ); 153 | 154 | expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql([]); 155 | }); 156 | 157 | 158 | it('for-in-statement leaks', function() { 159 | const ast = parse(` 160 | function outer() { 161 | for (x in y) { } 162 | }`); 163 | 164 | const { scopes } = analyze(ast); 165 | 166 | expect(scopes.map(scope => scope.variables.map(variable => variable.name))).to.be.eql( 167 | [ 168 | [ 169 | 'outer' 170 | ], 171 | [ 172 | 'arguments' 173 | ] 174 | ] 175 | ); 176 | 177 | expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql( 178 | [ 179 | 'x' 180 | ] 181 | ); 182 | }); 183 | 184 | it('for-in-statement doesn\'t leaks', function() { 185 | const ast = parse(` 186 | function outer() { 187 | function inner() { 188 | for (x in y) { } 189 | } 190 | var x; 191 | } 192 | `); 193 | 194 | const { scopes } = analyze(ast); 195 | 196 | expect(scopes.map(scope => scope.variables.map(variable => variable.name))).to.be.eql( 197 | [ 198 | [ 199 | 'outer' 200 | ], 201 | [ 202 | 'arguments', 203 | 'inner', 204 | 'x' 205 | ], 206 | [ 207 | 'arguments' 208 | ] 209 | ] 210 | ); 211 | 212 | expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql([]); 213 | }); 214 | }); 215 | -------------------------------------------------------------------------------- /src/scope-manager.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | import WeakMap from 'es6-weak-map'; 26 | import Scope from './scope'; 27 | import assert from 'assert'; 28 | 29 | import { 30 | GlobalScope, 31 | CatchScope, 32 | WithScope, 33 | ModuleScope, 34 | ClassScope, 35 | SwitchScope, 36 | FunctionScope, 37 | ForScope, 38 | TDZScope, 39 | FunctionExpressionNameScope, 40 | BlockScope 41 | } from './scope'; 42 | 43 | /** 44 | * @class ScopeManager 45 | */ 46 | export default class ScopeManager { 47 | constructor(options) { 48 | this.scopes = []; 49 | this.globalScope = null; 50 | this.__nodeToScope = new WeakMap(); 51 | this.__currentScope = null; 52 | this.__options = options; 53 | this.__declaredVariables = new WeakMap(); 54 | } 55 | 56 | __useDirective() { 57 | return this.__options.directive; 58 | } 59 | 60 | __isOptimistic() { 61 | return this.__options.optimistic; 62 | } 63 | 64 | __ignoreEval() { 65 | return this.__options.ignoreEval; 66 | } 67 | 68 | __isNodejsScope() { 69 | return this.__options.nodejsScope; 70 | } 71 | 72 | isModule() { 73 | return this.__options.sourceType === 'module'; 74 | } 75 | 76 | isImpliedStrict() { 77 | return this.__options.impliedStrict; 78 | } 79 | 80 | isStrictModeSupported() { 81 | return this.__options.ecmaVersion >= 5; 82 | } 83 | 84 | // Returns appropriate scope for this node. 85 | __get(node) { 86 | return this.__nodeToScope.get(node); 87 | } 88 | 89 | /** 90 | * Get variables that are declared by the node. 91 | * 92 | * "are declared by the node" means the node is same as `Variable.defs[].node` or `Variable.defs[].parent`. 93 | * If the node declares nothing, this method returns an empty array. 94 | * CAUTION: This API is experimental. See https://github.com/estools/escope/pull/69 for more details. 95 | * 96 | * @param {Esprima.Node} node - a node to get. 97 | * @returns {Variable[]} variables that declared by the node. 98 | */ 99 | getDeclaredVariables(node) { 100 | return this.__declaredVariables.get(node) || []; 101 | } 102 | 103 | /** 104 | * acquire scope from node. 105 | * @method ScopeManager#acquire 106 | * @param {Esprima.Node} node - node for the acquired scope. 107 | * @param {boolean=} inner - look up the most inner scope, default value is false. 108 | * @return {Scope?} 109 | */ 110 | acquire(node, inner) { 111 | var scopes, scope, i, iz; 112 | 113 | function predicate(scope) { 114 | if (scope.type === 'function' && scope.functionExpressionScope) { 115 | return false; 116 | } 117 | if (scope.type === 'TDZ') { 118 | return false; 119 | } 120 | return true; 121 | } 122 | 123 | scopes = this.__get(node); 124 | if (!scopes || scopes.length === 0) { 125 | return null; 126 | } 127 | 128 | // Heuristic selection from all scopes. 129 | // If you would like to get all scopes, please use ScopeManager#acquireAll. 130 | if (scopes.length === 1) { 131 | return scopes[0]; 132 | } 133 | 134 | if (inner) { 135 | for (i = scopes.length - 1; i >= 0; --i) { 136 | scope = scopes[i]; 137 | if (predicate(scope)) { 138 | return scope; 139 | } 140 | } 141 | } else { 142 | for (i = 0, iz = scopes.length; i < iz; ++i) { 143 | scope = scopes[i]; 144 | if (predicate(scope)) { 145 | return scope; 146 | } 147 | } 148 | } 149 | 150 | return null; 151 | } 152 | 153 | /** 154 | * acquire all scopes from node. 155 | * @method ScopeManager#acquireAll 156 | * @param {Esprima.Node} node - node for the acquired scope. 157 | * @return {Scope[]?} 158 | */ 159 | acquireAll(node) { 160 | return this.__get(node); 161 | } 162 | 163 | /** 164 | * release the node. 165 | * @method ScopeManager#release 166 | * @param {Esprima.Node} node - releasing node. 167 | * @param {boolean=} inner - look up the most inner scope, default value is false. 168 | * @return {Scope?} upper scope for the node. 169 | */ 170 | release(node, inner) { 171 | var scopes, scope; 172 | scopes = this.__get(node); 173 | if (scopes && scopes.length) { 174 | scope = scopes[0].upper; 175 | if (!scope) { 176 | return null; 177 | } 178 | return this.acquire(scope.block, inner); 179 | } 180 | return null; 181 | } 182 | 183 | attach() { } 184 | 185 | detach() { } 186 | 187 | __nestScope(scope) { 188 | if (scope instanceof GlobalScope) { 189 | assert(this.__currentScope === null); 190 | this.globalScope = scope; 191 | } 192 | this.__currentScope = scope; 193 | return scope; 194 | } 195 | 196 | __nestGlobalScope(node) { 197 | return this.__nestScope(new GlobalScope(this, node)); 198 | } 199 | 200 | __nestBlockScope(node, isMethodDefinition) { 201 | return this.__nestScope(new BlockScope(this, this.__currentScope, node)); 202 | } 203 | 204 | __nestFunctionScope(node, isMethodDefinition) { 205 | return this.__nestScope(new FunctionScope(this, this.__currentScope, node, isMethodDefinition)); 206 | } 207 | 208 | __nestForScope(node) { 209 | return this.__nestScope(new ForScope(this, this.__currentScope, node)); 210 | } 211 | 212 | __nestCatchScope(node) { 213 | return this.__nestScope(new CatchScope(this, this.__currentScope, node)); 214 | } 215 | 216 | __nestWithScope(node) { 217 | return this.__nestScope(new WithScope(this, this.__currentScope, node)); 218 | } 219 | 220 | __nestClassScope(node) { 221 | return this.__nestScope(new ClassScope(this, this.__currentScope, node)); 222 | } 223 | 224 | __nestSwitchScope(node) { 225 | return this.__nestScope(new SwitchScope(this, this.__currentScope, node)); 226 | } 227 | 228 | __nestModuleScope(node) { 229 | return this.__nestScope(new ModuleScope(this, this.__currentScope, node)); 230 | } 231 | 232 | __nestTDZScope(node) { 233 | return this.__nestScope(new TDZScope(this, this.__currentScope, node)); 234 | } 235 | 236 | __nestFunctionExpressionNameScope(node) { 237 | return this.__nestScope(new FunctionExpressionNameScope(this, this.__currentScope, node)); 238 | } 239 | 240 | __isES6() { 241 | return this.__options.ecmaVersion >= 6; 242 | } 243 | } 244 | 245 | /* vim: set sw=4 ts=4 et tw=80 : */ 246 | -------------------------------------------------------------------------------- /test/es6-class.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 class', function() { 29 | it('declaration name creates class scope', function() { 30 | const ast = parse(` 31 | class Derived extends Base { 32 | constructor() { 33 | } 34 | } 35 | new Derived(); 36 | `); 37 | 38 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 39 | expect(scopeManager.scopes).to.have.length(3); 40 | 41 | let scope = scopeManager.scopes[0]; 42 | expect(scope.type).to.be.equal('global'); 43 | expect(scope.block.type).to.be.equal('Program'); 44 | expect(scope.isStrict).to.be.false; 45 | expect(scope.variables).to.have.length(1); 46 | expect(scope.variables[0].name).to.be.equal('Derived'); 47 | expect(scope.references).to.have.length(2); 48 | expect(scope.references[0].identifier.name).to.be.equal('Base'); 49 | expect(scope.references[1].identifier.name).to.be.equal('Derived'); 50 | 51 | scope = scopeManager.scopes[1]; 52 | expect(scope.type).to.be.equal('class'); 53 | expect(scope.block.type).to.be.equal('ClassDeclaration'); 54 | expect(scope.isStrict).to.be.true; 55 | expect(scope.variables).to.have.length(1); 56 | expect(scope.variables[0].name).to.be.equal('Derived'); 57 | expect(scope.references).to.have.length(0); 58 | 59 | scope = scopeManager.scopes[2]; 60 | expect(scope.type).to.be.equal('function'); 61 | expect(scope.block.type).to.be.equal('FunctionExpression'); 62 | expect(scope.isStrict).to.be.true; 63 | expect(scope.variables).to.have.length(1); 64 | expect(scope.variables[0].name).to.be.equal('arguments'); 65 | expect(scope.references).to.have.length(0); 66 | }); 67 | 68 | it('expression name creates class scope#1', function() { 69 | const ast = parse(` 70 | (class Derived extends Base { 71 | constructor() { 72 | } 73 | }); 74 | `); 75 | 76 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 77 | expect(scopeManager.scopes).to.have.length(3); 78 | 79 | let scope = scopeManager.scopes[0]; 80 | expect(scope.type).to.be.equal('global'); 81 | expect(scope.block.type).to.be.equal('Program'); 82 | expect(scope.isStrict).to.be.false; 83 | expect(scope.variables).to.have.length(0); 84 | expect(scope.references).to.have.length(1); 85 | expect(scope.references[0].identifier.name).to.be.equal('Base'); 86 | 87 | scope = scopeManager.scopes[1]; 88 | expect(scope.type).to.be.equal('class'); 89 | expect(scope.block.type).to.be.equal('ClassExpression'); 90 | expect(scope.isStrict).to.be.true; 91 | expect(scope.variables).to.have.length(1); 92 | expect(scope.variables[0].name).to.be.equal('Derived'); 93 | expect(scope.references).to.have.length(0); 94 | 95 | scope = scopeManager.scopes[2]; 96 | expect(scope.type).to.be.equal('function'); 97 | expect(scope.block.type).to.be.equal('FunctionExpression'); 98 | }); 99 | 100 | it('expression name creates class scope#2', function() { 101 | const ast = parse(` 102 | (class extends Base { 103 | constructor() { 104 | } 105 | }); 106 | `); 107 | 108 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 109 | expect(scopeManager.scopes).to.have.length(3); 110 | 111 | let scope = scopeManager.scopes[0]; 112 | expect(scope.type).to.be.equal('global'); 113 | expect(scope.block.type).to.be.equal('Program'); 114 | expect(scope.isStrict).to.be.false; 115 | expect(scope.variables).to.have.length(0); 116 | expect(scope.references).to.have.length(1); 117 | expect(scope.references[0].identifier.name).to.be.equal('Base'); 118 | 119 | scope = scopeManager.scopes[1]; 120 | expect(scope.type).to.be.equal('class'); 121 | expect(scope.block.type).to.be.equal('ClassExpression'); 122 | 123 | scope = scopeManager.scopes[2]; 124 | expect(scope.type).to.be.equal('function'); 125 | expect(scope.block.type).to.be.equal('FunctionExpression'); 126 | }); 127 | 128 | it('computed property key may refer variables', function() { 129 | const ast = parse(` 130 | (function () { 131 | var yuyushiki = 42; 132 | (class { 133 | [yuyushiki]() { 134 | } 135 | 136 | [yuyushiki + 40]() { 137 | } 138 | }); 139 | }()); 140 | `); 141 | 142 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 143 | expect(scopeManager.scopes).to.have.length(5); 144 | 145 | let scope = scopeManager.scopes[0]; 146 | expect(scope.type).to.be.equal('global'); 147 | expect(scope.block.type).to.be.equal('Program'); 148 | expect(scope.isStrict).to.be.false; 149 | 150 | scope = scopeManager.scopes[1]; 151 | expect(scope.type).to.be.equal('function'); 152 | expect(scope.block.type).to.be.equal('FunctionExpression'); 153 | expect(scope.isStrict).to.be.false; 154 | expect(scope.variables).to.have.length(2); 155 | expect(scope.variables[0].name).to.be.equal('arguments'); 156 | expect(scope.variables[1].name).to.be.equal('yuyushiki'); 157 | expect(scope.references).to.have.length(1); 158 | expect(scope.references[0].identifier.name).to.be.equal('yuyushiki'); 159 | 160 | scope = scopeManager.scopes[2]; 161 | expect(scope.type).to.be.equal('class'); 162 | expect(scope.block.type).to.be.equal('ClassExpression'); 163 | expect(scope.isStrict).to.be.true; 164 | expect(scope.variables).to.have.length(0); 165 | expect(scope.references).to.have.length(2); 166 | expect(scope.references[0].identifier.name).to.be.equal('yuyushiki'); 167 | expect(scope.references[1].identifier.name).to.be.equal('yuyushiki'); 168 | }); 169 | 170 | it('regression #49', function() { 171 | const ast = parse(` 172 | class Shoe { 173 | constructor() { 174 | //Shoe.x = true; 175 | } 176 | } 177 | let shoe = new Shoe(); 178 | `); 179 | 180 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 181 | expect(scopeManager.scopes).to.have.length(3); 182 | 183 | const scope = scopeManager.scopes[0]; 184 | expect(scope.type).to.be.equal('global'); 185 | expect(scope.block.type).to.be.equal('Program'); 186 | expect(scope.isStrict).to.be.false; 187 | expect(scope.variables).to.have.length(2); 188 | expect(scope.variables[0].name).to.be.equal('Shoe'); 189 | expect(scope.variables[1].name).to.be.equal('shoe'); 190 | expect(scope.references).to.have.length(2); 191 | expect(scope.references[0].identifier.name).to.be.equal('shoe'); 192 | expect(scope.references[1].identifier.name).to.be.equal('Shoe'); 193 | }); 194 | }); 195 | 196 | // vim: set sw=4 ts=4 et tw=80 : 197 | -------------------------------------------------------------------------------- /test/es6-block-scope.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 block scope', function() { 29 | it('let is materialized in ES6 block scope#1', function() { 30 | const ast = parse(` 31 | { 32 | let i = 20; 33 | i; 34 | } 35 | `); 36 | 37 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 38 | expect(scopeManager.scopes).to.have.length(2); // Program and BlcokStatement scope. 39 | 40 | let scope = scopeManager.scopes[0]; 41 | expect(scope.type).to.be.equal('global'); 42 | expect(scope.variables).to.have.length(0); // No variable in Program scope. 43 | 44 | scope = scopeManager.scopes[1]; 45 | expect(scope.type).to.be.equal('block'); 46 | expect(scope.variables).to.have.length(1); // `i` in block scope. 47 | expect(scope.variables[0].name).to.be.equal('i'); 48 | expect(scope.references).to.have.length(2); 49 | expect(scope.references[0].identifier.name).to.be.equal('i'); 50 | expect(scope.references[1].identifier.name).to.be.equal('i'); 51 | }); 52 | 53 | it('let is materialized in ES6 block scope#2', function() { 54 | const ast = parse(` 55 | { 56 | let i = 20; 57 | var i = 20; 58 | i; 59 | } 60 | `); 61 | 62 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 63 | expect(scopeManager.scopes).to.have.length(2); // Program and BlcokStatement scope. 64 | 65 | let scope = scopeManager.scopes[0]; 66 | expect(scope.type).to.be.equal('global'); 67 | expect(scope.variables).to.have.length(1); // No variable in Program scope. 68 | expect(scope.variables[0].name).to.be.equal('i'); 69 | 70 | scope = scopeManager.scopes[1]; 71 | expect(scope.type).to.be.equal('block'); 72 | expect(scope.variables).to.have.length(1); // `i` in block scope. 73 | expect(scope.variables[0].name).to.be.equal('i'); 74 | expect(scope.references).to.have.length(3); 75 | expect(scope.references[0].identifier.name).to.be.equal('i'); 76 | expect(scope.references[1].identifier.name).to.be.equal('i'); 77 | expect(scope.references[2].identifier.name).to.be.equal('i'); 78 | }); 79 | 80 | it('function delaration is materialized in ES6 block scope', function() { 81 | const ast = parse(` 82 | { 83 | function test() { 84 | } 85 | test(); 86 | } 87 | `); 88 | 89 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 90 | expect(scopeManager.scopes).to.have.length(3); 91 | 92 | let scope = scopeManager.scopes[0]; 93 | expect(scope.type).to.be.equal('global'); 94 | expect(scope.variables).to.have.length(0); 95 | 96 | scope = scopeManager.scopes[1]; 97 | expect(scope.type).to.be.equal('block'); 98 | expect(scope.variables).to.have.length(1); 99 | expect(scope.variables[0].name).to.be.equal('test'); 100 | expect(scope.references).to.have.length(1); 101 | expect(scope.references[0].identifier.name).to.be.equal('test'); 102 | 103 | scope = scopeManager.scopes[2]; 104 | expect(scope.type).to.be.equal('function'); 105 | expect(scope.variables).to.have.length(1); 106 | expect(scope.variables[0].name).to.be.equal('arguments'); 107 | expect(scope.references).to.have.length(0); 108 | }); 109 | 110 | it('let is not hoistable#1', function() { 111 | const ast = parse(` 112 | var i = 42; (1) 113 | { 114 | i; // (2) ReferenceError at runtime. 115 | let i = 20; // (2) 116 | i; // (2) 117 | } 118 | `); 119 | 120 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 121 | expect(scopeManager.scopes).to.have.length(2); 122 | 123 | const globalScope = scopeManager.scopes[0]; 124 | expect(globalScope.type).to.be.equal('global'); 125 | expect(globalScope.variables).to.have.length(1); 126 | expect(globalScope.variables[0].name).to.be.equal('i'); 127 | expect(globalScope.references).to.have.length(1); 128 | 129 | const scope = scopeManager.scopes[1]; 130 | expect(scope.type).to.be.equal('block'); 131 | expect(scope.variables).to.have.length(1); 132 | expect(scope.variables[0].name).to.be.equal('i'); 133 | expect(scope.references).to.have.length(3); 134 | expect(scope.references[0].resolved).to.be.equal(scope.variables[0]); 135 | expect(scope.references[1].resolved).to.be.equal(scope.variables[0]); 136 | expect(scope.references[2].resolved).to.be.equal(scope.variables[0]); 137 | }); 138 | 139 | it('let is not hoistable#2', function() { 140 | const ast = parse(` 141 | (function () { 142 | var i = 42; // (1) 143 | i; // (1) 144 | { 145 | i; // (3) 146 | { 147 | i; // (2) 148 | let i = 20; // (2) 149 | i; // (2) 150 | } 151 | let i = 30; // (3) 152 | i; // (3) 153 | } 154 | i; // (1) 155 | }()); 156 | `); 157 | 158 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 159 | expect(scopeManager.scopes).to.have.length(4); 160 | 161 | const globalScope = scopeManager.scopes[0]; 162 | expect(globalScope.type).to.be.equal('global'); 163 | expect(globalScope.variables).to.have.length(0); 164 | expect(globalScope.references).to.have.length(0); 165 | 166 | let scope = scopeManager.scopes[1]; 167 | expect(scope.type).to.be.equal('function'); 168 | expect(scope.variables).to.have.length(2); 169 | expect(scope.variables[0].name).to.be.equal('arguments'); 170 | expect(scope.variables[1].name).to.be.equal('i'); 171 | const v1 = scope.variables[1]; 172 | expect(scope.references).to.have.length(3); 173 | expect(scope.references[0].resolved).to.be.equal(v1); 174 | expect(scope.references[1].resolved).to.be.equal(v1); 175 | expect(scope.references[2].resolved).to.be.equal(v1); 176 | 177 | scope = scopeManager.scopes[2]; 178 | expect(scope.type).to.be.equal('block'); 179 | expect(scope.variables).to.have.length(1); 180 | expect(scope.variables[0].name).to.be.equal('i'); 181 | const v3 = scope.variables[0]; 182 | expect(scope.references).to.have.length(3); 183 | expect(scope.references[0].resolved).to.be.equal(v3); 184 | expect(scope.references[1].resolved).to.be.equal(v3); 185 | expect(scope.references[2].resolved).to.be.equal(v3); 186 | 187 | scope = scopeManager.scopes[3]; 188 | expect(scope.type).to.be.equal('block'); 189 | expect(scope.variables).to.have.length(1); 190 | expect(scope.variables[0].name).to.be.equal('i'); 191 | const v2 = scope.variables[0]; 192 | expect(scope.references).to.have.length(3); 193 | expect(scope.references[0].resolved).to.be.equal(v2); 194 | expect(scope.references[1].resolved).to.be.equal(v2); 195 | expect(scope.references[2].resolved).to.be.equal(v2); 196 | }); 197 | }); 198 | 199 | // vim: set sw=4 ts=4 et tw=80 : 200 | -------------------------------------------------------------------------------- /test/get-declared-variables.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Toru Nagashima 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { visit } from 'esrecurse'; 26 | import espree from '../third_party/espree'; 27 | import { analyze } from '..'; 28 | 29 | describe('ScopeManager.prototype.getDeclaredVariables', function() { 30 | const verify = (ast, type, expectedNamesList) => { 31 | const scopeManager = analyze(ast, { 32 | ecmaVersion: 6, 33 | sourceType: 'module' 34 | }); 35 | 36 | visit(ast, { 37 | [type](node) { 38 | const expected = expectedNamesList.shift(); 39 | const actual = scopeManager.getDeclaredVariables(node); 40 | 41 | expect(actual).to.have.length(expected.length); 42 | if (actual.length > 0) { 43 | const end = actual.length-1; 44 | for (let i = 0; i <= end; i++) { 45 | expect(actual[i].name).to.be.equal(expected[i]); 46 | } 47 | } 48 | 49 | this.visitChildren(node); 50 | } 51 | }); 52 | 53 | expect(expectedNamesList).to.have.length(0); 54 | }; 55 | 56 | 57 | it('should get variables that declared on `VariableDeclaration`', function() { 58 | const ast = espree(` 59 | var {a, x: [b], y: {c = 0}} = foo; 60 | let {d, x: [e], y: {f = 0}} = foo; 61 | const {g, x: [h], y: {i = 0}} = foo, {j, k = function() { let l; }} = bar; 62 | `); 63 | 64 | verify(ast, 'VariableDeclaration', [ 65 | ['a', 'b', 'c'], 66 | ['d', 'e', 'f'], 67 | ['g', 'h', 'i', 'j', 'k'], 68 | ['l'] 69 | ]); 70 | }); 71 | 72 | 73 | it('should get variables that declared on `VariableDeclaration` in for-in/of', function() { 74 | const ast = espree(` 75 | for (var {a, x: [b], y: {c = 0}} in foo) { 76 | let g; 77 | } 78 | for (let {d, x: [e], y: {f = 0}} of foo) { 79 | let h; 80 | } 81 | `); 82 | 83 | verify(ast, 'VariableDeclaration', [ 84 | ['a', 'b', 'c'], 85 | ['g'], 86 | ['d', 'e', 'f'], 87 | ['h'] 88 | ]); 89 | }); 90 | 91 | 92 | it('should get variables that declared on `VariableDeclarator`', function() { 93 | const ast = espree(` 94 | var {a, x: [b], y: {c = 0}} = foo; 95 | let {d, x: [e], y: {f = 0}} = foo; 96 | const {g, x: [h], y: {i = 0}} = foo, {j, k = function() { let l; }} = bar; 97 | `); 98 | 99 | verify(ast, 'VariableDeclarator', [ 100 | ['a', 'b', 'c'], 101 | ['d', 'e', 'f'], 102 | ['g', 'h', 'i'], 103 | ['j', 'k'], 104 | ['l'] 105 | ]); 106 | }); 107 | 108 | 109 | it('should get variables that declared on `FunctionDeclaration`', function() { 110 | const ast = espree(` 111 | function foo({a, x: [b], y: {c = 0}}, [d, e]) { 112 | let z; 113 | } 114 | function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) { 115 | let z; 116 | } 117 | `); 118 | 119 | verify(ast, 'FunctionDeclaration', [ 120 | ['foo', 'a', 'b', 'c', 'd', 'e'], 121 | ['bar', 'f', 'g', 'h', 'i', 'j'] 122 | ]); 123 | }); 124 | 125 | 126 | it('should get variables that declared on `FunctionExpression`', function() { 127 | const ast = espree(` 128 | (function foo({a, x: [b], y: {c = 0}}, [d, e]) { 129 | let z; 130 | }); 131 | (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) { 132 | let z; 133 | }); 134 | `); 135 | 136 | verify(ast, 'FunctionExpression', [ 137 | ['foo', 'a', 'b', 'c', 'd', 'e'], 138 | ['bar', 'f', 'g', 'h', 'i', 'j'], 139 | ['q'] 140 | ]); 141 | }); 142 | 143 | 144 | it('should get variables that declared on `ArrowFunctionExpression`', function() { 145 | const ast = espree(` 146 | (({a, x: [b], y: {c = 0}}, [d, e]) => { 147 | let z; 148 | }); 149 | (({f, x: [g], y: {h = 0}}, [i, j]) => { 150 | let z; 151 | }); 152 | `); 153 | 154 | verify(ast, 'ArrowFunctionExpression', [ 155 | ['a', 'b', 'c', 'd', 'e'], 156 | ['f', 'g', 'h', 'i', 'j'] 157 | ]); 158 | }); 159 | 160 | 161 | it('should get variables that declared on `ClassDeclaration`', function() { 162 | const ast = espree(` 163 | class A { foo(x) { let y; } } 164 | class B { foo(x) { let y; } } 165 | `); 166 | 167 | verify(ast, 'ClassDeclaration', [ 168 | ['A', 'A'], // outer scope's and inner scope's. 169 | ['B', 'B'] 170 | ]); 171 | }); 172 | 173 | 174 | it('should get variables that declared on `ClassExpression`', function() { 175 | const ast = espree(` 176 | (class A { foo(x) { let y; } }); 177 | (class B { foo(x) { let y; } }); 178 | `); 179 | 180 | verify(ast, 'ClassExpression', [ 181 | ['A'], 182 | ['B'] 183 | ]); 184 | }); 185 | 186 | 187 | it('should get variables that declared on `CatchClause`', function() { 188 | const ast = espree(` 189 | try {} catch ({a, b}) { 190 | let x; 191 | try {} catch ({c, d}) { 192 | let y; 193 | } 194 | } 195 | `); 196 | 197 | verify(ast, 'CatchClause', [ 198 | ['a', 'b'], 199 | ['c', 'd'] 200 | ]); 201 | }); 202 | 203 | 204 | it('should get variables that declared on `ImportDeclaration`', function() { 205 | const ast = espree(` 206 | import "aaa"; 207 | import * as a from "bbb"; 208 | import b, {c, x as d} from "ccc";`, 209 | {sourceType: 'module'} 210 | ); 211 | 212 | verify(ast, 'ImportDeclaration', [ 213 | [], 214 | ['a'], 215 | ['b', 'c', 'd'] 216 | ]); 217 | }); 218 | 219 | 220 | it('should get variables that declared on `ImportSpecifier`', function() { 221 | const ast = espree(` 222 | import "aaa"; 223 | import * as a from "bbb"; 224 | import b, {c, x as d} from "ccc";`, 225 | {sourceType: 'module'} 226 | ); 227 | 228 | verify(ast, 'ImportSpecifier', [ 229 | ['c'], 230 | ['d'] 231 | ]); 232 | }); 233 | 234 | 235 | it('should get variables that declared on `ImportDefaultSpecifier`', function() { 236 | const ast = espree(` 237 | import "aaa"; 238 | import * as a from "bbb"; 239 | import b, {c, x as d} from "ccc";`, 240 | {sourceType: 'module'} 241 | ); 242 | 243 | verify(ast, 'ImportDefaultSpecifier', [ 244 | ['b'] 245 | ]); 246 | }); 247 | 248 | 249 | it('should get variables that declared on `ImportNamespaceSpecifier`', function() { 250 | const ast = espree(` 251 | import "aaa"; 252 | import * as a from "bbb"; 253 | import b, {c, x as d} from "ccc";`, 254 | {sourceType: 'module'} 255 | ); 256 | 257 | verify(ast, 'ImportNamespaceSpecifier', [ 258 | ['a'] 259 | ]); 260 | }); 261 | 262 | 263 | it('should not get duplicate even if it\'s declared twice', function() { 264 | const ast = espree(`var a = 0, a = 1;`); 265 | 266 | verify(ast, 'VariableDeclaration', [ 267 | ['a'] 268 | ]); 269 | }); 270 | }); 271 | 272 | // vim: set sw=4 ts=4 et tw=80 : 273 | -------------------------------------------------------------------------------- /test/es6-export.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import espree from '../third_party/espree'; 26 | import { analyze } from '..'; 27 | 28 | describe('export declaration', function() { 29 | // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-static-and-runtme-semantics-module-records 30 | it('should create vairable bindings', function() { 31 | const ast = espree(`export var v;`, {sourceType: 'module'}); 32 | 33 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 34 | expect(scopeManager.scopes).to.have.length(2); 35 | const globalScope = scopeManager.scopes[0]; 36 | expect(globalScope.type).to.be.equal('global'); 37 | expect(globalScope.variables).to.have.length(0); 38 | expect(globalScope.references).to.have.length(0); 39 | 40 | const scope = scopeManager.scopes[1]; 41 | expect(scope.type).to.be.equal('module'); 42 | expect(scope.variables).to.have.length(1); 43 | expect(scope.variables[0].name).to.be.equal('v'); 44 | expect(scope.variables[0].defs[0].type).to.be.equal('Variable'); 45 | expect(scope.references).to.have.length(0); 46 | }); 47 | 48 | it('should create function declaration bindings', function() { 49 | const ast = espree(`export default function f(){};`, {sourceType: 'module'}); 50 | 51 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 52 | expect(scopeManager.scopes).to.have.length(3); 53 | const globalScope = scopeManager.scopes[0]; 54 | expect(globalScope.type).to.be.equal('global'); 55 | expect(globalScope.variables).to.have.length(0); 56 | expect(globalScope.references).to.have.length(0); 57 | 58 | let scope = scopeManager.scopes[1]; 59 | expect(scope.type).to.be.equal('module'); 60 | expect(scope.variables).to.have.length(1); 61 | expect(scope.variables[0].name).to.be.equal('f'); 62 | expect(scope.variables[0].defs[0].type).to.be.equal('FunctionName'); 63 | expect(scope.references).to.have.length(0); 64 | 65 | scope = scopeManager.scopes[2]; 66 | expect(scope.type).to.be.equal('function'); 67 | expect(scope.variables).to.have.length(1); 68 | expect(scope.variables[0].name).to.be.equal('arguments'); 69 | expect(scope.references).to.have.length(0); 70 | }); 71 | 72 | 73 | it('should export function expression', function() { 74 | const ast = espree(`export default function(){};`, {sourceType: 'module'}); 75 | 76 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 77 | expect(scopeManager.scopes).to.have.length(3); 78 | const globalScope = scopeManager.scopes[0]; 79 | expect(globalScope.type).to.be.equal('global'); 80 | expect(globalScope.variables).to.have.length(0); 81 | expect(globalScope.references).to.have.length(0); 82 | 83 | let scope = scopeManager.scopes[1]; 84 | expect(scope.type).to.be.equal('module'); 85 | expect(scope.variables).to.have.length(0); 86 | expect(scope.references).to.have.length(0); 87 | 88 | scope = scopeManager.scopes[2]; 89 | expect(scope.type).to.be.equal('function'); 90 | expect(scope.variables).to.have.length(1); 91 | expect(scope.variables[0].name).to.be.equal('arguments'); 92 | expect(scope.references).to.have.length(0); 93 | }); 94 | 95 | it('should export literal', function() { 96 | const ast = espree(`export default 42;`, {sourceType: 'module'}); 97 | 98 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 99 | expect(scopeManager.scopes).to.have.length(2); 100 | const globalScope = scopeManager.scopes[0]; 101 | expect(globalScope.type).to.be.equal('global'); 102 | expect(globalScope.variables).to.have.length(0); 103 | expect(globalScope.references).to.have.length(0); 104 | 105 | const scope = scopeManager.scopes[1]; 106 | expect(scope.type).to.be.equal('module'); 107 | expect(scope.variables).to.have.length(0); 108 | expect(scope.references).to.have.length(0); 109 | }); 110 | 111 | it('should refer exported references#1', function() { 112 | const ast = espree(`export {x};`, {sourceType: 'module'}); 113 | 114 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 115 | expect(scopeManager.scopes).to.have.length(2); 116 | const globalScope = scopeManager.scopes[0]; 117 | expect(globalScope.type).to.be.equal('global'); 118 | expect(globalScope.variables).to.have.length(0); 119 | expect(globalScope.references).to.have.length(0); 120 | 121 | const scope = scopeManager.scopes[1]; 122 | expect(scope.type).to.be.equal('module'); 123 | expect(scope.variables).to.have.length(0); 124 | expect(scope.references).to.have.length(1); 125 | expect(scope.references[0].identifier.name).to.be.equal('x'); 126 | }); 127 | 128 | it('should refer exported references#2', function() { 129 | const ast = espree(`export {v as x};`, {sourceType: 'module'}); 130 | 131 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 132 | expect(scopeManager.scopes).to.have.length(2); 133 | const globalScope = scopeManager.scopes[0]; 134 | expect(globalScope.type).to.be.equal('global'); 135 | expect(globalScope.variables).to.have.length(0); 136 | expect(globalScope.references).to.have.length(0); 137 | 138 | const scope = scopeManager.scopes[1]; 139 | expect(scope.type).to.be.equal('module'); 140 | expect(scope.variables).to.have.length(0); 141 | expect(scope.references).to.have.length(1); 142 | expect(scope.references[0].identifier.name).to.be.equal('v'); 143 | }); 144 | 145 | it('should not refer exported references from other source#1', function() { 146 | const ast = espree(`export {x} from "mod";`, {sourceType: 'module'}); 147 | 148 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 149 | expect(scopeManager.scopes).to.have.length(2); 150 | const globalScope = scopeManager.scopes[0]; 151 | expect(globalScope.type).to.be.equal('global'); 152 | expect(globalScope.variables).to.have.length(0); 153 | expect(globalScope.references).to.have.length(0); 154 | 155 | const scope = scopeManager.scopes[1]; 156 | expect(scope.type).to.be.equal('module'); 157 | expect(scope.variables).to.have.length(0); 158 | expect(scope.references).to.have.length(0); 159 | }); 160 | 161 | it('should not refer exported references from other source#2', function() { 162 | const ast = espree(`export {v as x} from "mod";`, {sourceType: 'module'}); 163 | 164 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 165 | expect(scopeManager.scopes).to.have.length(2); 166 | const globalScope = scopeManager.scopes[0]; 167 | expect(globalScope.type).to.be.equal('global'); 168 | expect(globalScope.variables).to.have.length(0); 169 | expect(globalScope.references).to.have.length(0); 170 | 171 | const scope = scopeManager.scopes[1]; 172 | expect(scope.type).to.be.equal('module'); 173 | expect(scope.variables).to.have.length(0); 174 | expect(scope.references).to.have.length(0); 175 | }); 176 | 177 | it('should not refer exported references from other source#3', function() { 178 | const ast = espree(`export * from "mod";`, {sourceType: 'module'}); 179 | 180 | const scopeManager = analyze(ast, {ecmaVersion: 6, sourceType: 'module'}); 181 | expect(scopeManager.scopes).to.have.length(2); 182 | const globalScope = scopeManager.scopes[0]; 183 | expect(globalScope.type).to.be.equal('global'); 184 | expect(globalScope.variables).to.have.length(0); 185 | expect(globalScope.references).to.have.length(0); 186 | 187 | const scope = scopeManager.scopes[1]; 188 | expect(scope.type).to.be.equal('module'); 189 | expect(scope.variables).to.have.length(0); 190 | expect(scope.references).to.have.length(0); 191 | }); 192 | }); 193 | 194 | // vim: set sw=4 ts=4 et tw=80 : 195 | -------------------------------------------------------------------------------- /test/es6-default-parameters.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2015 Toru Nagashima 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import espree from '../third_party/espree'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 default parameters:', function() { 29 | describe('a default parameter creates a writable reference for its initialization:', function() { 30 | const patterns = { 31 | FunctionDeclaration: `function foo(a, b = 0) {}`, 32 | FunctionExpression: `let foo = function(a, b = 0) {};`, 33 | ArrowExpression: `let foo = (a, b = 0) => {};` 34 | }; 35 | 36 | for (const name in patterns) { 37 | const code = patterns[name]; 38 | (function(name, code) { 39 | it(name, function() { 40 | const numVars = name === 'ArrowExpression' ? 2 : 3; 41 | const ast = espree(code); 42 | 43 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 44 | expect(scopeManager.scopes).to.have.length(2); // [global, foo] 45 | 46 | const scope = scopeManager.scopes[1]; 47 | expect(scope.variables).to.have.length(numVars); // [arguments?, a, b] 48 | expect(scope.references).to.have.length(1); 49 | 50 | const reference = scope.references[0]; 51 | expect(reference.from).to.equal(scope); 52 | expect(reference.identifier.name).to.equal('b'); 53 | expect(reference.resolved).to.equal(scope.variables[numVars - 1]); 54 | expect(reference.writeExpr).to.not.be.undefined; 55 | expect(reference.isWrite()).to.be.true; 56 | expect(reference.isRead()).to.be.false; 57 | }); 58 | })(name, code); 59 | } 60 | }); 61 | 62 | describe('a default parameter creates a readable reference for references in right:', function() { 63 | const patterns = { 64 | FunctionDeclaration: ` 65 | let a; 66 | function foo(b = a) {} 67 | `, 68 | FunctionExpression: ` 69 | let a; 70 | let foo = function(b = a) {} 71 | `, 72 | ArrowExpression: ` 73 | let a; 74 | let foo = (b = a) => {}; 75 | ` 76 | }; 77 | 78 | for (const name in patterns) { 79 | const code = patterns[name]; 80 | (function(name, code) { 81 | it(name, function() { 82 | const numVars = name === 'ArrowExpression' ? 1 : 2; 83 | const ast = espree(code); 84 | 85 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 86 | expect(scopeManager.scopes).to.have.length(2); // [global, foo] 87 | 88 | const scope = scopeManager.scopes[1]; 89 | expect(scope.variables).to.have.length(numVars); // [arguments?, b] 90 | expect(scope.references).to.have.length(2); // [b, a] 91 | 92 | const reference = scope.references[1]; 93 | expect(reference.from).to.equal(scope); 94 | expect(reference.identifier.name).to.equal('a'); 95 | expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]); 96 | expect(reference.writeExpr).to.be.undefined; 97 | expect(reference.isWrite()).to.be.false; 98 | expect(reference.isRead()).to.be.true; 99 | }); 100 | })(name, code); 101 | } 102 | }); 103 | 104 | describe('a default parameter creates a readable reference for references in right (for const):', function() { 105 | const patterns = { 106 | FunctionDeclaration: ` 107 | const a = 0; 108 | function foo(b = a) {} 109 | `, 110 | FunctionExpression: ` 111 | const a = 0; 112 | let foo = function(b = a) {} 113 | `, 114 | ArrowExpression: ` 115 | const a = 0; 116 | let foo = (b = a) => {}; 117 | ` 118 | }; 119 | 120 | for (const name in patterns) { 121 | const code = patterns[name]; 122 | (function(name, code) { 123 | it(name, function() { 124 | const numVars = name === 'ArrowExpression' ? 1 : 2; 125 | const ast = espree(code); 126 | 127 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 128 | expect(scopeManager.scopes).to.have.length(2); // [global, foo] 129 | 130 | const scope = scopeManager.scopes[1]; 131 | expect(scope.variables).to.have.length(numVars); // [arguments?, b] 132 | expect(scope.references).to.have.length(2); // [b, a] 133 | 134 | const reference = scope.references[1]; 135 | expect(reference.from).to.equal(scope); 136 | expect(reference.identifier.name).to.equal('a'); 137 | expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]); 138 | expect(reference.writeExpr).to.be.undefined; 139 | expect(reference.isWrite()).to.be.false; 140 | expect(reference.isRead()).to.be.true; 141 | }); 142 | })(name, code); 143 | } 144 | }); 145 | 146 | describe('a default parameter creates a readable reference for references in right (partial):', function() { 147 | const patterns = { 148 | FunctionDeclaration: ` 149 | let a; 150 | function foo(b = a.c) {} 151 | `, 152 | FunctionExpression: ` 153 | let a; 154 | let foo = function(b = a.c) {} 155 | `, 156 | ArrowExpression: ` 157 | let a; 158 | let foo = (b = a.c) => {}; 159 | ` 160 | }; 161 | 162 | for (const name in patterns) { 163 | const code = patterns[name]; 164 | (function(name, code) { 165 | it(name, function() { 166 | const numVars = name === 'ArrowExpression' ? 1 : 2; 167 | const ast = espree(code); 168 | 169 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 170 | expect(scopeManager.scopes).to.have.length(2); // [global, foo] 171 | 172 | const scope = scopeManager.scopes[1]; 173 | expect(scope.variables).to.have.length(numVars); // [arguments?, b] 174 | expect(scope.references).to.have.length(2); // [b, a] 175 | 176 | const reference = scope.references[1]; 177 | expect(reference.from).to.equal(scope); 178 | expect(reference.identifier.name).to.equal('a'); 179 | expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]); 180 | expect(reference.writeExpr).to.be.undefined; 181 | expect(reference.isWrite()).to.be.false; 182 | expect(reference.isRead()).to.be.true; 183 | }); 184 | })(name, code); 185 | } 186 | }); 187 | 188 | describe('a default parameter creates a readable reference for references in right\'s nested scope:', function() { 189 | const patterns = { 190 | FunctionDeclaration: ` 191 | let a; 192 | function foo(b = function() { return a; }) {} 193 | `, 194 | FunctionExpression: ` 195 | let a; 196 | let foo = function(b = function() { return a; }) {} 197 | `, 198 | ArrowExpression: ` 199 | let a; 200 | let foo = (b = function() { return a; }) => {}; 201 | ` 202 | }; 203 | 204 | for (const name in patterns) { 205 | const code = patterns[name]; 206 | (function(name, code) { 207 | it(name, function() { 208 | const ast = espree(code); 209 | 210 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 211 | expect(scopeManager.scopes).to.have.length(3); // [global, foo, anonymous] 212 | 213 | const scope = scopeManager.scopes[2]; 214 | expect(scope.variables).to.have.length(1); // [arguments] 215 | expect(scope.references).to.have.length(1); // [a] 216 | 217 | const reference = scope.references[0]; 218 | expect(reference.from).to.equal(scope); 219 | expect(reference.identifier.name).to.equal('a'); 220 | expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]); 221 | expect(reference.writeExpr).to.be.undefined; 222 | expect(reference.isWrite()).to.be.false; 223 | expect(reference.isRead()).to.be.true; 224 | }); 225 | })(name, code); 226 | } 227 | }); 228 | }); 229 | 230 | // vim: set sw=4 ts=4 et tw=80 : 231 | -------------------------------------------------------------------------------- /test/es6-iteration-scope.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright (C) 2014 Yusuke Suzuki 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | import { expect } from 'chai'; 25 | import { parse } from '../third_party/esprima'; 26 | import { analyze } from '..'; 27 | 28 | describe('ES6 iteration scope', function() { 29 | it('let materialize iteration scope for ForInStatement#1', function() { 30 | const ast = parse(` 31 | (function () { 32 | let i = 20; 33 | for (let i in i) { 34 | console.log(i); 35 | } 36 | }()); 37 | `); 38 | 39 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 40 | expect(scopeManager.scopes).to.have.length(5); 41 | 42 | let scope = scopeManager.scopes[0]; 43 | expect(scope.type).to.be.equal('global'); 44 | expect(scope.variables).to.have.length(0); 45 | 46 | scope = scopeManager.scopes[1]; 47 | expect(scope.type).to.be.equal('function'); 48 | expect(scope.variables).to.have.length(2); 49 | expect(scope.variables[0].name).to.be.equal('arguments'); 50 | expect(scope.variables[1].name).to.be.equal('i'); 51 | expect(scope.references).to.have.length(1); 52 | expect(scope.references[0].identifier.name).to.be.equal('i'); 53 | expect(scope.references[0].resolved).to.be.equal(scope.variables[1]); 54 | 55 | let iterScope = scope = scopeManager.scopes[2]; 56 | expect(scope.type).to.be.equal('TDZ'); 57 | expect(scope.variables).to.have.length(1); 58 | expect(scope.variables[0].name).to.be.equal('i'); 59 | expect(scope.variables[0].defs[0].type).to.be.equal('TDZ'); 60 | expect(scope.references).to.have.length(1); 61 | expect(scope.references[0].identifier.name).to.be.equal('i'); 62 | expect(scope.references[0].resolved).to.be.equal(scope.variables[0]); 63 | 64 | iterScope = scope = scopeManager.scopes[3]; 65 | expect(scope.type).to.be.equal('for'); 66 | expect(scope.variables).to.have.length(1); 67 | expect(scope.variables[0].name).to.be.equal('i'); 68 | expect(scope.references).to.have.length(1); 69 | expect(scope.references[0].identifier.name).to.be.equal('i'); 70 | expect(scope.references[0].resolved).to.be.equal(scope.variables[0]); 71 | 72 | scope = scopeManager.scopes[4]; 73 | expect(scope.type).to.be.equal('block'); 74 | expect(scope.variables).to.have.length(0); 75 | expect(scope.references).to.have.length(2); 76 | expect(scope.references[0].identifier.name).to.be.equal('console'); 77 | expect(scope.references[0].resolved).to.be.equal(null); 78 | expect(scope.references[1].identifier.name).to.be.equal('i'); 79 | expect(scope.references[1].resolved).to.be.equal(iterScope.variables[0]); 80 | }); 81 | 82 | it('let materialize iteration scope for ForInStatement#2', function() { 83 | const ast = parse(` 84 | (function () { 85 | let i = 20; 86 | for (let { i, j, k } in i) { 87 | console.log(i); 88 | } 89 | }()); 90 | `); 91 | 92 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 93 | expect(scopeManager.scopes).to.have.length(5); 94 | 95 | let scope = scopeManager.scopes[0]; 96 | expect(scope.type).to.be.equal('global'); 97 | expect(scope.variables).to.have.length(0); 98 | 99 | scope = scopeManager.scopes[1]; 100 | expect(scope.type).to.be.equal('function'); 101 | expect(scope.variables).to.have.length(2); 102 | expect(scope.variables[0].name).to.be.equal('arguments'); 103 | expect(scope.variables[1].name).to.be.equal('i'); 104 | expect(scope.references).to.have.length(1); 105 | expect(scope.references[0].identifier.name).to.be.equal('i'); 106 | expect(scope.references[0].resolved).to.be.equal(scope.variables[1]); 107 | 108 | let iterScope = scope = scopeManager.scopes[2]; 109 | expect(scope.type).to.be.equal('TDZ'); 110 | expect(scope.variables).to.have.length(3); 111 | expect(scope.variables[0].name).to.be.equal('i'); 112 | expect(scope.variables[0].defs[0].type).to.be.equal('TDZ'); 113 | expect(scope.variables[1].name).to.be.equal('j'); 114 | expect(scope.variables[1].defs[0].type).to.be.equal('TDZ'); 115 | expect(scope.variables[2].name).to.be.equal('k'); 116 | expect(scope.variables[2].defs[0].type).to.be.equal('TDZ'); 117 | expect(scope.references).to.have.length(1); 118 | expect(scope.references[0].identifier.name).to.be.equal('i'); 119 | expect(scope.references[0].resolved).to.be.equal(scope.variables[0]); 120 | 121 | iterScope = scope = scopeManager.scopes[3]; 122 | expect(scope.type).to.be.equal('for'); 123 | expect(scope.variables).to.have.length(3); 124 | expect(scope.variables[0].name).to.be.equal('i'); 125 | expect(scope.variables[1].name).to.be.equal('j'); 126 | expect(scope.variables[2].name).to.be.equal('k'); 127 | expect(scope.references).to.have.length(3); 128 | expect(scope.references[0].identifier.name).to.be.equal('i'); 129 | expect(scope.references[0].resolved).to.be.equal(scope.variables[0]); 130 | expect(scope.references[1].identifier.name).to.be.equal('j'); 131 | expect(scope.references[1].resolved).to.be.equal(scope.variables[1]); 132 | expect(scope.references[2].identifier.name).to.be.equal('k'); 133 | expect(scope.references[2].resolved).to.be.equal(scope.variables[2]); 134 | 135 | scope = scopeManager.scopes[4]; 136 | expect(scope.type).to.be.equal('block'); 137 | expect(scope.variables).to.have.length(0); 138 | expect(scope.references).to.have.length(2); 139 | expect(scope.references[0].identifier.name).to.be.equal('console'); 140 | expect(scope.references[0].resolved).to.be.equal(null); 141 | expect(scope.references[1].identifier.name).to.be.equal('i'); 142 | expect(scope.references[1].resolved).to.be.equal(iterScope.variables[0]); 143 | }); 144 | 145 | it('let materialize iteration scope for ForStatement#2', function() { 146 | const ast = parse(` 147 | (function () { 148 | let i = 20; 149 | let obj = {}; 150 | for (let { i, j, k } = obj; i < okok; ++i) { 151 | console.log(i, j, k); 152 | } 153 | }()); 154 | `); 155 | 156 | const scopeManager = analyze(ast, {ecmaVersion: 6}); 157 | expect(scopeManager.scopes).to.have.length(4); 158 | 159 | let scope = scopeManager.scopes[0]; 160 | expect(scope.type).to.be.equal('global'); 161 | expect(scope.variables).to.have.length(0); 162 | 163 | const functionScope = scope = scopeManager.scopes[1]; 164 | expect(scope.type).to.be.equal('function'); 165 | expect(scope.variables).to.have.length(3); 166 | expect(scope.variables[0].name).to.be.equal('arguments'); 167 | expect(scope.variables[1].name).to.be.equal('i'); 168 | expect(scope.variables[2].name).to.be.equal('obj'); 169 | expect(scope.references).to.have.length(2); 170 | expect(scope.references[0].identifier.name).to.be.equal('i'); 171 | expect(scope.references[0].resolved).to.be.equal(scope.variables[1]); 172 | expect(scope.references[1].identifier.name).to.be.equal('obj'); 173 | expect(scope.references[1].resolved).to.be.equal(scope.variables[2]); 174 | 175 | const iterScope = scope = scopeManager.scopes[2]; 176 | expect(scope.type).to.be.equal('for'); 177 | expect(scope.variables).to.have.length(3); 178 | expect(scope.variables[0].name).to.be.equal('i'); 179 | expect(scope.variables[0].defs[0].type).to.be.equal('Variable'); 180 | expect(scope.variables[1].name).to.be.equal('j'); 181 | expect(scope.variables[1].defs[0].type).to.be.equal('Variable'); 182 | expect(scope.variables[2].name).to.be.equal('k'); 183 | expect(scope.variables[2].defs[0].type).to.be.equal('Variable'); 184 | expect(scope.references).to.have.length(7); 185 | expect(scope.references[0].identifier.name).to.be.equal('i'); 186 | expect(scope.references[0].resolved).to.be.equal(scope.variables[0]); 187 | expect(scope.references[1].identifier.name).to.be.equal('j'); 188 | expect(scope.references[1].resolved).to.be.equal(scope.variables[1]); 189 | expect(scope.references[2].identifier.name).to.be.equal('k'); 190 | expect(scope.references[2].resolved).to.be.equal(scope.variables[2]); 191 | expect(scope.references[3].identifier.name).to.be.equal('obj'); 192 | expect(scope.references[3].resolved).to.be.equal(functionScope.variables[2]); 193 | expect(scope.references[4].identifier.name).to.be.equal('i'); 194 | expect(scope.references[4].resolved).to.be.equal(scope.variables[0]); 195 | expect(scope.references[5].identifier.name).to.be.equal('okok'); 196 | expect(scope.references[5].resolved).to.be.null; 197 | expect(scope.references[6].identifier.name).to.be.equal('i'); 198 | expect(scope.references[6].resolved).to.be.equal(scope.variables[0]); 199 | 200 | scope = scopeManager.scopes[3]; 201 | expect(scope.type).to.be.equal('block'); 202 | expect(scope.variables).to.have.length(0); 203 | expect(scope.references).to.have.length(4); 204 | expect(scope.references[0].identifier.name).to.be.equal('console'); 205 | expect(scope.references[0].resolved).to.be.null; 206 | expect(scope.references[1].identifier.name).to.be.equal('i'); 207 | expect(scope.references[1].resolved).to.be.equal(iterScope.variables[0]); 208 | expect(scope.references[2].identifier.name).to.be.equal('j'); 209 | expect(scope.references[2].resolved).to.be.equal(iterScope.variables[1]); 210 | expect(scope.references[3].identifier.name).to.be.equal('k'); 211 | expect(scope.references[3].resolved).to.be.equal(iterScope.variables[2]); 212 | }); 213 | }); 214 | 215 | // vim: set sw=4 ts=4 et tw=80 : 216 | -------------------------------------------------------------------------------- /src/referencer.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | import { Syntax } from 'estraverse'; 25 | import esrecurse from 'esrecurse'; 26 | import Reference from './reference'; 27 | import Variable from './variable'; 28 | import PatternVisitor from './pattern-visitor'; 29 | import { ParameterDefinition, Definition } from './definition'; 30 | import assert from 'assert'; 31 | 32 | function traverseIdentifierInPattern(options, rootPattern, referencer, callback) { 33 | // Call the callback at left hand identifier nodes, and Collect right hand nodes. 34 | var visitor = new PatternVisitor(options, rootPattern, callback); 35 | visitor.visit(rootPattern); 36 | 37 | // Process the right hand nodes recursively. 38 | if (referencer != null) { 39 | visitor.rightHandNodes.forEach(referencer.visit, referencer); 40 | } 41 | } 42 | 43 | // Importing ImportDeclaration. 44 | // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-moduledeclarationinstantiation 45 | // https://github.com/estree/estree/blob/master/es6.md#importdeclaration 46 | // FIXME: Now, we don't create module environment, because the context is 47 | // implementation dependent. 48 | 49 | class Importer extends esrecurse.Visitor { 50 | constructor(declaration, referencer) { 51 | super(null, referencer.options); 52 | this.declaration = declaration; 53 | this.referencer = referencer; 54 | } 55 | 56 | visitImport(id, specifier) { 57 | this.referencer.visitPattern(id, (pattern) => { 58 | this.referencer.currentScope().__define(pattern, 59 | new Definition( 60 | Variable.ImportBinding, 61 | pattern, 62 | specifier, 63 | this.declaration, 64 | null, 65 | null 66 | )); 67 | }); 68 | } 69 | 70 | ImportNamespaceSpecifier(node) { 71 | let local = (node.local || node.id); 72 | if (local) { 73 | this.visitImport(local, node); 74 | } 75 | } 76 | 77 | ImportDefaultSpecifier(node) { 78 | let local = (node.local || node.id); 79 | this.visitImport(local, node); 80 | } 81 | 82 | ImportSpecifier(node) { 83 | let local = (node.local || node.id); 84 | if (node.name) { 85 | this.visitImport(node.name, node); 86 | } else { 87 | this.visitImport(local, node); 88 | } 89 | } 90 | } 91 | 92 | // Referencing variables and creating bindings. 93 | export default class Referencer extends esrecurse.Visitor { 94 | constructor(options, scopeManager) { 95 | super(null, options); 96 | this.options = options; 97 | this.scopeManager = scopeManager; 98 | this.parent = null; 99 | this.isInnerMethodDefinition = false; 100 | } 101 | 102 | currentScope() { 103 | return this.scopeManager.__currentScope; 104 | } 105 | 106 | close(node) { 107 | while (this.currentScope() && node === this.currentScope().block) { 108 | this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager); 109 | } 110 | } 111 | 112 | pushInnerMethodDefinition(isInnerMethodDefinition) { 113 | var previous = this.isInnerMethodDefinition; 114 | this.isInnerMethodDefinition = isInnerMethodDefinition; 115 | return previous; 116 | } 117 | 118 | popInnerMethodDefinition(isInnerMethodDefinition) { 119 | this.isInnerMethodDefinition = isInnerMethodDefinition; 120 | } 121 | 122 | materializeTDZScope(node, iterationNode) { 123 | // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-forin-div-ofexpressionevaluation-abstract-operation 124 | // TDZ scope hides the declaration's names. 125 | this.scopeManager.__nestTDZScope(node, iterationNode); 126 | this.visitVariableDeclaration(this.currentScope(), Variable.TDZ, iterationNode.left, 0, true); 127 | } 128 | 129 | materializeIterationScope(node) { 130 | // Generate iteration scope for upper ForIn/ForOf Statements. 131 | var letOrConstDecl; 132 | this.scopeManager.__nestForScope(node); 133 | letOrConstDecl = node.left; 134 | this.visitVariableDeclaration(this.currentScope(), Variable.Variable, letOrConstDecl, 0); 135 | this.visitPattern(letOrConstDecl.declarations[0].id, (pattern) => { 136 | this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true); 137 | }); 138 | } 139 | 140 | referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) { 141 | const scope = this.currentScope(); 142 | assignments.forEach(assignment => { 143 | scope.__referencing( 144 | pattern, 145 | Reference.WRITE, 146 | assignment.right, 147 | maybeImplicitGlobal, 148 | pattern !== assignment.left, 149 | init); 150 | }); 151 | } 152 | 153 | visitPattern(node, options, callback) { 154 | if (typeof options === 'function') { 155 | callback = options; 156 | options = {processRightHandNodes: false} 157 | } 158 | traverseIdentifierInPattern( 159 | this.options, 160 | node, 161 | options.processRightHandNodes ? this : null, 162 | callback); 163 | } 164 | 165 | visitFunction(node) { 166 | var i, iz; 167 | // FunctionDeclaration name is defined in upper scope 168 | // NOTE: Not referring variableScope. It is intended. 169 | // Since 170 | // in ES5, FunctionDeclaration should be in FunctionBody. 171 | // in ES6, FunctionDeclaration should be block scoped. 172 | if (node.type === Syntax.FunctionDeclaration) { 173 | // id is defined in upper scope 174 | this.currentScope().__define(node.id, 175 | new Definition( 176 | Variable.FunctionName, 177 | node.id, 178 | node, 179 | null, 180 | null, 181 | null 182 | )); 183 | } 184 | 185 | // FunctionExpression with name creates its special scope; 186 | // FunctionExpressionNameScope. 187 | if (node.type === Syntax.FunctionExpression && node.id) { 188 | this.scopeManager.__nestFunctionExpressionNameScope(node); 189 | } 190 | 191 | // Consider this function is in the MethodDefinition. 192 | this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition); 193 | 194 | // Process parameter declarations. 195 | for (i = 0, iz = node.params.length; i < iz; ++i) { 196 | this.visitPattern(node.params[i], {processRightHandNodes: true}, (pattern, info) => { 197 | this.currentScope().__define(pattern, 198 | new ParameterDefinition( 199 | pattern, 200 | node, 201 | i, 202 | info.rest 203 | )); 204 | 205 | this.referencingDefaultValue(pattern, info.assignments, null, true); 206 | }); 207 | } 208 | 209 | // if there's a rest argument, add that 210 | if (node.rest) { 211 | this.visitPattern({ 212 | type: 'RestElement', 213 | argument: node.rest 214 | }, (pattern) => { 215 | this.currentScope().__define(pattern, 216 | new ParameterDefinition( 217 | pattern, 218 | node, 219 | node.params.length, 220 | true 221 | )); 222 | }); 223 | } 224 | 225 | // Skip BlockStatement to prevent creating BlockStatement scope. 226 | if (node.body.type === Syntax.BlockStatement) { 227 | this.visitChildren(node.body); 228 | } else { 229 | this.visit(node.body); 230 | } 231 | 232 | this.close(node); 233 | } 234 | 235 | visitClass(node) { 236 | if (node.type === Syntax.ClassDeclaration) { 237 | this.currentScope().__define(node.id, 238 | new Definition( 239 | Variable.ClassName, 240 | node.id, 241 | node, 242 | null, 243 | null, 244 | null 245 | )); 246 | } 247 | 248 | // FIXME: Maybe consider TDZ. 249 | this.visit(node.superClass); 250 | 251 | this.scopeManager.__nestClassScope(node); 252 | 253 | if (node.id) { 254 | this.currentScope().__define(node.id, 255 | new Definition( 256 | Variable.ClassName, 257 | node.id, 258 | node 259 | )); 260 | } 261 | this.visit(node.body); 262 | 263 | this.close(node); 264 | } 265 | 266 | visitProperty(node) { 267 | var previous, isMethodDefinition; 268 | if (node.computed) { 269 | this.visit(node.key); 270 | } 271 | 272 | isMethodDefinition = node.type === Syntax.MethodDefinition; 273 | if (isMethodDefinition) { 274 | previous = this.pushInnerMethodDefinition(true); 275 | } 276 | this.visit(node.value); 277 | if (isMethodDefinition) { 278 | this.popInnerMethodDefinition(previous); 279 | } 280 | } 281 | 282 | visitForIn(node) { 283 | if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== 'var') { 284 | this.materializeTDZScope(node.right, node); 285 | this.visit(node.right); 286 | this.close(node.right); 287 | 288 | this.materializeIterationScope(node); 289 | this.visit(node.body); 290 | this.close(node); 291 | } else { 292 | if (node.left.type === Syntax.VariableDeclaration) { 293 | this.visit(node.left); 294 | this.visitPattern(node.left.declarations[0].id, (pattern) => { 295 | this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true); 296 | }); 297 | } else { 298 | this.visitPattern(node.left, {processRightHandNodes: true}, (pattern, info) => { 299 | var maybeImplicitGlobal = null; 300 | if (!this.currentScope().isStrict) { 301 | maybeImplicitGlobal = { 302 | pattern: pattern, 303 | node: node 304 | }; 305 | } 306 | this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false); 307 | this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false); 308 | }); 309 | } 310 | this.visit(node.right); 311 | this.visit(node.body); 312 | } 313 | } 314 | 315 | visitVariableDeclaration(variableTargetScope, type, node, index, fromTDZ) { 316 | // If this was called to initialize a TDZ scope, this needs to make definitions, but doesn't make references. 317 | var decl, init; 318 | 319 | decl = node.declarations[index]; 320 | init = decl.init; 321 | this.visitPattern(decl.id, {processRightHandNodes: !fromTDZ}, (pattern, info) => { 322 | variableTargetScope.__define(pattern, 323 | new Definition( 324 | type, 325 | pattern, 326 | decl, 327 | node, 328 | index, 329 | node.kind 330 | )); 331 | 332 | if (!fromTDZ) { 333 | this.referencingDefaultValue(pattern, info.assignments, null, true); 334 | } 335 | if (init) { 336 | this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true); 337 | } 338 | }); 339 | } 340 | 341 | AssignmentExpression(node) { 342 | if (PatternVisitor.isPattern(node.left)) { 343 | if (node.operator === '=') { 344 | this.visitPattern(node.left, {processRightHandNodes: true}, (pattern, info) => { 345 | var maybeImplicitGlobal = null; 346 | if (!this.currentScope().isStrict) { 347 | maybeImplicitGlobal = { 348 | pattern: pattern, 349 | node: node 350 | }; 351 | } 352 | this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false); 353 | this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false); 354 | }); 355 | } else { 356 | this.currentScope().__referencing(node.left, Reference.RW, node.right); 357 | } 358 | } else { 359 | this.visit(node.left); 360 | } 361 | this.visit(node.right); 362 | } 363 | 364 | CatchClause(node) { 365 | this.scopeManager.__nestCatchScope(node); 366 | 367 | this.visitPattern(node.param, {processRightHandNodes: true}, (pattern, info) => { 368 | this.currentScope().__define(pattern, 369 | new Definition( 370 | Variable.CatchClause, 371 | node.param, 372 | node, 373 | null, 374 | null, 375 | null 376 | )); 377 | this.referencingDefaultValue(pattern, info.assignments, null, true); 378 | }); 379 | this.visit(node.body); 380 | 381 | this.close(node); 382 | } 383 | 384 | Program(node) { 385 | this.scopeManager.__nestGlobalScope(node); 386 | 387 | if (this.scopeManager.__isNodejsScope()) { 388 | // Force strictness of GlobalScope to false when using node.js scope. 389 | this.currentScope().isStrict = false; 390 | this.scopeManager.__nestFunctionScope(node, false); 391 | } 392 | 393 | if (this.scopeManager.__isES6() && this.scopeManager.isModule()) { 394 | this.scopeManager.__nestModuleScope(node); 395 | } 396 | 397 | if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) { 398 | this.currentScope().isStrict = true; 399 | } 400 | 401 | this.visitChildren(node); 402 | this.close(node); 403 | } 404 | 405 | Identifier(node) { 406 | this.currentScope().__referencing(node); 407 | } 408 | 409 | UpdateExpression(node) { 410 | if (PatternVisitor.isPattern(node.argument)) { 411 | this.currentScope().__referencing(node.argument, Reference.RW, null); 412 | } else { 413 | this.visitChildren(node); 414 | } 415 | } 416 | 417 | MemberExpression(node) { 418 | this.visit(node.object); 419 | if (node.computed) { 420 | this.visit(node.property); 421 | } 422 | } 423 | 424 | Property(node) { 425 | this.visitProperty(node); 426 | } 427 | 428 | MethodDefinition(node) { 429 | this.visitProperty(node); 430 | } 431 | 432 | BreakStatement() {} 433 | 434 | ContinueStatement() {} 435 | 436 | LabeledStatement(node) { 437 | this.visit(node.body); 438 | } 439 | 440 | ForStatement(node) { 441 | // Create ForStatement declaration. 442 | // NOTE: In ES6, ForStatement dynamically generates 443 | // per iteration environment. However, escope is 444 | // a static analyzer, we only generate one scope for ForStatement. 445 | if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== 'var') { 446 | this.scopeManager.__nestForScope(node); 447 | } 448 | 449 | this.visitChildren(node); 450 | 451 | this.close(node); 452 | } 453 | 454 | ClassExpression(node) { 455 | this.visitClass(node); 456 | } 457 | 458 | ClassDeclaration(node) { 459 | this.visitClass(node); 460 | } 461 | 462 | CallExpression(node) { 463 | // Check this is direct call to eval 464 | if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === 'eval') { 465 | // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and 466 | // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment. 467 | this.currentScope().variableScope.__detectEval(); 468 | } 469 | this.visitChildren(node); 470 | } 471 | 472 | BlockStatement(node) { 473 | if (this.scopeManager.__isES6()) { 474 | this.scopeManager.__nestBlockScope(node); 475 | } 476 | 477 | this.visitChildren(node); 478 | 479 | this.close(node); 480 | } 481 | 482 | ThisExpression() { 483 | this.currentScope().variableScope.__detectThis(); 484 | } 485 | 486 | WithStatement(node) { 487 | this.visit(node.object); 488 | // Then nest scope for WithStatement. 489 | this.scopeManager.__nestWithScope(node); 490 | 491 | this.visit(node.body); 492 | 493 | this.close(node); 494 | } 495 | 496 | VariableDeclaration(node) { 497 | var variableTargetScope, i, iz, decl; 498 | variableTargetScope = (node.kind === 'var') ? this.currentScope().variableScope : this.currentScope(); 499 | for (i = 0, iz = node.declarations.length; i < iz; ++i) { 500 | decl = node.declarations[i]; 501 | this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i); 502 | if (decl.init) { 503 | this.visit(decl.init); 504 | } 505 | } 506 | } 507 | 508 | // sec 13.11.8 509 | SwitchStatement(node) { 510 | var i, iz; 511 | 512 | this.visit(node.discriminant); 513 | 514 | if (this.scopeManager.__isES6()) { 515 | this.scopeManager.__nestSwitchScope(node); 516 | } 517 | 518 | for (i = 0, iz = node.cases.length; i < iz; ++i) { 519 | this.visit(node.cases[i]); 520 | } 521 | 522 | this.close(node); 523 | } 524 | 525 | FunctionDeclaration(node) { 526 | this.visitFunction(node); 527 | } 528 | 529 | FunctionExpression(node) { 530 | this.visitFunction(node); 531 | } 532 | 533 | ForOfStatement(node) { 534 | this.visitForIn(node); 535 | } 536 | 537 | ForInStatement(node) { 538 | this.visitForIn(node); 539 | } 540 | 541 | ArrowFunctionExpression(node) { 542 | this.visitFunction(node); 543 | } 544 | 545 | ImportDeclaration(node) { 546 | var importer; 547 | 548 | assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), 'ImportDeclaration should appear when the mode is ES6 and in the module context.'); 549 | 550 | importer = new Importer(node, this); 551 | importer.visit(node); 552 | } 553 | 554 | visitExportDeclaration(node) { 555 | if (node.source) { 556 | return; 557 | } 558 | if (node.declaration) { 559 | this.visit(node.declaration); 560 | return; 561 | } 562 | 563 | this.visitChildren(node); 564 | } 565 | 566 | ExportDeclaration(node) { 567 | this.visitExportDeclaration(node); 568 | } 569 | 570 | ExportNamedDeclaration(node) { 571 | this.visitExportDeclaration(node); 572 | } 573 | 574 | ExportSpecifier(node) { 575 | let local = (node.id || node.local); 576 | this.visit(local); 577 | } 578 | 579 | MetaProperty() { 580 | // do nothing. 581 | } 582 | } 583 | 584 | /* vim: set sw=4 ts=4 et tw=80 : */ 585 | --------------------------------------------------------------------------------