Create parser plugin "topLevelAwait" (#10449)

* Create parser plugin "topLevelAwait"

* Update test262 whitelist

* Update ts typings

* Fix "sourceType: unambiguous" with TLA

* Ambiguous tokens after await

* Update await %x(0)

* typo [skip ci]

* Typo [skip ci]

Co-Authored-By: Brian Ng <bng412@gmail.com>
This commit is contained in:
Nicolò Ribaudo
2019-10-29 22:18:39 +01:00
committed by GitHub
parent 63f9a3c946
commit 143d159982
42 changed files with 2108 additions and 461 deletions

View File

@@ -25,15 +25,32 @@ export function parse(input: string, options?: Options): File {
const parser = getParser(options, input);
const ast = parser.parse();
// Rather than try to parse as a script first, we opt to parse as a module and convert back
// to a script where possible to avoid having to do a full re-parse of the input content.
if (!parser.sawUnambiguousESM) ast.program.sourceType = "script";
if (parser.sawUnambiguousESM) {
return ast;
}
if (parser.ambiguousScriptDifferentAst) {
// Top level await introduces code which can be both a valid script and
// a valid module, but which produces different ASTs:
// await
// 0
// can be parsed either as an AwaitExpression, or as two ExpressionStatements.
try {
options.sourceType = "script";
return getParser(options, input).parse();
} catch {}
} else {
// This is both a valid module and a valid script, but
// we parse it as a script by default
ast.program.sourceType = "script";
}
return ast;
} catch (moduleError) {
try {
options.sourceType = "script";
return getParser(options, input).parse();
} catch (scriptError) {}
} catch {}
throw moduleError;
}

View File

@@ -13,6 +13,7 @@ export default class BaseParser {
plugins: PluginsMap;
filename: ?string;
sawUnambiguousESM: boolean = false;
ambiguousScriptDifferentAst: boolean = false;
// Initialized by Tokenizer
state: State;

View File

@@ -2208,6 +2208,7 @@ export default class ExpressionParser extends LValParser {
isAwaitAllowed(): boolean {
if (this.scope.inFunction) return this.scope.inAsync;
if (this.options.allowAwaitOutsideFunction) return true;
if (this.hasPlugin("topLevelAwait")) return this.inModule;
return false;
}
@@ -2234,9 +2235,33 @@ export default class ExpressionParser extends LValParser {
);
}
if (!this.scope.inFunction && !this.options.allowAwaitOutsideFunction) {
if (
this.hasPrecedingLineBreak() ||
// All the following expressions are ambiguous:
// await + 0, await - 0, await ( 0 ), await [ 0 ], await / 0 /u, await ``
this.match(tt.plusMin) ||
this.match(tt.parenL) ||
this.match(tt.bracketL) ||
this.match(tt.backQuote) ||
// Sometimes the tokenizer generates tt.slash for regexps, and this is
// handler by parseExprAtom
this.match(tt.regexp) ||
this.match(tt.slash) ||
// This code could be parsed both as a modulo operator or as an intrinsic:
// await %x(0)
(this.hasPlugin("v8intrinsic") && this.match(tt.modulo))
) {
this.ambiguousScriptDifferentAst = true;
} else {
this.sawUnambiguousESM = true;
}
}
if (!this.state.soloAwait) {
node.argument = this.parseMaybeUnary();
}
return this.finishNode(node, "AwaitExpression");
}

View File

@@ -495,11 +495,7 @@ export default class StatementParser extends ExpressionParser {
this.state.labels.push(loopLabel);
let awaitAt = -1;
if (
(this.scope.inAsync ||
(!this.scope.inFunction && this.options.allowAwaitOutsideFunction)) &&
this.eatContextual("await")
) {
if (this.isAwaitAllowed() && this.eatContextual("await")) {
awaitAt = this.state.lastTokStart;
}
this.scope.enter(SCOPE_OTHER);