Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,7 @@ pub enum ExprFlag {
ForbidIn,
HasNonOptionalChainParent,
ExprResultIsUnused,
IsFollowedByOf,
}

pub type ExprFlagSet = enumset::EnumSet<ExprFlag>;
Expand All @@ -1546,6 +1547,10 @@ impl ExprFlag {
pub fn expr_result_is_unused() -> ExprFlagSet {
ExprFlag::ExprResultIsUnused.into()
}
#[inline]
pub fn is_followed_by_of() -> ExprFlagSet {
ExprFlag::IsFollowedByOf.into()
}
}

#[derive(Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -3818,6 +3823,8 @@ pub mod __gated_printer {
flags.remove(ExprFlag::HasNonOptionalChainParent);
}

// The index target is not directly followed by `of`.
flags.remove(ExprFlag::IsFollowedByOf);
self.print_expr(e.target, Level::Postfix, flags);

let is_optional_chain_start =
Expand Down Expand Up @@ -4255,7 +4262,12 @@ pub mod __gated_printer {
}
ExprData::EIdentifier(e) => {
let name = self.name_for_symbol(e.ref_);
let wrap = self.writer.written() == self.for_of_init_start && name == b"let";
// A for-of loop initializer must not start with the token "let" and
// must not be the exact token sequence "async of" (e.g. the escaped
// identifier in "for (\u0061sync of []) ;"), so wrap in parentheses.
let wrap = self.writer.written() == self.for_of_init_start
&& (name == b"let"
|| (name == b"async" && flags.contains(ExprFlag::IsFollowedByOf)));
Comment thread
robobun marked this conversation as resolved.

if wrap {
self.print(b"(");
Expand Down Expand Up @@ -5808,7 +5820,7 @@ pub mod __gated_printer {
self.print(b"for");
self.print_space();
self.print(b"(");
self.print_for_loop_init(s.init);
self.print_for_loop_init(s.init, ExprFlag::none());
self.print_space();
self.print_space_before_identifier();
self.print(b"in");
Expand All @@ -5828,7 +5840,7 @@ pub mod __gated_printer {
self.print_space();
self.print(b"(");
self.for_of_init_start = self.writer.written();
self.print_for_loop_init(s.init);
self.print_for_loop_init(s.init, ExprFlag::is_followed_by_of());
self.print_space();
self.print_space_before_identifier();
self.print(b"of");
Expand Down Expand Up @@ -5914,7 +5926,7 @@ pub mod __gated_printer {
self.print(b"(");

if let Some(init_) = &s.init {
self.print_for_loop_init(*init_);
self.print_for_loop_init(*init_, ExprFlag::none());
}

self.print(b";");
Expand Down Expand Up @@ -6601,13 +6613,13 @@ pub mod __gated_printer {
self.print(b", enumerable: true, configurable: true})");
}

pub fn print_for_loop_init(&mut self, init_st: Stmt) {
pub fn print_for_loop_init(&mut self, init_st: Stmt, extra_flags: ExprFlagSet) {
match &init_st.data {
StmtData::SExpr(s) => {
self.print_expr(
s.value,
Level::Lowest,
ExprFlag::ForbidIn | ExprFlag::ExprResultIsUnused,
ExprFlag::ForbidIn | ExprFlag::ExprResultIsUnused | extra_flags,
);
}
StmtData::SLocal(s) => {
Expand Down
24 changes: 24 additions & 0 deletions test/bundler/transpiler/transpiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,30 @@ console.log(<div {...obj} key="after" />);`),
expectParseError("await -x ** 0", "Unexpected **");
});

it("for-of loop variable named async", () => {
// "\u0061sync" is the identifier `async`, which is legal as a for-of loop
// variable, but printing it as the raw token sequence `async of` is a
// syntax error, so the printer must parenthesize it.
expectPrinted_("for (\\u0061sync of [7]);", "for ((async) of [7])\n ;\n");
expectPrinted_("for ((async) of [7]);", "for ((async) of [7])\n ;\n");
expectPrinted_(
"async function f() { for await (\\u0061sync of [7]); }",
"async function f() {\n for await ((async) of [7])\n ;\n}",
);

// The same identifier needs no parentheses when it is not directly followed by `of`
expectPrinted_("for (async.x of [7]);", "for (async.x of [7])\n ;\n");
expectPrinted_("for (\\u0061sync[0] of [7]);", "for (async[0] of [7])\n ;\n");
expectPrinted_("for (x[\\u0061sync] of [7]);", "for (x[async] of [7])\n ;\n");
expectPrinted_("for (\\u0061sync in x);", "for (async in x)\n ;\n");

// `let` as a for-of loop variable keeps its parentheses too
expectPrinted_("for ((let) of [7]);", "for ((let) of [7])\n ;\n");

// The keyword spelling is a syntax error, which is why the parentheses matter
expect(() => parsed("for (async of [7]);", false, false)).toThrow();
});

it("await", () => {
expectPrinted("await x", "await x");
expectPrinted("await +x", "await +x");
Expand Down
Loading