Top level await (#627)

Implements a top level await for es2017+, and allow it for typescript and ecmascript (ecmascript requires topLevelAwait: true).

Closes #626.
This commit is contained in:
kdy1 2020-02-05 01:05:23 +00:00
parent 42373f975b
commit 94eac1de89
10 changed files with 69 additions and 6 deletions

View File

@ -46,6 +46,8 @@ pub struct Error {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum SyntaxError { pub enum SyntaxError {
TopLevelAwait,
LegacyDecimal, LegacyDecimal,
LegacyOctal, LegacyOctal,
InvalidIdentChar, InvalidIdentChar,
@ -226,6 +228,9 @@ impl<'a> From<ErrorToDiag<'a>> for DiagnosticBuilder<'a> {
#[cold] #[cold]
fn from(e: ErrorToDiag<'a>) -> Self { fn from(e: ErrorToDiag<'a>) -> Self {
let msg: Cow<'static, _> = match e.error { let msg: Cow<'static, _> = match e.error {
TopLevelAwait => "top level await requires target to es2017 or higher and \
topLevelAwait:true for ecmascript"
.into(),
LegacyDecimal => "Legacy decimal escape is not permitted in strict mode".into(), LegacyDecimal => "Legacy decimal escape is not permitted in strict mode".into(),
LegacyOctal => "Legacy octal escape is not permitted in strict mode".into(), LegacyOctal => "Legacy octal escape is not permitted in strict mode".into(),
InvalidIdentChar => "Invalid character in identifier".into(), InvalidIdentChar => "Invalid character in identifier".into(),

View File

@ -281,6 +281,18 @@ impl Syntax {
_ => false, _ => false,
} }
} }
pub fn top_level_await(self) -> bool {
match self {
Syntax::Es(EsConfig {
top_level_await: true,
..
})
| Syntax::Typescript(..) => true,
_ => false,
}
}
} }
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
@ -378,6 +390,10 @@ pub struct EsConfig {
/// Stage 3. /// Stage 3.
#[serde(default)] #[serde(default)]
pub import_meta: bool, pub import_meta: bool,
/// Stage 3.
#[serde(default)]
pub top_level_await: bool,
} }
/// Syntactic context. /// Syntactic context.

View File

@ -338,11 +338,10 @@ impl<'a, I: Tokens> Parser<'a, I> {
Ok(expr) Ok(expr)
} }
fn parse_await_expr(&mut self) -> PResult<'a, Box<Expr>> { pub(crate) fn parse_await_expr(&mut self) -> PResult<'a, Box<Expr>> {
let start = cur_pos!(); let start = cur_pos!();
assert_and_bump!("await"); assert_and_bump!("await");
debug_assert!(self.ctx().in_async);
if is!('*') { if is!('*') {
syntax_error!(SyntaxError::AwaitStar); syntax_error!(SyntaxError::AwaitStar);

View File

@ -175,7 +175,7 @@ where
F: for<'a> FnOnce(&'a mut Parser<'a, Lexer<'a, crate::SourceFileInput<'_>>>) -> Result<Ret, ()>, F: for<'a> FnOnce(&'a mut Parser<'a, Lexer<'a, crate::SourceFileInput<'_>>>) -> Result<Ret, ()>,
{ {
crate::with_test_sess(s, |sess, input| { crate::with_test_sess(s, |sess, input| {
let lexer = Lexer::new(sess, syntax, Default::default(), input, None); let lexer = Lexer::new(sess, syntax, JscTarget::Es2019, input, None);
f(&mut Parser::new_from(sess, lexer)) f(&mut Parser::new_from(sess, lexer))
}) })
.unwrap_or_else(|output| panic!("test_parser(): failed to parse \n{}\n{}", s, output)) .unwrap_or_else(|output| panic!("test_parser(): failed to parse \n{}\n{}", s, output))

View File

@ -92,6 +92,20 @@ impl<'a, I: Tokens> Parser<'a, I> {
top_level: bool, top_level: bool,
decorators: Vec<Decorator>, decorators: Vec<Decorator>,
) -> PResult<'a, Stmt> { ) -> PResult<'a, Stmt> {
let start = cur_pos!();
if top_level && is!("await") {
let valid = self.target() >= JscTarget::Es2017 && self.syntax().top_level_await();
if !valid {
self.emit_err(self.input.cur_span(), SyntaxError::TopLevelAwait);
}
let expr = self.parse_await_expr()?;
let span = span!(start);
return Ok(Stmt::Expr(ExprStmt { span, expr }));
}
if self.input.syntax().typescript() && is!("const") && peeked_is!("enum") { if self.input.syntax().typescript() && is!("const") && peeked_is!("enum") {
assert_and_bump!("const"); assert_and_bump!("const");
assert_and_bump!("enum"); assert_and_bump!("enum");
@ -1634,4 +1648,20 @@ export default function waitUntil(callback, options = {}) {
}, },
); );
} }
#[test]
fn top_level_await() {
test_parser(
"await foo",
Syntax::Es(EsConfig {
top_level_await: true,
..Default::default()
}),
|p| {
p.parse_module().map_err(|mut e| {
e.emit();
})
},
);
}
} }

View File

@ -1,4 +1,10 @@
error: Unexpected token Some(Word(await)) error: top level await requires target to es2017 or higher and topLevelAwait:true for ecmascript
--> $DIR/tests/test262-parser/fail/1aefe47e20eb91fa.module.js:1:1
|
1 | await
| ^^^^^
error: Unexpected token None
--> $DIR/tests/test262-parser/fail/1aefe47e20eb91fa.module.js:1:1 --> $DIR/tests/test262-parser/fail/1aefe47e20eb91fa.module.js:1:1
| |
1 | await 1 | await

View File

@ -0,0 +1,6 @@
error: top level await requires target to es2017 or higher and topLevelAwait:true for ecmascript
--> $DIR/tests/typescript-errors/custom/top-level-await-jsc-target/input.ts:1:1
|
1 | await foo
| ^^^^^

View File

@ -21,7 +21,7 @@
}, },
"declare": false, "declare": false,
"span": { "span": {
"start": 0, "start": 5,
"end": 196, "end": 196,
"ctxt": 0 "ctxt": 0
}, },

View File

@ -21,7 +21,7 @@
}, },
"declare": false, "declare": false,
"span": { "span": {
"start": 0, "start": 21,
"end": 34, "end": 34,
"ctxt": 0 "ctxt": 0
}, },