• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict"
2
3const acorn = require('internal/deps/acorn/acorn/dist/acorn')
4if (acorn.version.indexOf("6.") != 0 && acorn.version.indexOf("6.0.") == 0 && acorn.version.indexOf("7.") != 0) {
5  throw new Error(`acorn-private-class-elements requires acorn@^6.1.0 or acorn@7.0.0, not ${acorn.version}`)
6}
7const tt = acorn.tokTypes
8const TokenType = acorn.TokenType
9
10module.exports = function(Parser) {
11  // Only load this plugin once.
12  if (Parser.prototype.parsePrivateName) {
13    return Parser
14  }
15
16  // Make sure `Parser` comes from the same acorn as our `tt`,
17  // otherwise the comparisons fail.
18  let cur = Parser
19  while (cur && cur !== acorn.Parser) {
20    cur = cur.__proto__
21  }
22  if (cur !== acorn.Parser) {
23    throw new Error("acorn-private-class-elements does not support mixing different acorn copies")
24  }
25
26  Parser = class extends Parser {
27    _branch() {
28      this.__branch = this.__branch || new Parser({ecmaVersion: this.options.ecmaVersion}, this.input)
29      this.__branch.end = this.end
30      this.__branch.pos = this.pos
31      this.__branch.type = this.type
32      this.__branch.value = this.value
33      this.__branch.containsEsc = this.containsEsc
34      return this.__branch
35    }
36
37    parsePrivateClassElementName(element) {
38      element.computed = false
39      element.key = this.parsePrivateName()
40      if (element.key.name == "constructor") this.raise(element.key.start, "Classes may not have a private element named constructor")
41      const accept = {get: "set", set: "get"}[element.kind]
42      const privateBoundNames = this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1]
43      if (Object.prototype.hasOwnProperty.call(privateBoundNames, element.key.name) && privateBoundNames[element.key.name] !== accept) {
44        this.raise(element.start, "Duplicate private element")
45      }
46      privateBoundNames[element.key.name] = element.kind || true
47      delete this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1][element.key.name]
48      return element.key
49    }
50
51    parsePrivateName() {
52      const node = this.startNode()
53      node.name = this.value
54      this.next()
55      this.finishNode(node, "PrivateName")
56      if (this.options.allowReserved == "never") this.checkUnreserved(node)
57      return node
58    }
59
60    // Parse # token
61    getTokenFromCode(code) {
62      if (code === 35) {
63        ++this.pos
64        const word = this.readWord1()
65        return this.finishToken(this.privateNameToken, word)
66      }
67      return super.getTokenFromCode(code)
68    }
69
70    // Manage stacks and check for undeclared private names
71    parseClass(node, isStatement) {
72      this._privateBoundNamesStack = this._privateBoundNamesStack || []
73      const privateBoundNames = Object.create(this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1] || null)
74      this._privateBoundNamesStack.push(privateBoundNames)
75      this._unresolvedPrivateNamesStack = this._unresolvedPrivateNamesStack || []
76      const unresolvedPrivateNames = Object.create(null)
77      this._unresolvedPrivateNamesStack.push(unresolvedPrivateNames)
78      const _return = super.parseClass(node, isStatement)
79      this._privateBoundNamesStack.pop()
80      this._unresolvedPrivateNamesStack.pop()
81      if (!this._unresolvedPrivateNamesStack.length) {
82        const names = Object.keys(unresolvedPrivateNames)
83        if (names.length) {
84          names.sort((n1, n2) => unresolvedPrivateNames[n1] - unresolvedPrivateNames[n2])
85          this.raise(unresolvedPrivateNames[names[0]], "Usage of undeclared private name")
86        }
87      } else Object.assign(this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1], unresolvedPrivateNames)
88      return _return
89    }
90
91    // Parse private element access
92    parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow) {
93      if (!this.eat(tt.dot)) {
94        return super.parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow)
95      }
96      let node = this.startNodeAt(startPos, startLoc)
97      node.object = base
98      node.computed = false
99      if (this.type == this.privateNameToken) {
100        node.property = this.parsePrivateName()
101        if (!this._privateBoundNamesStack.length || !this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1][node.property.name]) {
102          this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1][node.property.name] = node.property.start
103        }
104      } else {
105        node.property = this.parseIdent(true)
106      }
107      return this.finishNode(node, "MemberExpression")
108    }
109
110    // Prohibit delete of private class elements
111    parseMaybeUnary(refDestructuringErrors, sawUnary) {
112      const _return = super.parseMaybeUnary(refDestructuringErrors, sawUnary)
113      if (_return.operator == "delete") {
114        if (_return.argument.type == "MemberExpression" && _return.argument.property.type == "PrivateName") {
115          this.raise(_return.start, "Private elements may not be deleted")
116        }
117      }
118      return _return
119    }
120  }
121  Parser.prototype.privateNameToken = new TokenType("privateName")
122  return Parser
123}
124