fix(parser): apply no-side-effects annotations after export (#27504)

`export /*@__NO_SIDE_EFFECTS__*/ function f() {}` left the function’s `pure` flag unset because statement parsing only captured annotations before `export`. Capture annotations immediately after `export` and apply the existing function purity handling.
This commit is contained in:
camc314 committed 2026-10-09 16:19:48 +00:00
1 parent b6eb914283
commit c57d9c7137
4 files changed
+26 -2

No files matched your search

@@ -626,6 +626,10 @@ fn no_side_effects() {
}
check("/* @__NO_SIDE_EFFECTS__ */ function f() {}");
check("/* @__NO_SIDE_EFFECTS__ */ export function f() {}");
check("export /*@__NO_SIDE_EFFECTS__*/ function f() { sideEffect() }");
check("export /* @__NO_SIDE_EFFECTS__ */ async function f() { sideEffect() }");
check("export /* @__NO_SIDE_EFFECTS__ */ function* f() { sideEffect() }");
check("export /* @__NO_SIDE_EFFECTS__ */ async function* f() { sideEffect() }");
check("/* @__NO_SIDE_EFFECTS__ */ export default function f() {}");
check("export default /* @__NO_SIDE_EFFECTS__ */ function f() {}");
check("const f = /* @__NO_SIDE_EFFECTS__ */ function() {}");
+10 -1
View File
@@ -477,6 +477,9 @@ impl<'a, C: Config> ParserImpl<'a, C> {
) -> Statement<'a> {
self.check_module_declaration(stmt_ctx);
self.bump_any(); // bump `export`
// export /* @__NO_SIDE_EFFECTS__ */ ...
let no_side_effects_comments =
self.lexer.trivia_builder.previous_token_no_side_effects_comments();
// `export` is unambiguously module syntax (ECMA-262 §16.2.3): commit to the
// Module goal so the declaration parses under `Await` on the first pass and
// isn't reparsed. e.g. `@foo export default class C { x = await + 1 }`
@@ -567,7 +570,13 @@ impl<'a, C: Config> ParserImpl<'a, C> {
)
}
};
Statement::from(decl)
let mut stmt = Statement::from(decl);
if let Some(comments) = no_side_effects_comments
&& Self::set_pure_on_function_stmt(&mut stmt)
{
self.lexer.trivia_builder.mark_no_side_effects_comments_applied(comments);
}
stmt
}
// export NamedExports ;
+1 -1
View File
@@ -207,7 +207,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
stmt
}
fn set_pure_on_function_stmt(stmt: &mut Statement<'a>) -> bool {
pub(super) fn set_pure_on_function_stmt(stmt: &mut Statement<'a>) -> bool {
match stmt {
Statement::FunctionDeclaration(func) => {
func.pure = true;
@@ -1100,6 +1100,10 @@ function bar() {}";
"/* #__NO_SIDE_EFFECTS__ */ let foo = () => {};",
"/* #__NO_SIDE_EFFECTS__ */ var foo = function() {};",
"const foo /* #__NO_SIDE_EFFECTS__ */ = () => {};",
"export /* #__NO_SIDE_EFFECTS__ */ class Foo {}",
"export /* #__NO_SIDE_EFFECTS__ */ let foo = () => {};",
"export /* #__NO_SIDE_EFFECTS__ */ var foo = function() {};",
"export /* #__NO_SIDE_EFFECTS__ */ const foo = 1, bar = () => {};",
];
for source_text in cases {
let comments = get_comments(source_text);
@@ -1117,11 +1121,18 @@ function bar() {}";
"/* #__NO_SIDE_EFFECTS__ */ function foo() {}",
"/* #__NO_SIDE_EFFECTS__ */ async function foo() {}",
"/* #__NO_SIDE_EFFECTS__ */ export function foo() {}",
"export /*@__NO_SIDE_EFFECTS__*/ function foo() {}",
"export /* #__NO_SIDE_EFFECTS__ */ async function foo() {}",
"export /* #__NO_SIDE_EFFECTS__ */ function* foo() {}",
"export /* #__NO_SIDE_EFFECTS__ */ async function* foo() {}",
"export /* #__NO_SIDE_EFFECTS__ */ default function foo() {}",
"export default /* #__NO_SIDE_EFFECTS__ */ function foo() {}",
"const foo = /* #__NO_SIDE_EFFECTS__ */ function() {};",
"const foo = /* #__NO_SIDE_EFFECTS__ */ () => {};",
"/* #__NO_SIDE_EFFECTS__ */ const foo = () => {};",
"/* #__NO_SIDE_EFFECTS__ */ export const foo = () => {};",
"export /* #__NO_SIDE_EFFECTS__ */ const foo = () => {};",
"export /* #__NO_SIDE_EFFECTS__ */ const foo = function() {};",
"[/* #__NO_SIDE_EFFECTS__ */ function() {}];",
];
for source_text in cases {