* fix: disallow all parenthesized pattern except parsing LHS * fix: forStatement requires LHS * simplify toAssignable * add more test cases * fix: pass through isLHS on object property and assignment expression * fix: record parenthesized identifier error for LHS * remove duplicated skipped tests * fix: do not record errors on ancestry arrow head * Update packages/babel-parser/src/util/expression-scope.js Co-authored-by: Brian Ng <bng412@gmail.com> Co-authored-by: Brian Ng <bng412@gmail.com>
226 lines
7.7 KiB
JavaScript
226 lines
7.7 KiB
JavaScript
// @flow
|
|
|
|
/*:: 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;
|
|
|
|
type raiseFunction = (number, string, ...any) => void;
|
|
|
|
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 */ string> = new Map();
|
|
constructor(type: 1 | 2) {
|
|
super(type);
|
|
}
|
|
recordDeclarationError(pos: number, message: string) {
|
|
this.errors.set(pos, message);
|
|
}
|
|
clearDeclarationError(pos: number) {
|
|
this.errors.delete(pos);
|
|
}
|
|
iterateErrors(iterator: (message: string, 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 {string} message Error message
|
|
* @memberof ExpressionScopeHandler
|
|
*/
|
|
recordParameterInitializerError(pos: number, message: string): 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, message);
|
|
} 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, message);
|
|
}
|
|
|
|
/**
|
|
* 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 {string} message
|
|
* @returns {void}
|
|
* @memberof ExpressionScopeHandler
|
|
*/
|
|
recordParenthesizedIdentifierError(pos: number, message: string): void {
|
|
const { stack } = this;
|
|
const scope: ExpressionScope = stack[stack.length - 1];
|
|
if (scope.isCertainlyParameterDeclaration()) {
|
|
this.raise(pos, message);
|
|
} else if (scope.canBeArrowParameterDeclaration()) {
|
|
/*:: invariant(scope instanceof ArrowHeadParsingScope) */
|
|
scope.recordDeclarationError(pos, message);
|
|
} 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 {string} message
|
|
* @memberof ExpressionScopeHandler
|
|
*/
|
|
recordAsyncArrowParametersError(pos: number, message: string): 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, message);
|
|
}
|
|
scope = stack[--i];
|
|
}
|
|
}
|
|
|
|
validateAsPattern(): void {
|
|
const { stack } = this;
|
|
const currentScope = stack[stack.length - 1];
|
|
if (!currentScope.canBeArrowParameterDeclaration()) return;
|
|
/*:: invariant(currentScope instanceof ArrowHeadParsingScope) */
|
|
currentScope.iterateErrors((message, pos) => {
|
|
/* eslint-disable @babel/development-internal/dry-error-messages */
|
|
this.raise(pos, message);
|
|
// 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();
|
|
}
|