Parser refactoring (#11871)

* refactor: parseMaybeUnary => parseUnary

* refactor: extract shouldExitDescending method

* refactor: add parseUpdate

* refactor: avoid comparing with hardcoded token value

* refactor: add ParseNewOrNewTarget

* refactor: add parseCoverCallAndAsyncArrowHead

* add parseBind

* refactor: polish parseTaggedTemplateExpression interface

* refactor: add parseMember method

* refactor: add parseSuper method

* refactor: add parseAsyncArrowUnaryFunction method

* fix: disallow line break before async binding arrow

* refactor: simplify tt.name logic

* refactor: add parseDo method

* refactor: misc

* refactor: rename parseObjectMember by parsePropertyDefinition

* refactor: unify set/get/async keyword parsing in ObjectMethod

* refactor: misc

* refactor: add parseArrayLike method

* refactor: move fsharp epilogure and prologue inside parseObjectLike

* fixup

* refactor: rename parseFunctionExpression to parseFunctionOrFunctionSent

* refactor: remove redundant logic

* refactor: rename parseClassPropertyName by parseClassElementName

* refactor: avoid unecessary lookahead when parsing tt._export

* fix: export-default-from should support escaped async as export binding

* address review comments

* parseUnary -> parseMaybeUnary
This commit is contained in:
Huáng Jùnliàng 2020-07-31 20:36:04 -04:00 committed by GitHub
parent ad60153a98
commit a4ebe29b3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 620 additions and 430 deletions

View File

@ -80,6 +80,7 @@ export const ErrorMessages = Object.freeze({
LabelRedeclaration: "Label '%0' is already declared", LabelRedeclaration: "Label '%0' is already declared",
LetInLexicalBinding: LetInLexicalBinding:
"'let' is not allowed to be used as a name in 'let' or 'const' declarations.", "'let' is not allowed to be used as a name in 'let' or 'const' declarations.",
LineTerminatorBeforeArrow: "No line break is allowed before '=>'",
MalformedRegExpFlags: "Invalid regular expression flag", MalformedRegExpFlags: "Invalid regular expression flag",
MissingClassName: "A class name is required", MissingClassName: "A class name is required",
MissingEqInAssignment: MissingEqInAssignment:

File diff suppressed because it is too large Load Diff

View File

@ -235,15 +235,18 @@ export default class LValParser extends NodeUtils {
return this.finishNode(node, "SpreadElement"); return this.finishNode(node, "SpreadElement");
} }
// https://tc39.es/ecma262/#prod-BindingRestProperty
// https://tc39.es/ecma262/#prod-BindingRestElement
parseRestBinding(): RestElement { parseRestBinding(): RestElement {
const node = this.startNode(); const node = this.startNode();
this.next(); this.next(); // eat `...`
node.argument = this.parseBindingAtom(); node.argument = this.parseBindingAtom();
return this.finishNode(node, "RestElement"); return this.finishNode(node, "RestElement");
} }
// Parses lvalue (assignable) atom. // Parses lvalue (assignable) atom.
parseBindingAtom(): Pattern { parseBindingAtom(): Pattern {
// https://tc39.es/ecma262/#prod-BindingPattern
switch (this.state.type) { switch (this.state.type) {
case tt.bracketL: { case tt.bracketL: {
const node = this.startNode(); const node = this.startNode();
@ -257,12 +260,14 @@ export default class LValParser extends NodeUtils {
} }
case tt.braceL: case tt.braceL:
return this.parseObj(tt.braceR, true); return this.parseObjectLike(tt.braceR, true);
} }
// https://tc39.es/ecma262/#prod-BindingIdentifier
return this.parseIdentifier(); return this.parseIdentifier();
} }
// https://tc39.es/ecma262/#prod-BindingElementList
parseBindingList( parseBindingList(
close: TokenType, close: TokenType,
closeCharCode: $Values<typeof charCodes>, closeCharCode: $Values<typeof charCodes>,
@ -292,6 +297,7 @@ export default class LValParser extends NodeUtils {
if (this.match(tt.at) && this.hasPlugin("decorators")) { if (this.match(tt.at) && this.hasPlugin("decorators")) {
this.raise(this.state.start, Errors.UnsupportedParameterDecorator); this.raise(this.state.start, Errors.UnsupportedParameterDecorator);
} }
// invariant: hasPlugin("decorators-legacy")
while (this.match(tt.at)) { while (this.match(tt.at)) {
decorators.push(this.parseDecorator()); decorators.push(this.parseDecorator());
} }
@ -314,20 +320,22 @@ export default class LValParser extends NodeUtils {
return elt; return elt;
} }
// Used by flow/typescript plugin to add type annotations to binding elements
parseAssignableListItemTypes(param: Pattern): Pattern { parseAssignableListItemTypes(param: Pattern): Pattern {
return param; return param;
} }
// Parses assignment pattern around given atom if possible. // Parses assignment pattern around given atom if possible.
// https://tc39.es/ecma262/#prod-BindingElement
parseMaybeDefault( parseMaybeDefault(
startPos?: ?number, startPos?: ?number,
startLoc?: ?Position, startLoc?: ?Position,
left?: ?Pattern, left?: ?Pattern,
): Pattern { ): Pattern {
startLoc = startLoc || this.state.startLoc; startLoc = startLoc ?? this.state.startLoc;
startPos = startPos || this.state.start; startPos = startPos ?? this.state.start;
left = left || this.parseBindingAtom(); // $FlowIgnore
left = left ?? this.parseBindingAtom();
if (!this.eat(tt.eq)) return left; if (!this.eat(tt.eq)) return left;
const node = this.startNodeAt(startPos, startLoc); const node = this.startNodeAt(startPos, startLoc);

View File

@ -141,7 +141,9 @@ export default class StatementParser extends ExpressionParser {
// regular expression literal. This is to handle cases like // regular expression literal. This is to handle cases like
// `if (foo) /blah/.exec(foo)`, where looking at the previous token // `if (foo) /blah/.exec(foo)`, where looking at the previous token
// does not help. // does not help.
// https://tc39.es/ecma262/#prod-Statement
// ImportDeclaration and ExportDeclaration are also handled here so we can throw recoverable errors
// when they are not at the top level
parseStatement(context: ?string, topLevel?: boolean): N.Statement { parseStatement(context: ?string, topLevel?: boolean): N.Statement {
if (this.match(tt.at)) { if (this.match(tt.at)) {
this.parseDecorators(true); this.parseDecorators(true);
@ -216,21 +218,22 @@ export default class StatementParser extends ExpressionParser {
return this.parseBlock(); return this.parseBlock();
case tt.semi: case tt.semi:
return this.parseEmptyStatement(node); return this.parseEmptyStatement(node);
case tt._export:
case tt._import: { case tt._import: {
const nextTokenCharCode = this.lookaheadCharCode(); const nextTokenCharCode = this.lookaheadCharCode();
if ( if (
nextTokenCharCode === charCodes.leftParenthesis || nextTokenCharCode === charCodes.leftParenthesis || // import()
nextTokenCharCode === charCodes.dot nextTokenCharCode === charCodes.dot // import.meta
) { ) {
break; break;
} }
}
// fall through
case tt._export: {
if (!this.options.allowImportExportEverywhere && !topLevel) { if (!this.options.allowImportExportEverywhere && !topLevel) {
this.raise(this.state.start, Errors.UnexpectedImportExport); this.raise(this.state.start, Errors.UnexpectedImportExport);
} }
this.next(); this.next(); // eat `import`/`export`
let result; let result;
if (starttype === tt._import) { if (starttype === tt._import) {
@ -847,6 +850,8 @@ export default class StatementParser extends ExpressionParser {
} }
// Undefined directives means that directives are not allowed. // Undefined directives means that directives are not allowed.
// https://tc39.es/ecma262/#prod-Block
// https://tc39.es/ecma262/#prod-ModuleBody
parseBlockOrModuleBlockBody( parseBlockOrModuleBlockBody(
body: N.Statement[], body: N.Statement[],
directives: ?(N.Directive[]), directives: ?(N.Directive[]),
@ -1161,10 +1166,9 @@ export default class StatementParser extends ExpressionParser {
this.parseClassId(node, isStatement, optionalId); this.parseClassId(node, isStatement, optionalId);
this.parseClassSuper(node); this.parseClassSuper(node);
// this.state.strict is restored in parseClassBody
node.body = this.parseClassBody(!!node.superClass, oldStrict); node.body = this.parseClassBody(!!node.superClass, oldStrict);
this.state.strict = oldStrict;
return this.finishNode( return this.finishNode(
node, node,
isStatement ? "ClassDeclaration" : "ClassExpression", isStatement ? "ClassDeclaration" : "ClassExpression",
@ -1188,6 +1192,7 @@ export default class StatementParser extends ExpressionParser {
); );
} }
// https://tc39.es/ecma262/#prod-ClassBody
parseClassBody( parseClassBody(
constructorAllowsSuper: boolean, constructorAllowsSuper: boolean,
oldStrict?: boolean, oldStrict?: boolean,
@ -1238,11 +1243,9 @@ export default class StatementParser extends ExpressionParser {
} }
}); });
if (!oldStrict) { this.state.strict = oldStrict;
this.state.strict = false;
}
this.next(); this.next(); // eat `}`
if (decorators.length) { if (decorators.length) {
throw this.raise(this.state.start, Errors.TrailingDecorator); throw this.raise(this.state.start, Errors.TrailingDecorator);
@ -1253,13 +1256,26 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(classBody, "ClassBody"); return this.finishNode(classBody, "ClassBody");
} }
// Check grammar production:
// IdentifierName *_opt ClassElementName
// It is used in `parsePropertyDefinition` to detect AsyncMethod and Accessors
maybeClassModifier(prop: N.ObjectProperty): boolean {
return (
!prop.computed &&
prop.key.type === "Identifier" &&
(this.isLiteralPropertyName() ||
this.match(tt.bracketL) ||
this.match(tt.star) ||
this.match(tt.hash))
);
}
// returns true if the current identifier is a method/field name, // returns true if the current identifier is a method/field name,
// false if it is a modifier // false if it is a modifier
parseClassMemberFromModifier( parseClassMemberFromModifier(
classBody: N.ClassBody, classBody: N.ClassBody,
member: N.ClassMember, member: N.ClassMember,
): boolean { ): boolean {
const containsEsc = this.state.containsEsc;
const key = this.parseIdentifier(true); // eats the modifier const key = this.parseIdentifier(true); // eats the modifier
if (this.isClassMethod()) { if (this.isClassMethod()) {
@ -1288,10 +1304,7 @@ export default class StatementParser extends ExpressionParser {
prop.static = false; prop.static = false;
classBody.body.push(this.parseClassProperty(prop)); classBody.body.push(this.parseClassProperty(prop));
return true; return true;
} else if (containsEsc) {
throw this.unexpected();
} }
return false; return false;
} }
@ -1337,7 +1350,7 @@ export default class StatementParser extends ExpressionParser {
if (this.eat(tt.star)) { if (this.eat(tt.star)) {
// a generator // a generator
method.kind = "method"; method.kind = "method";
this.parseClassPropertyName(method); this.parseClassElementName(method);
if (method.key.type === "PrivateName") { if (method.key.type === "PrivateName") {
// Private generator method // Private generator method
@ -1362,7 +1375,7 @@ export default class StatementParser extends ExpressionParser {
} }
const containsEsc = this.state.containsEsc; const containsEsc = this.state.containsEsc;
const key = this.parseClassPropertyName(member); const key = this.parseClassElementName(member);
const isPrivate = key.type === "PrivateName"; const isPrivate = key.type === "PrivateName";
// Check the key is not a computed expression or string literal. // Check the key is not a computed expression or string literal.
const isSimple = key.type === "Identifier"; const isSimple = key.type === "Identifier";
@ -1421,7 +1434,7 @@ export default class StatementParser extends ExpressionParser {
method.kind = "method"; method.kind = "method";
// The so-called parsed name would have been "async": get the real name. // The so-called parsed name would have been "async": get the real name.
this.parseClassPropertyName(method); this.parseClassElementName(method);
this.parsePostMemberNameModifiers(publicMember); this.parsePostMemberNameModifiers(publicMember);
if (method.key.type === "PrivateName") { if (method.key.type === "PrivateName") {
@ -1456,7 +1469,7 @@ export default class StatementParser extends ExpressionParser {
// a getter or setter // a getter or setter
method.kind = key.name; method.kind = key.name;
// The so-called parsed name would have been "get/set": get the real name. // The so-called parsed name would have been "get/set": get the real name.
this.parseClassPropertyName(publicMethod); this.parseClassElementName(publicMethod);
if (method.key.type === "PrivateName") { if (method.key.type === "PrivateName") {
// private getter/setter // private getter/setter
@ -1488,7 +1501,8 @@ export default class StatementParser extends ExpressionParser {
} }
} }
parseClassPropertyName(member: N.ClassMember): N.Expression | N.Identifier { // https://tc39.es/proposal-class-fields/#prod-ClassElementName
parseClassElementName(member: N.ClassMember): N.Expression | N.Identifier {
const key = this.parsePropertyName(member, /* isPrivateNameAllowed */ true); const key = this.parsePropertyName(member, /* isPrivateNameAllowed */ true);
if ( if (
@ -1660,11 +1674,13 @@ export default class StatementParser extends ExpressionParser {
} }
} }
// https://tc39.es/ecma262/#prod-ClassHeritage
parseClassSuper(node: N.Class): void { parseClassSuper(node: N.Class): void {
node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null; node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null;
} }
// Parses module export declaration. // Parses module export declaration.
// https://tc39.es/ecma262/#prod-ExportDeclaration
parseExport(node: N.Node): N.AnyExport { parseExport(node: N.Node): N.AnyExport {
const hasDefault = this.maybeParseExportDefaultSpecifier(node); const hasDefault = this.maybeParseExportDefaultSpecifier(node);
@ -1839,7 +1855,7 @@ export default class StatementParser extends ExpressionParser {
isExportDefaultSpecifier(): boolean { isExportDefaultSpecifier(): boolean {
if (this.match(tt.name)) { if (this.match(tt.name)) {
const value = this.state.value; const value = this.state.value;
if (value === "async" || value === "let") { if ((value === "async" && !this.state.containsEsc) || value === "let") {
return false; return false;
} }
if ( if (
@ -2068,6 +2084,7 @@ export default class StatementParser extends ExpressionParser {
} }
// Parses import declaration. // Parses import declaration.
// https://tc39.es/ecma262/#prod-ImportDeclaration
parseImport(node: N.Node): N.AnyImport { parseImport(node: N.Node): N.AnyImport {
// import '...' // import '...'
@ -2232,6 +2249,7 @@ export default class StatementParser extends ExpressionParser {
} }
} }
// https://tc39.es/ecma262/#prod-ImportSpecifier
parseImportSpecifier(node: N.ImportDeclaration): void { parseImportSpecifier(node: N.ImportDeclaration): void {
const specifier = this.startNode(); const specifier = this.startNode();
specifier.imported = this.parseIdentifier(true); specifier.imported = this.parseIdentifier(true);

View File

@ -313,14 +313,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isPattern: boolean, isPattern: boolean,
containsEsc: boolean, isAccessor: boolean,
): ?N.ObjectMethod { ): ?N.ObjectMethod {
const node: N.EstreeProperty = (super.parseObjectMethod( const node: N.EstreeProperty = (super.parseObjectMethod(
prop, prop,
isGenerator, isGenerator,
isAsync, isAsync,
isPattern, isPattern,
containsEsc, isAccessor,
): any); ): any);
if (node) { if (node) {

View File

@ -2362,8 +2362,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isPattern: boolean, isPattern: boolean,
isAccessor: boolean,
refExpressionErrors: ?ExpressionErrors, refExpressionErrors: ?ExpressionErrors,
containsEsc: boolean,
): void { ): void {
if ((prop: $FlowFixMe).variance) { if ((prop: $FlowFixMe).variance) {
this.unexpected((prop: $FlowFixMe).variance.start); this.unexpected((prop: $FlowFixMe).variance.start);
@ -2373,7 +2373,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
let typeParameters; let typeParameters;
// method shorthand // method shorthand
if (this.isRelational("<")) { if (this.isRelational("<") && !isAccessor) {
typeParameters = this.flowParseTypeParameterDeclaration(); typeParameters = this.flowParseTypeParameterDeclaration();
if (!this.match(tt.parenL)) this.unexpected(); if (!this.match(tt.parenL)) this.unexpected();
} }
@ -2385,8 +2385,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator, isGenerator,
isAsync, isAsync,
isPattern, isPattern,
isAccessor,
refExpressionErrors, refExpressionErrors,
containsEsc,
); );
// add typeParameters if we found them // add typeParameters if we found them

View File

@ -1823,13 +1823,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node.typeParameters = typeArguments; node.typeParameters = typeArguments;
return this.finishCallExpression(node, state.optionalChainMember); return this.finishCallExpression(node, state.optionalChainMember);
} else if (this.match(tt.backQuote)) { } else if (this.match(tt.backQuote)) {
return this.parseTaggedTemplateExpression( const result = this.parseTaggedTemplateExpression(
base,
startPos, startPos,
startLoc, startLoc,
base,
state, state,
typeArguments,
); );
result.typeParameters = typeArguments;
return result;
} }
} }

View File

@ -389,6 +389,11 @@ export type ArrayExpression = NodeBase & {
elements: $ReadOnlyArray<?(Expression | SpreadElement)>, elements: $ReadOnlyArray<?(Expression | SpreadElement)>,
}; };
export type DoExpression = NodeBase & {
type: "DoExpression",
body: ?BlockStatement,
};
export type TupleExpression = NodeBase & { export type TupleExpression = NodeBase & {
type: "TupleExpression", type: "TupleExpression",
elements: $ReadOnlyArray<?(Expression | SpreadElement)>, elements: $ReadOnlyArray<?(Expression | SpreadElement)>,

View File

@ -1,3 +1,3 @@
{ {
"throws": "Unexpected token, expected \"{\" (1:7)" "throws": "Unexpected token, expected \"{\" (1:7)"
} }

View File

@ -0,0 +1,2 @@
async x
=> x

View File

@ -0,0 +1,39 @@
{
"type": "File",
"start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}},
"errors": [
"SyntaxError: No line break is allowed before '=>' (2:2)"
],
"program": {
"type": "Program",
"start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}},
"expression": {
"type": "ArrowFunctionExpression",
"start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}},
"id": null,
"generator": false,
"async": true,
"params": [
{
"type": "Identifier",
"start":6,"end":7,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":7},"identifierName":"x"},
"name": "x"
}
],
"body": {
"type": "Identifier",
"start":11,"end":12,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":4},"identifierName":"x"},
"name": "x"
}
}
}
],
"directives": []
}
}

View File

@ -1,4 +0,0 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}

View File

@ -1,4 +0,0 @@
{
"sourceType": "module",
"plugins": ["exportDefaultFrom"]
}

View File

@ -1,4 +0,0 @@
{
"sourceType": "module",
"plugins": ["exportDefaultFrom"]
}

View File

@ -0,0 +1 @@
export asyn\u{63} from "async";

View File

@ -0,0 +1,37 @@
{
"type": "File",
"start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":31}},
"program": {
"type": "Program",
"start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":31}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExportNamedDeclaration",
"start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":31}},
"specifiers": [
{
"type": "ExportDefaultSpecifier",
"start":7,"end":17,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":17}},
"exported": {
"type": "Identifier",
"start":7,"end":17,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":17},"identifierName":"async"},
"name": "async"
}
}
],
"source": {
"type": "StringLiteral",
"start":23,"end":30,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":30}},
"extra": {
"rawValue": "async",
"raw": "\"async\""
},
"value": "async"
}
}
],
"directives": []
}
}

View File

@ -1,4 +0,0 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}

View File

@ -1,4 +0,0 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}

View File

@ -1,4 +0,0 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}

View File

@ -1,4 +0,0 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}

View File

@ -1,4 +0,0 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}

View File

@ -34,7 +34,6 @@
}, },
"computed": false, "computed": false,
"kind": "set", "kind": "set",
"variance": null,
"id": null, "id": null,
"generator": false, "generator": false,
"async": false, "async": false,

View File

@ -34,7 +34,6 @@
}, },
"computed": false, "computed": false,
"kind": "set", "kind": "set",
"variance": null,
"id": null, "id": null,
"generator": false, "generator": false,
"async": false, "async": false,

View File

@ -34,7 +34,6 @@
}, },
"computed": false, "computed": false,
"kind": "get", "kind": "get",
"variance": null,
"id": null, "id": null,
"generator": false, "generator": false,
"async": false, "async": false,