/
expression-scope.js
228 lines (199 loc) 路 7.76 KB
/
expression-scope.js
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// @flow
import type { ErrorTemplate, raiseFunction } from "../parser/error";
/*:: declare var invariant; */
/**
* @module util/expression-scope
ExpressionScope is used to track declaration errors in these ambiguous patterns:
- CoverParenthesizedExpressionAndArrowParameterList
e.g. we don't know if `({ x })` is an parenthesized expression or an
arrow function parameters until we see an `=>` after `)`.
- CoverCallExpressionAndAsyncArrowHead
e.g. we don't know if `async({ x })` is a call expression or an async arrow
function parameters until we see an `=>` after `)`
The following declaration errors (@see parser/error-message) will be recorded in
some expression scopes and thrown later when we know what the ambigous pattern is
- AwaitBindingIdentifier
- AwaitExpressionFormalParameter
- YieldInParameter
- InvalidParenthesizedAssignment when parenthesized is an identifier
There are four different expression scope
- Expression
A general scope that represents program / function body / static block. No errors
will be recorded nor thrown in this scope.
- MaybeArrowParameterDeclaration
A scope that represents ambiguous arrow head e.g. `(x)`. Errors will be recorded
alongside parent scopes and thrown when `ExpressionScopeHandler#validateAsPattern`
is called.
- MaybeAsyncArrowParameterDeclaration
A scope that represents ambiguous async arrow head e.g. `async(x)`. Errors will
be recorded alongside parent scopes and thrown when
`ExpressionScopeHandler#validateAsPattern` is called.
- ParameterDeclaration
A scope that represents unambiguous function parameters `function(x)`. Errors
recorded in this scope will be thrown immediately. No errors will be recorded in
this scope.
// @see {@link https://docs.google.com/document/d/1FAvEp9EUK-G8kHfDIEo_385Hs2SUBCYbJ5H-NnLvq8M|V8 Expression Scope design docs}
*/
const kExpression = 0,
kMaybeArrowParameterDeclaration = 1,
kMaybeAsyncArrowParameterDeclaration = 2,
kParameterDeclaration = 3;
type ExpressionScopeType = 0 | 1 | 2 | 3;
class ExpressionScope {
type: ExpressionScopeType;
constructor(type: ExpressionScopeType = kExpression) {
this.type = type;
}
canBeArrowParameterDeclaration() {
return (
this.type === kMaybeAsyncArrowParameterDeclaration ||
this.type === kMaybeArrowParameterDeclaration
);
}
isCertainlyParameterDeclaration() {
return this.type === kParameterDeclaration;
}
}
class ArrowHeadParsingScope extends ExpressionScope {
errors: Map</* pos */ number, /* message */ ErrorTemplate> = new Map();
constructor(type: 1 | 2) {
super(type);
}
recordDeclarationError(pos: number, template: ErrorTemplate) {
this.errors.set(pos, template);
}
clearDeclarationError(pos: number) {
this.errors.delete(pos);
}
iterateErrors(iterator: (template: ErrorTemplate, pos: number) => void) {
this.errors.forEach(iterator);
}
}
export default class ExpressionScopeHandler {
stack: Array<ExpressionScope> = [new ExpressionScope()];
declare raise: raiseFunction;
constructor(raise: raiseFunction) {
this.raise = raise;
}
enter(scope: ExpressionScope) {
this.stack.push(scope);
}
exit() {
this.stack.pop();
}
/**
* Record likely parameter initializer errors
*
* When current scope is a ParameterDeclaration, the error will be thrown immediately,
* otherwise it will be recorded to any ancestry MaybeArrowParameterDeclaration and
* MaybeAsyncArrowParameterDeclaration scope until an Expression scope is seen.
* @param {number} pos Error position
* @param {ErrorTemplate} template Error template
* @memberof ExpressionScopeHandler
*/
recordParameterInitializerError(pos: number, template: ErrorTemplate): void {
const { stack } = this;
let i = stack.length - 1;
let scope: ExpressionScope = stack[i];
while (!scope.isCertainlyParameterDeclaration()) {
if (scope.canBeArrowParameterDeclaration()) {
/*:: invariant(scope instanceof ArrowHeadParsingScope) */
scope.recordDeclarationError(pos, template);
} else {
/*:: invariant(scope.type == kExpression) */
// Type-Expression is the boundary where initializer error can populate to
return;
}
scope = stack[--i];
}
/* eslint-disable @babel/development-internal/dry-error-messages */
this.raise(pos, template);
}
/**
* Record parenthesized identifier errors
*
* A parenthesized identifier in LHS can be ambiguous because the assignment
* can be transformed to an assignable later, but not vice versa:
* For example, in `([(a) = []] = []) => {}`, we think `(a) = []` is an LHS in `[(a) = []]`,
* an LHS within `[(a) = []] = []`. However the LHS chain is then transformed by toAssignable,
* and we should throw assignment `(a)`, which is only valid in LHS. Hence we record the
* location of parenthesized `(a)` to current scope if it is one of MaybeArrowParameterDeclaration
* and MaybeAsyncArrowParameterDeclaration
*
* Unlike `recordParameterInitializerError`, we don't record to ancestry scope because we
* validate arrow head parsing scope before exit, and then the LHS will be unambiguous:
* For example, in `( x = ( [(a) = []] = [] ) ) => {}`, we should not record `(a)` in `( x = ... ) =>`
* arrow scope because when we finish parsing `( [(a) = []] = [] )`, it is an unambiguous assignment
* expression and can not be cast to pattern
* @param {number} pos
* @param {ErrorTemplate} template
* @returns {void}
* @memberof ExpressionScopeHandler
*/
recordParenthesizedIdentifierError(
pos: number,
template: ErrorTemplate,
): void {
const { stack } = this;
const scope: ExpressionScope = stack[stack.length - 1];
if (scope.isCertainlyParameterDeclaration()) {
this.raise(pos, template);
} else if (scope.canBeArrowParameterDeclaration()) {
/*:: invariant(scope instanceof ArrowHeadParsingScope) */
scope.recordDeclarationError(pos, template);
} else {
return;
}
}
/**
* Record likely async arrow parameter errors
*
* Errors will be recorded to any ancestry MaybeAsyncArrowParameterDeclaration
* scope until an Expression scope is seen.
* @param {number} pos
* @param {ErrorTemplate} template
* @memberof ExpressionScopeHandler
*/
recordAsyncArrowParametersError(pos: number, template: ErrorTemplate): void {
const { stack } = this;
let i = stack.length - 1;
let scope: ExpressionScope = stack[i];
while (scope.canBeArrowParameterDeclaration()) {
if (scope.type === kMaybeAsyncArrowParameterDeclaration) {
/*:: invariant(scope instanceof ArrowHeadParsingScope) */
scope.recordDeclarationError(pos, template);
}
scope = stack[--i];
}
}
validateAsPattern(): void {
const { stack } = this;
const currentScope = stack[stack.length - 1];
if (!currentScope.canBeArrowParameterDeclaration()) return;
/*:: invariant(currentScope instanceof ArrowHeadParsingScope) */
currentScope.iterateErrors((template, pos) => {
/* eslint-disable @babel/development-internal/dry-error-messages */
this.raise(pos, template);
// iterate from parent scope
let i = stack.length - 2;
let scope = stack[i];
while (scope.canBeArrowParameterDeclaration()) {
/*:: invariant(scope instanceof ArrowHeadParsingScope) */
scope.clearDeclarationError(pos);
scope = stack[--i];
}
});
}
}
export function newParameterDeclarationScope() {
return new ExpressionScope(kParameterDeclaration);
}
export function newArrowHeadScope() {
return new ArrowHeadParsingScope(kMaybeArrowParameterDeclaration);
}
export function newAsyncArrowScope() {
return new ArrowHeadParsingScope(kMaybeAsyncArrowParameterDeclaration);
}
export function newExpressionScope() {
return new ExpressionScope();
}