Reduce exprAllowed usage (#13431)

This commit is contained in:
Huáng Jùnliàng
2021-06-09 10:36:09 -04:00
committed by GitHub
parent 9252da782b
commit b9c1884a58
23 changed files with 714 additions and 305 deletions

View File

@@ -1,35 +1,27 @@
// @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
// The token context is used to track whether `}` matches
// a template quasi `${` or other tokens containing `{`:
// namely tt.braceL and tt.dollarBraceL
import { types as tt } from "./types";
export class TokContext {
constructor(token: string, isExpr?: boolean, preserveSpace?: boolean) {
constructor(token: string, preserveSpace?: boolean) {
this.token = token;
this.isExpr = !!isExpr;
this.preserveSpace = !!preserveSpace;
}
token: string;
isExpr: boolean;
preserveSpace: boolean;
}
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),
functionExpression: new TokContext("function", true),
functionStatement: new TokContext("function", false),
brace: new TokContext("{"),
templateQuasi: new TokContext("${"),
template: new TokContext("`", true),
};
// Token-specific context update code
@@ -42,93 +34,25 @@ export const types: {
// 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;
tt.braceR.updateContext = context => {
if (context.length > 1) {
context.pop();
}
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;
};
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.braceStatement
)
) {
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 */
tt.braceL.updateContext = tt.braceHashL.updateContext = context => {
context.push(types.brace);
};
tt.dollarBraceL.updateContext = context => {
context.push(types.templateQuasi);
};
tt.backQuote.updateContext = context => {
if (context[context.length - 1] === types.template) {
context.pop();
} else {
context.push(types.template);
}
};

View File

@@ -190,7 +190,6 @@ export default class Tokenizer extends ParserErrors {
end: state.end,
lastTokEnd: state.end,
context: [this.curContext()],
exprAllowed: state.exprAllowed,
inType: state.inType,
};
}
@@ -525,16 +524,9 @@ export default class Tokenizer extends ParserErrors {
}
readToken_slash(): void {
// '/'
if (this.state.exprAllowed && !this.state.inType) {
++this.state.pos;
this.readRegexp();
return;
}
const next = this.input.charCodeAt(this.state.pos + 1);
if (next === charCodes.equalsTo) {
this.finishOp(tt.assign, 2);
this.finishOp(tt.slashAssign, 2);
} else {
this.finishOp(tt.slash, 1);
}
@@ -565,7 +557,6 @@ export default class Tokenizer extends ParserErrors {
let type = code === charCodes.asterisk ? tt.star : tt.modulo;
let width = 1;
let next = this.input.charCodeAt(this.state.pos + 1);
const exprAllowed = this.state.exprAllowed;
// Exponentiation operator **
if (code === charCodes.asterisk && next === charCodes.asterisk) {
@@ -574,7 +565,7 @@ export default class Tokenizer extends ParserErrors {
type = tt.exponent;
}
if (next === charCodes.equalsTo && !exprAllowed) {
if (next === charCodes.equalsTo && !this.state.inType) {
width++;
type = tt.assign;
}
@@ -983,7 +974,7 @@ export default class Tokenizer extends ParserErrors {
}
readRegexp(): void {
const start = this.state.pos;
const start = this.state.start + 1;
let escaped, inClass;
for (;;) {
if (this.state.pos >= this.length) {
@@ -1565,68 +1556,9 @@ export default class Tokenizer extends ParserErrors {
}
}
braceIsBlock(prevType: TokenType): boolean {
const parent = this.curContext();
if (parent === ct.functionExpression || parent === ct.functionStatement) {
return true;
}
if (
prevType === tt.colon &&
(parent === ct.braceStatement || parent === ct.braceExpression)
) {
return !parent.isExpr;
}
// The check for `tt.name && exprAllowed` detects whether we are
// after a `yield` or `of` construct. See the `updateContext` for
// `tt.name`.
if (
prevType === tt._return ||
(prevType === tt.name && this.state.exprAllowed)
) {
return this.hasPrecedingLineBreak();
}
if (
prevType === tt._else ||
prevType === tt.semi ||
prevType === tt.eof ||
prevType === tt.parenR ||
prevType === tt.arrow
) {
return true;
}
if (prevType === tt.braceL) {
return parent === ct.braceStatement;
}
if (
prevType === tt._var ||
prevType === tt._const ||
prevType === tt.name
) {
return false;
}
if (prevType === tt.relational) {
// `class C<T> { ... }`
return true;
}
return !this.state.exprAllowed;
}
// the prevType is required by the jsx plugin
// eslint-disable-next-line no-unused-vars
updateContext(prevType: TokenType): void {
const type = this.state.type;
let update;
if (type.keyword && (prevType === tt.dot || prevType === tt.questionDot)) {
this.state.exprAllowed = false;
} else if ((update = type.updateContext)) {
update.call(this, prevType);
} else {
this.state.exprAllowed = type.beforeExpr;
}
this.state.type.updateContext?.(this.state.context);
}
}

View File

@@ -130,7 +130,7 @@ export default class State {
// The context stack is used to superficially track syntactic
// context to predict whether a regular expression is allowed in a
// given position.
context: Array<TokContext> = [ct.braceStatement];
context: Array<TokContext> = [ct.brace];
exprAllowed: boolean = true;
// Used to signal to callers of `readWord1` whether the word
@@ -181,7 +181,6 @@ export type LookaheadState = {
type: TokenType,
start: number,
end: number,
/* Used only in readSlashToken */
exprAllowed: boolean,
/* Used only in readToken_mult_modulo */
inType: boolean,
};

View File

@@ -1,5 +1,5 @@
// @flow
import type { TokContext } from "./context";
// ## Token types
// The assignment of fine-grained, information-carrying type objects
@@ -9,10 +9,9 @@
// All token type variables start with an underscore, to make them
// easy to recognize.
// The `beforeExpr` property is used to disambiguate between regular
// expressions and divisions. It is set on all token types that can
// be followed by an expression (thus, a slash after them would be a
// regular expression).
// The `beforeExpr` property is used to disambiguate between 1) binary
// expression (<) and JSX Tag start (<name>); 2) object literal and JSX
// texts. It is set on the `updateContext` function in the JSX plugin.
// The `startsExpr` property is used to determine whether an expression
// may be the “argument” subexpression of a `yield` expression or
@@ -53,7 +52,7 @@ export class TokenType {
prefix: boolean;
postfix: boolean;
binop: ?number;
updateContext: ?(prevType: TokenType) => void;
updateContext: ?(context: Array<TokContext>) => void;
constructor(label: string, conf: TokenOptions = {}) {
this.label = label;
@@ -102,7 +101,7 @@ export const types: { [name: string]: TokenType } = {
braceL: new TokenType("{", { beforeExpr, startsExpr }),
braceBarL: new TokenType("{|", { beforeExpr, startsExpr }),
braceHashL: new TokenType("#{", { beforeExpr, startsExpr }),
braceR: new TokenType("}"),
braceR: new TokenType("}", { beforeExpr }),
braceBarR: new TokenType("|}"),
parenL: new TokenType("(", { beforeExpr, startsExpr }),
parenR: new TokenType(")"),
@@ -140,6 +139,7 @@ export const types: { [name: string]: TokenType } = {
eq: new TokenType("=", { beforeExpr, isAssign }),
assign: new TokenType("_=", { beforeExpr, isAssign }),
slashAssign: new TokenType("_=", { beforeExpr, isAssign }),
incDec: new TokenType("++/--", { prefix, postfix, startsExpr }),
bang: new TokenType("!", { beforeExpr, prefix, startsExpr }),
tilde: new TokenType("~", { beforeExpr, prefix, startsExpr }),