Upgrade to https://github.com/acornjs/acorn.git commit 84eda6bf
[jst.git] / src / scope.js
1 import {Parser} from "./state.js"
2 import {SCOPE_VAR, SCOPE_FUNCTION, SCOPE_TOP, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, BIND_LEXICAL, BIND_SIMPLE_CATCH, BIND_FUNCTION} from "./scopeflags.js"
3
4 const pp = Parser.prototype
5
6 class Scope {
7   constructor(flags) {
8     this.flags = flags
9     // A list of var-declared names in the current lexical scope
10     this.var = []
11     // A list of lexically-declared names in the current lexical scope
12     this.lexical = []
13     // A list of lexically-declared FunctionDeclaration names in the current lexical scope
14     this.functions = []
15     // A switch to disallow the identifier reference 'arguments'
16     this.inClassFieldInit = false
17   }
18 }
19
20 // The functions in this module keep track of declared variables in the current scope in order to detect duplicate variable names.
21
22 pp.enterScope = function(flags) {
23   this.scopeStack.push(new Scope(flags))
24 }
25
26 pp.exitScope = function() {
27   this.scopeStack.pop()
28 }
29
30 // The spec says:
31 // > At the top level of a function, or script, function declarations are
32 // > treated like var declarations rather than like lexical declarations.
33 pp.treatFunctionsAsVarInScope = function(scope) {
34   return (scope.flags & SCOPE_FUNCTION) || !this.inModule && (scope.flags & SCOPE_TOP)
35 }
36
37 pp.declareName = function(name, bindingType, pos) {
38   let redeclared = false
39   if (bindingType === BIND_LEXICAL) {
40     const scope = this.currentScope()
41     redeclared = scope.lexical.indexOf(name) > -1 || scope.functions.indexOf(name) > -1 || scope.var.indexOf(name) > -1
42     scope.lexical.push(name)
43     if (this.inModule && (scope.flags & SCOPE_TOP))
44       delete this.undefinedExports[name]
45   } else if (bindingType === BIND_SIMPLE_CATCH) {
46     const scope = this.currentScope()
47     scope.lexical.push(name)
48   } else if (bindingType === BIND_FUNCTION) {
49     const scope = this.currentScope()
50     if (this.treatFunctionsAsVar)
51       redeclared = scope.lexical.indexOf(name) > -1
52     else
53       redeclared = scope.lexical.indexOf(name) > -1 || scope.var.indexOf(name) > -1
54     scope.functions.push(name)
55   } else {
56     for (let i = this.scopeStack.length - 1; i >= 0; --i) {
57       const scope = this.scopeStack[i]
58       if (scope.lexical.indexOf(name) > -1 && !((scope.flags & SCOPE_SIMPLE_CATCH) && scope.lexical[0] === name) ||
59           !this.treatFunctionsAsVarInScope(scope) && scope.functions.indexOf(name) > -1) {
60         redeclared = true
61         break
62       }
63       scope.var.push(name)
64       if (this.inModule && (scope.flags & SCOPE_TOP))
65         delete this.undefinedExports[name]
66       if (scope.flags & SCOPE_VAR) break
67     }
68   }
69   if (redeclared) this.raiseRecoverable(pos, `Identifier '${name}' has already been declared`)
70 }
71
72 pp.checkLocalExport = function(id) {
73   // scope.functions must be empty as Module code is always strict.
74   if (this.scopeStack[0].lexical.indexOf(id.name) === -1 &&
75       this.scopeStack[0].var.indexOf(id.name) === -1) {
76     this.undefinedExports[id.name] = id
77   }
78 }
79
80 pp.currentScope = function() {
81   return this.scopeStack[this.scopeStack.length - 1]
82 }
83
84 pp.currentVarScope = function() {
85   for (let i = this.scopeStack.length - 1;; i--) {
86     let scope = this.scopeStack[i]
87     if (scope.flags & SCOPE_VAR) return scope
88   }
89 }
90
91 // Could be useful for `this`, `new.target`, `super()`, `super.property`, and `super[property]`.
92 pp.currentThisScope = function() {
93   for (let i = this.scopeStack.length - 1;; i--) {
94     let scope = this.scopeStack[i]
95     if (scope.flags & SCOPE_VAR && !(scope.flags & SCOPE_ARROW)) return scope
96   }
97 }