"use strict";

var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };

var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

var _get = function get(object, property, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc && desc.writable) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };

var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };

var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };

/*
  Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

var Syntax = require("estraverse").Syntax;

var esrecurse = _interopRequire(require("esrecurse"));

var Reference = _interopRequire(require("./reference"));

var Variable = _interopRequire(require("./variable"));

var _definition = require("./definition");

var ParameterDefinition = _definition.ParameterDefinition;
var Definition = _definition.Definition;

var assert = _interopRequire(require("assert"));

var PatternVisitor = (function (_esrecurse$Visitor) {
    function PatternVisitor(rootPattern, callback) {
        _classCallCheck(this, PatternVisitor);

        _get(Object.getPrototypeOf(PatternVisitor.prototype), "constructor", this).call(this);
        this.rootPattern = rootPattern;
        this.callback = callback;
        this.assignments = [];
        this.rightHandNodes = [];
        this.restElements = [];
    }

    _inherits(PatternVisitor, _esrecurse$Visitor);

    _createClass(PatternVisitor, {
        Identifier: {
            value: function Identifier(pattern) {
                var lastRestElement = getLast(this.restElements);
                this.callback(pattern, {
                    topLevel: pattern === this.rootPattern,
                    rest: lastRestElement != null && lastRestElement.argument === pattern,
                    assignments: this.assignments
                });
            }
        },
        ObjectPattern: {
            value: function ObjectPattern(pattern) {
                var i, iz, property;
                for (i = 0, iz = pattern.properties.length; i < iz; ++i) {
                    property = pattern.properties[i];

                    // Computed property's key is a right hand node.
                    if (property.computed) {
                        this.rightHandNodes.push(property.key);
                    }

                    // If it's shorthand, its key is same as its value.
                    // If it's shorthand and has its default value, its key is same as its value.left (the value is AssignmentPattern).
                    // If it's not shorthand, the name of new variable is its value's.
                    this.visit(property.value);
                }
            }
        },
        ArrayPattern: {
            value: function ArrayPattern(pattern) {
                var i, iz, element;
                for (i = 0, iz = pattern.elements.length; i < iz; ++i) {
                    element = pattern.elements[i];
                    this.visit(element);
                }
            }
        },
        AssignmentPattern: {
            value: function AssignmentPattern(pattern) {
                this.assignments.push(pattern);
                this.visit(pattern.left);
                this.rightHandNodes.push(pattern.right);
                this.assignments.pop();
            }
        },
        RestElement: {
            value: function RestElement(pattern) {
                this.restElements.push(pattern);
                this.visit(pattern.argument);
                this.restElements.pop();
            }
        },
        MemberExpression: {
            value: function MemberExpression(node) {
                // Computed property's key is a right hand node.
                if (node.computed) {
                    this.rightHandNodes.push(node.property);
                }
                // the object is only read, write to its property.
                this.rightHandNodes.push(node.object);
            }
        },
        SpreadElement: {

            //
            // ForInStatement.left and AssignmentExpression.left are LeftHandSideExpression.
            // By spec, LeftHandSideExpression is Pattern or MemberExpression.
            //   (see also: https://github.com/estree/estree/pull/20#issuecomment-74584758)
            // But espree 2.0 and esprima 2.0 parse to ArrayExpression, ObjectExpression, etc...
            //

            value: function SpreadElement(node) {
                this.visit(node.argument);
            }
        },
        ArrayExpression: {
            value: function ArrayExpression(node) {
                node.elements.forEach(this.visit, this);
            }
        },
        ObjectExpression: {
            value: function ObjectExpression(node) {
                var _this = this;

                node.properties.forEach(function (property) {
                    // Computed property's key is a right hand node.
                    if (property.computed) {
                        _this.rightHandNodes.push(property.key);
                    }
                    _this.visit(property.value);
                });
            }
        },
        AssignmentExpression: {
            value: function AssignmentExpression(node) {
                this.assignments.push(node);
                this.visit(node.left);
                this.rightHandNodes.push(node.right);
                this.assignments.pop();
            }
        },
        CallExpression: {
            value: function CallExpression(node) {
                var _this = this;

                // arguments are right hand nodes.
                node.arguments.forEach(function (a) {
                    _this.rightHandNodes.push(a);
                });
                this.visit(node.callee);
            }
        }
    });

    return PatternVisitor;
})(esrecurse.Visitor);

function getLast(xs) {
    return xs[xs.length - 1] || null;
}

function traverseIdentifierInPattern(rootPattern, referencer, callback) {
    // Call the callback at left hand identifier nodes, and Collect right hand nodes.
    var visitor = new PatternVisitor(rootPattern, callback);
    visitor.visit(rootPattern);

    // Process the right hand nodes recursively.
    if (referencer != null) {
        visitor.rightHandNodes.forEach(referencer.visit, referencer);
    }
}

function isPattern(node) {
    var nodeType = node.type;
    return nodeType === Syntax.Identifier || nodeType === Syntax.ObjectPattern || nodeType === Syntax.ArrayPattern || nodeType === Syntax.SpreadElement || nodeType === Syntax.RestElement || nodeType === Syntax.AssignmentPattern;
}

// Importing ImportDeclaration.
// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-moduledeclarationinstantiation
// https://github.com/estree/estree/blob/master/es6.md#importdeclaration
// FIXME: Now, we don't create module environment, because the context is
// implementation dependent.

var Importer = (function (_esrecurse$Visitor2) {
    function Importer(declaration, referencer) {
        _classCallCheck(this, Importer);

        _get(Object.getPrototypeOf(Importer.prototype), "constructor", this).call(this);
        this.declaration = declaration;
        this.referencer = referencer;
    }

    _inherits(Importer, _esrecurse$Visitor2);

    _createClass(Importer, {
        visitImport: {
            value: function visitImport(id, specifier) {
                var _this = this;

                this.referencer.visitPattern(id, function (pattern) {
                    _this.referencer.currentScope().__define(pattern, new Definition(Variable.ImportBinding, pattern, specifier, _this.declaration, null, null));
                });
            }
        },
        ImportNamespaceSpecifier: {
            value: function ImportNamespaceSpecifier(node) {
                var local = node.local || node.id;
                if (local) {
                    this.visitImport(local, node);
                }
            }
        },
        ImportDefaultSpecifier: {
            value: function ImportDefaultSpecifier(node) {
                var local = node.local || node.id;
                this.visitImport(local, node);
            }
        },
        ImportSpecifier: {
            value: function ImportSpecifier(node) {
                var local = node.local || node.id;
                if (node.name) {
                    this.visitImport(node.name, node);
                } else {
                    this.visitImport(local, node);
                }
            }
        }
    });

    return Importer;
})(esrecurse.Visitor);

// Referencing variables and creating bindings.

var Referencer = (function (_esrecurse$Visitor3) {
    function Referencer(scopeManager) {
        _classCallCheck(this, Referencer);

        _get(Object.getPrototypeOf(Referencer.prototype), "constructor", this).call(this);
        this.scopeManager = scopeManager;
        this.parent = null;
        this.isInnerMethodDefinition = false;
    }

    _inherits(Referencer, _esrecurse$Visitor3);

    _createClass(Referencer, {
        currentScope: {
            value: function currentScope() {
                return this.scopeManager.__currentScope;
            }
        },
        close: {
            value: function close(node) {
                while (this.currentScope() && node === this.currentScope().block) {
                    this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager);
                }
            }
        },
        pushInnerMethodDefinition: {
            value: function pushInnerMethodDefinition(isInnerMethodDefinition) {
                var previous = this.isInnerMethodDefinition;
                this.isInnerMethodDefinition = isInnerMethodDefinition;
                return previous;
            }
        },
        popInnerMethodDefinition: {
            value: function popInnerMethodDefinition(isInnerMethodDefinition) {
                this.isInnerMethodDefinition = isInnerMethodDefinition;
            }
        },
        materializeTDZScope: {
            value: function materializeTDZScope(node, iterationNode) {
                // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-forin-div-ofexpressionevaluation-abstract-operation
                // TDZ scope hides the declaration's names.
                this.scopeManager.__nestTDZScope(node, iterationNode);
                this.visitVariableDeclaration(this.currentScope(), Variable.TDZ, iterationNode.left, 0, true);
            }
        },
        materializeIterationScope: {
            value: function materializeIterationScope(node) {
                var _this = this;

                // Generate iteration scope for upper ForIn/ForOf Statements.
                var letOrConstDecl;
                this.scopeManager.__nestForScope(node);
                letOrConstDecl = node.left;
                this.visitVariableDeclaration(this.currentScope(), Variable.Variable, letOrConstDecl, 0);
                this.visitPattern(letOrConstDecl.declarations[0].id, function (pattern) {
                    _this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
                });
            }
        },
        referencingDefaultValue: {
            value: function referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) {
                var scope = this.currentScope();
                assignments.forEach(function (assignment) {
                    scope.__referencing(pattern, Reference.WRITE, assignment.right, maybeImplicitGlobal, pattern !== assignment.left, init);
                });
            }
        },
        visitPattern: {
            value: function visitPattern(node, options, callback) {
                if (typeof options === "function") {
                    callback = options;
                    options = { processRightHandNodes: false };
                }
                traverseIdentifierInPattern(node, options.processRightHandNodes ? this : null, callback);
            }
        },
        visitFunction: {
            value: function visitFunction(node) {
                var _this = this;

                var i, iz;
                // FunctionDeclaration name is defined in upper scope
                // NOTE: Not referring variableScope. It is intended.
                // Since
                //  in ES5, FunctionDeclaration should be in FunctionBody.
                //  in ES6, FunctionDeclaration should be block scoped.
                if (node.type === Syntax.FunctionDeclaration) {
                    // id is defined in upper scope
                    this.currentScope().__define(node.id, new Definition(Variable.FunctionName, node.id, node, null, null, null));
                }

                // FunctionExpression with name creates its special scope;
                // FunctionExpressionNameScope.
                if (node.type === Syntax.FunctionExpression && node.id) {
                    this.scopeManager.__nestFunctionExpressionNameScope(node);
                }

                // Consider this function is in the MethodDefinition.
                this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);

                // Process parameter declarations.
                for (i = 0, iz = node.params.length; i < iz; ++i) {
                    this.visitPattern(node.params[i], { processRightHandNodes: true }, function (pattern, info) {
                        _this.currentScope().__define(pattern, new ParameterDefinition(pattern, node, i, info.rest));

                        _this.referencingDefaultValue(pattern, info.assignments, null, true);
                    });
                }

                // if there's a rest argument, add that
                if (node.rest) {
                    this.visitPattern({
                        type: "RestElement",
                        argument: node.rest
                    }, function (pattern) {
                        _this.currentScope().__define(pattern, new ParameterDefinition(pattern, node, node.params.length, true));
                    });
                }

                // Skip BlockStatement to prevent creating BlockStatement scope.
                if (node.body.type === Syntax.BlockStatement) {
                    this.visitChildren(node.body);
                } else {
                    this.visit(node.body);
                }

                this.close(node);
            }
        },
        visitClass: {
            value: function visitClass(node) {
                if (node.type === Syntax.ClassDeclaration) {
                    this.currentScope().__define(node.id, new Definition(Variable.ClassName, node.id, node, null, null, null));
                }

                // FIXME: Maybe consider TDZ.
                this.visit(node.superClass);

                this.scopeManager.__nestClassScope(node);

                if (node.id) {
                    this.currentScope().__define(node.id, new Definition(Variable.ClassName, node.id, node));
                }
                this.visit(node.body);

                this.close(node);
            }
        },
        visitProperty: {
            value: function visitProperty(node) {
                var previous, isMethodDefinition;
                if (node.computed) {
                    this.visit(node.key);
                }

                isMethodDefinition = node.type === Syntax.MethodDefinition || node.method;
                if (isMethodDefinition) {
                    previous = this.pushInnerMethodDefinition(true);
                }
                this.visit(node.value);
                if (isMethodDefinition) {
                    this.popInnerMethodDefinition(previous);
                }
            }
        },
        visitForIn: {
            value: function visitForIn(node) {
                var _this = this;

                if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== "var") {
                    this.materializeTDZScope(node.right, node);
                    this.visit(node.right);
                    this.close(node.right);

                    this.materializeIterationScope(node);
                    this.visit(node.body);
                    this.close(node);
                } else {
                    if (node.left.type === Syntax.VariableDeclaration) {
                        this.visit(node.left);
                        this.visitPattern(node.left.declarations[0].id, function (pattern) {
                            _this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
                        });
                    } else {
                        this.visitPattern(node.left, { processRightHandNodes: true }, function (pattern, info) {
                            var maybeImplicitGlobal = null;
                            if (!_this.currentScope().isStrict) {
                                maybeImplicitGlobal = {
                                    pattern: pattern,
                                    node: node
                                };
                            }
                            _this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
                            _this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false);
                        });
                    }
                    this.visit(node.right);
                    this.visit(node.body);
                }
            }
        },
        visitVariableDeclaration: {
            value: function visitVariableDeclaration(variableTargetScope, type, node, index, fromTDZ) {
                var _this = this;

                // If this was called to initialize a TDZ scope, this needs to make definitions, but doesn't make references.
                var decl, init;

                decl = node.declarations[index];
                init = decl.init;
                this.visitPattern(decl.id, { processRightHandNodes: !fromTDZ }, function (pattern, info) {
                    variableTargetScope.__define(pattern, new Definition(type, pattern, decl, node, index, node.kind));

                    if (!fromTDZ) {
                        _this.referencingDefaultValue(pattern, info.assignments, null, true);
                    }
                    if (init) {
                        _this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true);
                    }
                });
            }
        },
        AssignmentExpression: {
            value: function AssignmentExpression(node) {
                var _this = this;

                if (isPattern(node.left)) {
                    if (node.operator === "=") {
                        this.visitPattern(node.left, { processRightHandNodes: true }, function (pattern, info) {
                            var maybeImplicitGlobal = null;
                            if (!_this.currentScope().isStrict) {
                                maybeImplicitGlobal = {
                                    pattern: pattern,
                                    node: node
                                };
                            }
                            _this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
                            _this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false);
                        });
                    } else {
                        this.currentScope().__referencing(node.left, Reference.RW, node.right);
                    }
                } else {
                    this.visit(node.left);
                }
                this.visit(node.right);
            }
        },
        CatchClause: {
            value: function CatchClause(node) {
                var _this = this;

                this.scopeManager.__nestCatchScope(node);

                this.visitPattern(node.param, { processRightHandNodes: true }, function (pattern, info) {
                    _this.currentScope().__define(pattern, new Definition(Variable.CatchClause, node.param, node, null, null, null));
                    _this.referencingDefaultValue(pattern, info.assignments, null, true);
                });
                this.visit(node.body);

                this.close(node);
            }
        },
        Program: {
            value: function Program(node) {
                this.scopeManager.__nestGlobalScope(node);

                if (this.scopeManager.__isNodejsScope()) {
                    // Force strictness of GlobalScope to false when using node.js scope.
                    this.currentScope().isStrict = false;
                    this.scopeManager.__nestFunctionScope(node, false);
                }

                if (this.scopeManager.__isES6() && this.scopeManager.isModule()) {
                    this.scopeManager.__nestModuleScope(node);
                }

                this.visitChildren(node);
                this.close(node);
            }
        },
        Identifier: {
            value: function Identifier(node) {
                this.currentScope().__referencing(node);
            }
        },
        UpdateExpression: {
            value: function UpdateExpression(node) {
                if (isPattern(node.argument)) {
                    this.currentScope().__referencing(node.argument, Reference.RW, null);
                } else {
                    this.visitChildren(node);
                }
            }
        },
        MemberExpression: {
            value: function MemberExpression(node) {
                this.visit(node.object);
                if (node.computed) {
                    this.visit(node.property);
                }
            }
        },
        Property: {
            value: function Property(node) {
                this.visitProperty(node);
            }
        },
        MethodDefinition: {
            value: function MethodDefinition(node) {
                this.visitProperty(node);
            }
        },
        BreakStatement: {
            value: function BreakStatement() {}
        },
        ContinueStatement: {
            value: function ContinueStatement() {}
        },
        LabeledStatement: {
            value: function LabeledStatement(node) {
                this.visit(node.body);
            }
        },
        ForStatement: {
            value: function ForStatement(node) {
                // Create ForStatement declaration.
                // NOTE: In ES6, ForStatement dynamically generates
                // per iteration environment. However, escope is
                // a static analyzer, we only generate one scope for ForStatement.
                if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== "var") {
                    this.scopeManager.__nestForScope(node);
                }

                this.visitChildren(node);

                this.close(node);
            }
        },
        ClassExpression: {
            value: function ClassExpression(node) {
                this.visitClass(node);
            }
        },
        ClassDeclaration: {
            value: function ClassDeclaration(node) {
                this.visitClass(node);
            }
        },
        CallExpression: {
            value: function CallExpression(node) {
                // Check this is direct call to eval
                if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === "eval") {
                    // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and
                    // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment.
                    this.currentScope().variableScope.__detectEval();
                }
                this.visitChildren(node);
            }
        },
        BlockStatement: {
            value: function BlockStatement(node) {
                if (this.scopeManager.__isES6()) {
                    this.scopeManager.__nestBlockScope(node);
                }

                this.visitChildren(node);

                this.close(node);
            }
        },
        ThisExpression: {
            value: function ThisExpression() {
                this.currentScope().variableScope.__detectThis();
            }
        },
        WithStatement: {
            value: function WithStatement(node) {
                this.visit(node.object);
                // Then nest scope for WithStatement.
                this.scopeManager.__nestWithScope(node);

                this.visit(node.body);

                this.close(node);
            }
        },
        VariableDeclaration: {
            value: function VariableDeclaration(node) {
                var variableTargetScope, i, iz, decl;
                variableTargetScope = node.kind === "var" ? this.currentScope().variableScope : this.currentScope();
                for (i = 0, iz = node.declarations.length; i < iz; ++i) {
                    decl = node.declarations[i];
                    this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i);
                    if (decl.init) {
                        this.visit(decl.init);
                    }
                }
            }
        },
        SwitchStatement: {

            // sec 13.11.8

            value: function SwitchStatement(node) {
                var i, iz;

                this.visit(node.discriminant);

                if (this.scopeManager.__isES6()) {
                    this.scopeManager.__nestSwitchScope(node);
                }

                for (i = 0, iz = node.cases.length; i < iz; ++i) {
                    this.visit(node.cases[i]);
                }

                this.close(node);
            }
        },
        FunctionDeclaration: {
            value: function FunctionDeclaration(node) {
                this.visitFunction(node);
            }
        },
        FunctionExpression: {
            value: function FunctionExpression(node) {
                this.visitFunction(node);
            }
        },
        ForOfStatement: {
            value: function ForOfStatement(node) {
                this.visitForIn(node);
            }
        },
        ForInStatement: {
            value: function ForInStatement(node) {
                this.visitForIn(node);
            }
        },
        ArrowFunctionExpression: {
            value: function ArrowFunctionExpression(node) {
                this.visitFunction(node);
            }
        },
        ImportDeclaration: {
            value: function ImportDeclaration(node) {
                var importer;

                assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), "ImportDeclaration should appear when the mode is ES6 and in the module context.");

                importer = new Importer(node, this);
                importer.visit(node);
            }
        },
        visitExportDeclaration: {
            value: function visitExportDeclaration(node) {
                if (node.source) {
                    return;
                }
                if (node.declaration) {
                    this.visit(node.declaration);
                    return;
                }

                this.visitChildren(node);
            }
        },
        ExportDeclaration: {
            value: function ExportDeclaration(node) {
                this.visitExportDeclaration(node);
            }
        },
        ExportNamedDeclaration: {
            value: function ExportNamedDeclaration(node) {
                this.visitExportDeclaration(node);
            }
        },
        ExportSpecifier: {
            value: function ExportSpecifier(node) {
                var local = node.id || node.local;
                this.visit(local);
            }
        }
    });

    return Referencer;
})(esrecurse.Visitor);

module.exports = Referencer;

/* vim: set sw=4 ts=4 et tw=80 : */
//# sourceMappingURL=data:application/json;base64,[encoded data, 54956 characters]