Skip to content

Commit

Permalink
feat(parser,codegen)!: parse and print#__NO_SIDE_EFFECTS__ (#9496)
Browse files Browse the repository at this point in the history
Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
  • Loading branch information
Boshen and Boshen authored Mar 3, 2025
1 parent 7fde233 commit a8d1d48
Show file tree
Hide file tree
Showing 22 changed files with 281 additions and 180 deletions.
11 changes: 0 additions & 11 deletions crates/oxc_ast/src/ast/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,6 @@ impl Comment {
|| source_text.contains("@preserve")
}

/// `#__PURE__` Notation Specification
///
/// <https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/c14f7e197cb225c9eee877143536665ce3150712/pure-notation-spec.md>
#[inline] // inline because code path is hot.
pub fn is_pure(&self, source_text: &str) -> bool {
source_text[(self.span.start + 2) as usize..]
.trim_ascii_start()
.strip_prefix(['@', '#'])
.is_some_and(|s| s.starts_with("__PURE__"))
}

/// Gets the span of the comment content.
pub fn content_span(&self) -> Span {
match self.kind {
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1834,6 +1834,10 @@ pub struct ArrowFunctionExpression<'a> {
#[estree(via = ArrowFunctionExpressionBody)]
pub body: Box<'a, FunctionBody<'a>>,
pub scope_id: Cell<Option<ScopeId>>,
/// `true` if the function is marked with a `/*#__NO_SIDE_EFFECTS__*/` comment
#[builder(default)]
#[estree(skip)]
pub pure: bool,
}

/// Generator Function Definitions
Expand Down
4 changes: 3 additions & 1 deletion crates/oxc_ast/src/generated/assert_layouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ const _: () = {
assert!(offset_of!(ArrowFunctionExpression, return_type) == 32);
assert!(offset_of!(ArrowFunctionExpression, body) == 40);
assert!(offset_of!(ArrowFunctionExpression, scope_id) == 48);
assert!(offset_of!(ArrowFunctionExpression, pure) == 52);

assert!(size_of::<YieldExpression>() == 32);
assert!(align_of::<YieldExpression>() == 8);
Expand Down Expand Up @@ -1943,7 +1944,7 @@ const _: () = {
assert!(offset_of!(FunctionBody, directives) == 8);
assert!(offset_of!(FunctionBody, statements) == 24);

assert!(size_of::<ArrowFunctionExpression>() == 32);
assert!(size_of::<ArrowFunctionExpression>() == 36);
assert!(align_of::<ArrowFunctionExpression>() == 4);
assert!(offset_of!(ArrowFunctionExpression, span) == 0);
assert!(offset_of!(ArrowFunctionExpression, expression) == 8);
Expand All @@ -1953,6 +1954,7 @@ const _: () = {
assert!(offset_of!(ArrowFunctionExpression, return_type) == 20);
assert!(offset_of!(ArrowFunctionExpression, body) == 24);
assert!(offset_of!(ArrowFunctionExpression, scope_id) == 28);
assert!(offset_of!(ArrowFunctionExpression, pure) == 32);

assert!(size_of::<YieldExpression>() == 20);
assert!(align_of::<YieldExpression>() == 4);
Expand Down
21 changes: 14 additions & 7 deletions crates/oxc_ast/src/generated/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5589,6 +5589,7 @@ impl<'a> AstBuilder<'a> {
return_type: return_type.into_in(self.allocator),
body: body.into_in(self.allocator),
scope_id: Default::default(),
pure: Default::default(),
}
}

Expand Down Expand Up @@ -5635,9 +5636,9 @@ impl<'a> AstBuilder<'a> {
)
}

/// Build an [`ArrowFunctionExpression`] with `scope_id`.
/// Build an [`ArrowFunctionExpression`] with `scope_id` and `pure`.
///
/// If you want the built node to be allocated in the memory arena, use [`AstBuilder::alloc_arrow_function_expression_with_scope_id`] instead.
/// If you want the built node to be allocated in the memory arena, use [`AstBuilder::alloc_arrow_function_expression_with_scope_id_and_pure`] instead.
///
/// ## Parameters
/// * `span`: The [`Span`] covering this node
Expand All @@ -5648,8 +5649,9 @@ impl<'a> AstBuilder<'a> {
/// * `return_type`
/// * `body`: See `expression` for whether this arrow expression returns an expression.
/// * `scope_id`
/// * `pure`: `true` if the function is marked with a `/*#__NO_SIDE_EFFECTS__*/` comment
#[inline]
pub fn arrow_function_expression_with_scope_id<T1, T2, T3, T4>(
pub fn arrow_function_expression_with_scope_id_and_pure<T1, T2, T3, T4>(
self,
span: Span,
expression: bool,
Expand All @@ -5659,6 +5661,7 @@ impl<'a> AstBuilder<'a> {
return_type: T3,
body: T4,
scope_id: ScopeId,
pure: bool,
) -> ArrowFunctionExpression<'a>
where
T1: IntoIn<'a, Option<Box<'a, TSTypeParameterDeclaration<'a>>>>,
Expand All @@ -5675,12 +5678,13 @@ impl<'a> AstBuilder<'a> {
return_type: return_type.into_in(self.allocator),
body: body.into_in(self.allocator),
scope_id: Cell::new(Some(scope_id)),
pure,
}
}

/// Build an [`ArrowFunctionExpression`] with `scope_id`, and store it in the memory arena.
/// Build an [`ArrowFunctionExpression`] with `scope_id` and `pure`, and store it in the memory arena.
///
/// Returns a [`Box`] containing the newly-allocated node. If you want a stack-allocated node, use [`AstBuilder::arrow_function_expression_with_scope_id`] instead.
/// Returns a [`Box`] containing the newly-allocated node. If you want a stack-allocated node, use [`AstBuilder::arrow_function_expression_with_scope_id_and_pure`] instead.
///
/// ## Parameters
/// * `span`: The [`Span`] covering this node
Expand All @@ -5691,8 +5695,9 @@ impl<'a> AstBuilder<'a> {
/// * `return_type`
/// * `body`: See `expression` for whether this arrow expression returns an expression.
/// * `scope_id`
/// * `pure`: `true` if the function is marked with a `/*#__NO_SIDE_EFFECTS__*/` comment
#[inline]
pub fn alloc_arrow_function_expression_with_scope_id<T1, T2, T3, T4>(
pub fn alloc_arrow_function_expression_with_scope_id_and_pure<T1, T2, T3, T4>(
self,
span: Span,
expression: bool,
Expand All @@ -5702,6 +5707,7 @@ impl<'a> AstBuilder<'a> {
return_type: T3,
body: T4,
scope_id: ScopeId,
pure: bool,
) -> Box<'a, ArrowFunctionExpression<'a>>
where
T1: IntoIn<'a, Option<Box<'a, TSTypeParameterDeclaration<'a>>>>,
Expand All @@ -5710,7 +5716,7 @@ impl<'a> AstBuilder<'a> {
T4: IntoIn<'a, Box<'a, FunctionBody<'a>>>,
{
Box::new_in(
self.arrow_function_expression_with_scope_id(
self.arrow_function_expression_with_scope_id_and_pure(
span,
expression,
r#async,
Expand All @@ -5719,6 +5725,7 @@ impl<'a> AstBuilder<'a> {
return_type,
body,
scope_id,
pure,
),
self.allocator,
)
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_ast/src/generated/derive_clone_in.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1923,6 +1923,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for ArrowFunctionExpression<'_> {
return_type: CloneIn::clone_in(&self.return_type, allocator),
body: CloneIn::clone_in(&self.body, allocator),
scope_id: Default::default(),
pure: CloneIn::clone_in(&self.pure, allocator),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_ast/src/generated/derive_content_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,7 @@ impl ContentEq for ArrowFunctionExpression<'_> {
&& ContentEq::content_eq(&self.params, &other.params)
&& ContentEq::content_eq(&self.return_type, &other.return_type)
&& ContentEq::content_eq(&self.body, &other.body)
&& ContentEq::content_eq(&self.pure, &other.pure)
}
}

Expand Down
43 changes: 4 additions & 39 deletions crates/oxc_codegen/src/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,10 @@ impl Codegen<'_> {
self.comments.contains_key(&start)
}

pub(crate) fn has_annotation_comment(&self, start: u32) -> bool {
if !self.options.print_annotation_comments() {
return false;
}
self.comments.get(&start).is_some_and(|comments| {
comments.iter().any(|comment| self.is_annotation_comment(comment))
})
}

pub(crate) fn has_non_annotation_comment(&self, start: u32) -> bool {
if self.options.print_annotation_comments() {
self.comments.get(&start).is_some_and(|comments| {
comments.iter().any(|comment| !self.is_annotation_comment(comment))
comments.iter().any(|comment| !self.is_pure_comment(comment))
})
} else {
self.has_comment(start)
Expand All @@ -40,7 +31,7 @@ impl Codegen<'_> {
/// `#__PURE__` Notation Specification
///
/// <https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/c14f7e197cb225c9eee877143536665ce3150712/pure-notation-spec.md>
fn is_annotation_comment(&self, comment: &Comment) -> bool {
fn is_pure_comment(&self, comment: &Comment) -> bool {
let s = comment.content_span().source_text(self.source_text).trim_start();
if let Some(s) = s.strip_prefix(['@', '#']) {
s.starts_with("__PURE__") || s.starts_with("__NO_SIDE_EFFECTS__")
Expand All @@ -52,8 +43,7 @@ impl Codegen<'_> {
/// Whether to keep leading comments.
fn is_leading_comments(&self, comment: &Comment) -> bool {
comment.preceded_by_newline
&& (comment.is_jsdoc(self.source_text)
|| (comment.is_line() && self.is_annotation_comment(comment)))
&& (comment.is_jsdoc(self.source_text) && !self.is_pure_comment(comment))
&& !comment.content_span().source_text(self.source_text).chars().all(|c| c == '*')
// webpack comment `/*****/`
}
Expand Down Expand Up @@ -118,36 +108,11 @@ impl Codegen<'_> {
}
}

pub(crate) fn print_annotation_comments(&mut self, node_start: u32) {
if !self.options.print_annotation_comments() {
return;
}

// If there is has annotation comments awaiting move to here, print them.
let start = self.start_of_annotation_comment.take().unwrap_or(node_start);

let Some(comments) = self.comments.remove(&start) else { return };

for comment in comments {
if !self.is_annotation_comment(&comment) {
continue;
}
if comment.is_line() {
self.print_str("/*");
self.print_str(comment.content_span().source_text(self.source_text));
self.print_str("*/");
} else {
self.print_str(comment.span.source_text(self.source_text));
}
self.print_hard_space();
}
}

pub(crate) fn print_expr_comments(&mut self, start: u32) -> bool {
let Some(comments) = self.comments.remove(&start) else { return false };

let (annotation_comments, comments): (Vec<_>, Vec<_>) =
comments.into_iter().partition(|comment| self.is_annotation_comment(comment));
comments.into_iter().partition(|comment| self.is_pure_comment(comment));

if !annotation_comments.is_empty() {
self.comments.insert(start, annotation_comments);
Expand Down
Loading

0 comments on commit a8d1d48

Please sign in to comment.