summaryrefslogtreecommitdiff
path: root/deps/acorn-plugins/acorn-private-class-elements/index.js
blob: 90f2b704371c9f7fd838cabdf62231cc6b76fdbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"use strict"

const acorn = require('internal/deps/acorn/acorn/dist/acorn')
if (acorn.version.indexOf("6.") != 0 || acorn.version.indexOf("6.0.") == 0) {
  throw new Error(`acorn-private-class-elements requires acorn@^6.1.0, not ${acorn.version}`)
}
const tt = acorn.tokTypes
const TokenType = acorn.TokenType

module.exports = function(Parser) {
  // Only load this plugin once.
  if (Parser.prototype.parsePrivateName) {
    return Parser
  }

  // Make sure `Parser` comes from the same acorn as our `tt`,
  // otherwise the comparisons fail.
  let cur = Parser
  while (cur && cur !== acorn.Parser) {
    cur = cur.__proto__
  }
  if (cur !== acorn.Parser) {
    throw new Error("acorn-private-class-elements does not support mixing different acorn copies")
  }

  Parser = class extends Parser {
    _branch() {
      this.__branch = this.__branch || new Parser({ecmaVersion: this.options.ecmaVersion}, this.input)
      this.__branch.end = this.end
      this.__branch.pos = this.pos
      this.__branch.type = this.type
      this.__branch.value = this.value
      this.__branch.containsEsc = this.containsEsc
      return this.__branch
    }

    parsePrivateClassElementName(element) {
      element.computed = false
      element.key = this.parsePrivateName()
      if (element.key.name == "constructor") this.raise(element.key.start, "Classes may not have a private element named constructor")
      const accept = {get: "set", set: "get"}[element.kind]
      const privateBoundNames = this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1]
      if (Object.prototype.hasOwnProperty.call(privateBoundNames, element.key.name) && privateBoundNames[element.key.name] !== accept) {
        this.raise(element.start, "Duplicate private element")
      }
      privateBoundNames[element.key.name] = element.kind || true
      delete this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1][element.key.name]
      return element.key
    }

    parsePrivateName() {
      const node = this.startNode()
      node.name = this.value
      this.next()
      this.finishNode(node, "PrivateName")
      if (this.options.allowReserved == "never") this.checkUnreserved(node)
      return node
    }

    // Parse # token
    getTokenFromCode(code) {
      if (code === 35) {
        ++this.pos
        const word = this.readWord1()
        return this.finishToken(this.privateNameToken, word)
      }
      return super.getTokenFromCode(code)
    }

    // Manage stacks and check for undeclared private names
    parseClass(node, isStatement) {
      this._privateBoundNamesStack = this._privateBoundNamesStack || []
      const privateBoundNames = Object.create(this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1] || null)
      this._privateBoundNamesStack.push(privateBoundNames)
      this._unresolvedPrivateNamesStack = this._unresolvedPrivateNamesStack || []
      const unresolvedPrivateNames = Object.create(null)
      this._unresolvedPrivateNamesStack.push(unresolvedPrivateNames)
      const _return = super.parseClass(node, isStatement)
      this._privateBoundNamesStack.pop()
      this._unresolvedPrivateNamesStack.pop()
      if (!this._unresolvedPrivateNamesStack.length) {
        const names = Object.keys(unresolvedPrivateNames)
        if (names.length) {
          names.sort((n1, n2) => unresolvedPrivateNames[n1] - unresolvedPrivateNames[n2])
          this.raise(unresolvedPrivateNames[names[0]], "Usage of undeclared private name")
        }
      } else Object.assign(this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1], unresolvedPrivateNames)
      return _return
    }

    // Parse private element access
    parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow) {
      if (!this.eat(tt.dot)) {
        return super.parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow)
      }
      let node = this.startNodeAt(startPos, startLoc)
      node.object = base
      node.computed = false
      if (this.type == this.privateNameToken) {
        node.property = this.parsePrivateName()
        if (!this._privateBoundNamesStack.length || !this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1][node.property.name]) {
          this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1][node.property.name] = node.property.start
        }
      } else {
        node.property = this.parseIdent(true)
      }
      return this.finishNode(node, "MemberExpression")
    }

    // Prohibit delete of private class elements
    parseMaybeUnary(refDestructuringErrors, sawUnary) {
      const _return = super.parseMaybeUnary(refDestructuringErrors, sawUnary)
      if (_return.operator == "delete") {
        if (_return.argument.type == "MemberExpression" && _return.argument.property.type == "PrivateName") {
          this.raise(_return.start, "Private elements may not be deleted")
        }
      }
      return _return
    }
  }
  Parser.prototype.privateNameToken = new TokenType("privateName")
  return Parser
}