* fix: disallow expression after `}` is consumed in parseObjectLike * refactor: avoid accessing this.prodParam in context update
146 lines
4.0 KiB
JavaScript
146 lines
4.0 KiB
JavaScript
// @flow
|
|
|
|
// The algorithm used to determine whether a regexp can appear at a
|
|
// given point in the program is loosely based on sweet.js' approach.
|
|
// See https://github.com/mozilla/sweet.js/wiki/design
|
|
|
|
import { types as tt } from "./types";
|
|
|
|
export class TokContext {
|
|
constructor(
|
|
token: string,
|
|
isExpr?: boolean,
|
|
preserveSpace?: boolean,
|
|
override?: ?Function, // Takes a Tokenizer as a this-parameter, and returns void.
|
|
) {
|
|
this.token = token;
|
|
this.isExpr = !!isExpr;
|
|
this.preserveSpace = !!preserveSpace;
|
|
this.override = override;
|
|
}
|
|
|
|
token: string;
|
|
isExpr: boolean;
|
|
preserveSpace: boolean;
|
|
override: ?Function;
|
|
}
|
|
|
|
export const types: {
|
|
[key: string]: TokContext,
|
|
} = {
|
|
braceStatement: new TokContext("{", false),
|
|
braceExpression: new TokContext("{", true),
|
|
recordExpression: new TokContext("#{", true),
|
|
templateQuasi: new TokContext("${", false),
|
|
parenStatement: new TokContext("(", false),
|
|
parenExpression: new TokContext("(", true),
|
|
template: new TokContext("`", true, true, p => p.readTmplToken()),
|
|
functionExpression: new TokContext("function", true),
|
|
functionStatement: new TokContext("function", false),
|
|
};
|
|
|
|
// Token-specific context update code
|
|
// Note that we should avoid accessing `this.prodParam` in context update,
|
|
// because it is executed immediately when last token is consumed, which may be
|
|
// before `this.prodParam` is updated. e.g.
|
|
// ```
|
|
// function *g() { () => yield / 2 }
|
|
// ```
|
|
// When `=>` is eaten, the context update of `yield` is executed, however,
|
|
// `this.prodParam` still has `[Yield]` production because it is not yet updated
|
|
|
|
tt.parenR.updateContext = tt.braceR.updateContext = function () {
|
|
if (this.state.context.length === 1) {
|
|
this.state.exprAllowed = true;
|
|
return;
|
|
}
|
|
|
|
let out = this.state.context.pop();
|
|
if (out === types.braceStatement && this.curContext().token === "function") {
|
|
out = this.state.context.pop();
|
|
}
|
|
|
|
this.state.exprAllowed = !out.isExpr;
|
|
};
|
|
|
|
tt.name.updateContext = function (prevType) {
|
|
let allowed = false;
|
|
if (prevType !== tt.dot) {
|
|
if (
|
|
this.state.value === "of" &&
|
|
!this.state.exprAllowed &&
|
|
prevType !== tt._function &&
|
|
prevType !== tt._class
|
|
) {
|
|
allowed = true;
|
|
}
|
|
}
|
|
this.state.exprAllowed = allowed;
|
|
|
|
if (this.state.isIterator) {
|
|
this.state.isIterator = false;
|
|
}
|
|
};
|
|
|
|
tt.braceL.updateContext = function (prevType) {
|
|
this.state.context.push(
|
|
this.braceIsBlock(prevType) ? types.braceStatement : types.braceExpression,
|
|
);
|
|
this.state.exprAllowed = true;
|
|
};
|
|
|
|
tt.dollarBraceL.updateContext = function () {
|
|
this.state.context.push(types.templateQuasi);
|
|
this.state.exprAllowed = true;
|
|
};
|
|
|
|
tt.parenL.updateContext = function (prevType) {
|
|
const statementParens =
|
|
prevType === tt._if ||
|
|
prevType === tt._for ||
|
|
prevType === tt._with ||
|
|
prevType === tt._while;
|
|
this.state.context.push(
|
|
statementParens ? types.parenStatement : types.parenExpression,
|
|
);
|
|
this.state.exprAllowed = true;
|
|
};
|
|
|
|
tt.incDec.updateContext = function () {
|
|
// tokExprAllowed stays unchanged
|
|
};
|
|
|
|
tt._function.updateContext = tt._class.updateContext = function (prevType) {
|
|
if (
|
|
prevType.beforeExpr &&
|
|
prevType !== tt.semi &&
|
|
prevType !== tt._else &&
|
|
!(prevType === tt._return && this.hasPrecedingLineBreak()) &&
|
|
!(
|
|
(prevType === tt.colon || prevType === tt.braceL) &&
|
|
this.curContext() === types.b_stat
|
|
)
|
|
) {
|
|
this.state.context.push(types.functionExpression);
|
|
} else {
|
|
this.state.context.push(types.functionStatement);
|
|
}
|
|
|
|
this.state.exprAllowed = false;
|
|
};
|
|
|
|
tt.backQuote.updateContext = function () {
|
|
if (this.curContext() === types.template) {
|
|
this.state.context.pop();
|
|
} else {
|
|
this.state.context.push(types.template);
|
|
}
|
|
this.state.exprAllowed = false;
|
|
};
|
|
|
|
// we don't need to update context for tt.braceBarL because we do not pop context for tt.braceBarR
|
|
tt.braceHashL.updateContext = function () {
|
|
this.state.context.push(types.recordExpression);
|
|
this.state.exprAllowed = true; /* tt.braceHashL.beforeExpr */
|
|
};
|