Upgrade to https://github.com/acornjs/acorn.git commit 84eda6bf
[jst.git] / src / state.js
1 import {reservedWords, keywords} from "./identifier.js"
2 import {types as tt} from "./tokentype.js"
3 import {lineBreak} from "./whitespace.js"
4 import {getOptions} from "./options.js"
5 import {wordsRegexp} from "./util.js"
6 import {SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR, SCOPE_SUPER, SCOPE_DIRECT_SUPER, SCOPE_CLASS_STATIC_BLOCK} from "./scopeflags.js"
7
8 export class Parser {
9   constructor(options, input, startPos) {
10     this.options = options = getOptions(options)
11     this.sourceFile = options.sourceFile
12     this.keywords = wordsRegexp(keywords[options.ecmaVersion >= 6 ? 6 : options.sourceType === "module" ? "5module" : 5])
13     let reserved = ""
14     if (options.allowReserved !== true) {
15       reserved = reservedWords[options.ecmaVersion >= 6 ? 6 : options.ecmaVersion === 5 ? 5 : 3]
16       if (options.sourceType === "module") reserved += " await"
17     }
18     this.reservedWords = wordsRegexp(reserved)
19     let reservedStrict = (reserved ? reserved + " " : "") + reservedWords.strict
20     this.reservedWordsStrict = wordsRegexp(reservedStrict)
21     this.reservedWordsStrictBind = wordsRegexp(reservedStrict + " " + reservedWords.strictBind)
22     this.input = String(input)
23
24     // Used to signal to callers of `readWord1` whether the word
25     // contained any escape sequences. This is needed because words with
26     // escape sequences must not be interpreted as keywords.
27     this.containsEsc = false
28
29     // Set up token state
30
31     // The current position of the tokenizer in the input.
32     if (startPos) {
33       this.pos = startPos
34       this.lineStart = this.input.lastIndexOf("\n", startPos - 1) + 1
35       this.curLine = this.input.slice(0, this.lineStart).split(lineBreak).length
36     } else {
37       this.pos = this.lineStart = 0
38       this.curLine = 1
39     }
40
41     // Properties of the current token:
42     // Its type
43     this.type = tt.eof
44     // For tokens that include more information than their type, the value
45     this.value = null
46     // Its start and end offset
47     this.start = this.end = this.pos
48     // And, if locations are used, the {line, column} object
49     // corresponding to those offsets
50     this.startLoc = this.endLoc = this.curPosition()
51
52     // Position information for the previous token
53     this.lastTokEndLoc = this.lastTokStartLoc = null
54     this.lastTokStart = this.lastTokEnd = this.pos
55
56     // The context stack is used to superficially track syntactic
57     // context to predict whether a regular expression is allowed in a
58     // given position.
59     this.context = this.initialContext()
60     this.exprAllowed = true
61
62     // Figure out if it's a module code.
63     this.inModule = options.sourceType === "module"
64     this.strict = this.inModule || this.strictDirective(this.pos)
65
66     // Used to signify the start of a potential arrow function
67     this.potentialArrowAt = -1
68     this.potentialArrowInForAwait = false
69
70     // Positions to delayed-check that yield/await does not exist in default parameters.
71     this.yieldPos = this.awaitPos = this.awaitIdentPos = 0
72     // Labels in scope.
73     this.labels = []
74     // Thus-far undefined exports.
75     this.undefinedExports = Object.create(null)
76
77     // If enabled, skip leading hashbang line.
78     if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === "#!")
79       this.skipLineComment(2)
80
81     // Scope tracking for duplicate variable names (see scope.js)
82     this.scopeStack = []
83     this.enterScope(SCOPE_TOP)
84
85     // For RegExp validation
86     this.regexpState = null
87
88     // The stack of private names.
89     // Each element has two properties: 'declared' and 'used'.
90     // When it exited from the outermost class definition, all used private names must be declared.
91     this.privateNameStack = []
92   }
93
94   parse() {
95     let node = this.options.program || this.startNode()
96     this.nextToken()
97     return this.parseTopLevel(node)
98   }
99
100   get inFunction() { return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0 }
101
102   get inGenerator() { return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0 && !this.currentVarScope().inClassFieldInit }
103
104   get inAsync() { return (this.currentVarScope().flags & SCOPE_ASYNC) > 0 && !this.currentVarScope().inClassFieldInit }
105
106   get canAwait() {
107     for (let i = this.scopeStack.length - 1; i >= 0; i--) {
108       let scope = this.scopeStack[i]
109       if (scope.inClassFieldInit || scope.flags & SCOPE_CLASS_STATIC_BLOCK) return false
110       if (scope.flags & SCOPE_FUNCTION) return (scope.flags & SCOPE_ASYNC) > 0
111     }
112     return (this.inModule && this.options.ecmaVersion >= 13) || this.options.allowAwaitOutsideFunction
113   }
114
115   get allowSuper() {
116     const {flags, inClassFieldInit} = this.currentThisScope()
117     return (flags & SCOPE_SUPER) > 0 || inClassFieldInit || this.options.allowSuperOutsideMethod
118   }
119
120   get allowDirectSuper() { return (this.currentThisScope().flags & SCOPE_DIRECT_SUPER) > 0 }
121
122   get treatFunctionsAsVar() { return this.treatFunctionsAsVarInScope(this.currentScope()) }
123
124   get allowNewDotTarget() {
125     const {flags, inClassFieldInit} = this.currentThisScope()
126     return (flags & (SCOPE_FUNCTION | SCOPE_CLASS_STATIC_BLOCK)) > 0 || inClassFieldInit
127   }
128
129   get inClassStaticBlock() {
130     return (this.currentVarScope().flags & SCOPE_CLASS_STATIC_BLOCK) > 0
131   }
132
133   static extend(...plugins) {
134     let cls = this
135     for (let i = 0; i < plugins.length; i++) cls = plugins[i](cls)
136     return cls
137   }
138
139   static parse(input, options) {
140     return new this(options, input).parse()
141   }
142
143   static parseExpressionAt(input, pos, options) {
144     let parser = new this(options, input, pos)
145     parser.nextToken()
146     return parser.parseExpression()
147   }
148
149   static tokenizer(input, options) {
150     return new this(options, input)
151   }
152 }