mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
fix(parser): correctly classify unapplied pure annotations (#26084)
Mark all `#__PURE__` annotations not directly applied to a `NewExpression` or CallExpression` as `PureNotApplied`. This changes the approach by letting pure annotations start of their life as `CommentContent::PureNotApplied`, and only changing to `CommentContent::Pure` once they have successfully been applied to a node. This aligns the behaviour with Rollup. Rollup Playground: https://rollupjs.org/repl/?version=4.63.0&shareable=eyJleGFtcGxlIjpudWxsLCJtb2R1bGVzIjpbeyJjb2RlIjoidmFyIGZvbyAvKiAjX19QVVJFX18gKi8gPSBwdXJlT3BlcmF0aW9uKCk7XG5mb28gLyogI19fUFVSRV9fICovID0gcHVyZU9wZXJhdGlvbigpO1xuZm9vIC8qICNfX1BVUkVfXyAqLyArIHB1cmVPcGVyYXRpb24oKTtcbmZvbyAvKiAjX19QVVJFX18gKi8gJiYgcHVyZU9wZXJhdGlvbigpOyIsImlzRW50cnkiOnRydWUsIm5hbWUiOiJtYWluLmpzIn1dLCJvcHRpb25zIjp7fX0= Rolldown Playground: (only the 1st pure comment is marked as not applied). https://repl.rolldown.rs/#eNqlkU1LxDAQhv/KEGG/LO294kk8K4K3QEnbyW60nYR0uu5S+t9NUlwRPQgLgczHOy/zMJPQopyEoRZP+dsQYxLld56JJqRH5UFbC8UObqrq+fXlsapgV8A9uNHjk0Ov2FjabO+kl3Sd8vbfytXqlzTsi6JkP+KcCW+7rrUflDeWtNnnfMH7o7OAmt5ZzzBBi9oQPqQ2zKC97WH9NbaWJAlPSRqEauzSfxnYTJIADLmRS1gs8x5Z5amUQVEAHxC06XCAATtsGFuoz6laj8yWIDxUzQFY1dEsjFgXIYeY2ZGT9wS9IaPPJURkmCXNW0kisDvVvKs9hhPaALxQ/6gtvGlRKVp0GC5OjcFBiuAbnaJRcDoGWacYBxbzJ8uvunY= fixes https://github.com/oxc-project/oxc/issues/23669 closes https://github.com/oxc-project/oxc/pull/24161
This commit is contained in:
1 parent
7cec5f3f69
commit
d5163d0faa
5 files changed
+133
-79
No files matched your search
@@ -129,8 +129,6 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
// `const foo /* #__PURE__ */ = bar()` - pure comment before `=` cannot be applied
|
||||
self.lexer.trivia_builder.mark_current_pure_comment_not_applied();
|
||||
let init = self.eat(Kind::Eq).then(|| self.parse_assignment_expression_or_higher());
|
||||
let decl = VariableDeclarator::new(
|
||||
self.end_span(start),
|
||||
|
||||
@@ -744,6 +744,10 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
|
||||
/// Section 13.3 Left-Hand-Side Expression
|
||||
pub(crate) fn parse_lhs_expression_or_higher(&mut self) -> Expression<'a> {
|
||||
self.with_pure_comments(Self::parse_lhs_expression_or_higher_impl)
|
||||
}
|
||||
|
||||
fn parse_lhs_expression_or_higher_impl(&mut self) -> Expression<'a> {
|
||||
let start = self.cur_start();
|
||||
let mut in_optional_chain = false;
|
||||
// `MemberExpression`
|
||||
@@ -778,6 +782,20 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_pure_comments(
|
||||
&mut self,
|
||||
parse: impl FnOnce(&mut Self) -> Expression<'a>,
|
||||
) -> Expression<'a> {
|
||||
let pure_comments = self.lexer.trivia_builder.previous_token_pure_comments();
|
||||
let mut expr = parse(self);
|
||||
if let Some(comments) = pure_comments
|
||||
&& Self::set_pure_on_call_or_new_expr(&mut expr)
|
||||
{
|
||||
self.lexer.trivia_builder.mark_pure_comments_applied(comments);
|
||||
}
|
||||
expr
|
||||
}
|
||||
|
||||
fn map_to_chain_expression(&self, span: Span, expr: Expression<'a>) -> Expression<'a> {
|
||||
match expr {
|
||||
match_member_expression!(Expression) => {
|
||||
@@ -1235,7 +1253,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
// which parses the JSX element.)
|
||||
Kind::LAngle if !self.source_type.is_jsx() => {
|
||||
if self.is_ts {
|
||||
self.parse_ts_type_assertion()
|
||||
self.with_pure_comments(Self::parse_ts_type_assertion)
|
||||
} else {
|
||||
self.parse_jsx_in_non_jsx_error()
|
||||
}
|
||||
@@ -1257,7 +1275,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
return self.parse_jsx_expression();
|
||||
}
|
||||
if self.is_ts {
|
||||
return self.parse_ts_type_assertion();
|
||||
return self.with_pure_comments(Self::parse_ts_type_assertion);
|
||||
}
|
||||
self.parse_jsx_in_non_jsx_error()
|
||||
}
|
||||
@@ -1277,13 +1295,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
let start = self.cur_start();
|
||||
let operator = map_unary_operator(self.cur_kind());
|
||||
self.bump_any();
|
||||
let pure_comment_index = self.lexer.trivia_builder.previous_token_has_pure_comment();
|
||||
let mut argument = self.parse_simple_unary_expression(self.cur_start());
|
||||
if let Some(index) = pure_comment_index
|
||||
&& !Self::set_pure_on_call_or_new_expr(&mut argument)
|
||||
{
|
||||
self.lexer.trivia_builder.mark_pure_comment_not_applied(index);
|
||||
}
|
||||
let argument = self.parse_simple_unary_expression(self.cur_start());
|
||||
Expression::new_unary_expression(self.end_span(start), operator, argument, self)
|
||||
}
|
||||
|
||||
@@ -1298,13 +1310,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
let lhs = if self.ctx.has_in() && self.at(Kind::PrivateIdentifier) {
|
||||
self.parse_private_in_expression(lhs_start, lhs_precedence)
|
||||
} else {
|
||||
let has_pure_comment =
|
||||
self.lexer.trivia_builder.previous_token_has_pure_comment().is_some();
|
||||
let mut expr = self.parse_unary_expression_or_higher(lhs_start);
|
||||
if has_pure_comment {
|
||||
Self::set_pure_on_call_or_new_expr(&mut expr);
|
||||
}
|
||||
expr
|
||||
self.parse_unary_expression_or_higher(lhs_start)
|
||||
};
|
||||
|
||||
self.parse_binary_expression_rest(lhs_start, lhs, lhs_parenthesized, lhs_precedence)
|
||||
@@ -1485,7 +1491,6 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
) -> Expression<'a> {
|
||||
let has_no_side_effects_comment =
|
||||
self.lexer.trivia_builder.previous_token_has_no_side_effects_comment();
|
||||
let pure_comment_index = self.lexer.trivia_builder.previous_token_has_pure_comment();
|
||||
// [+Yield] YieldExpression
|
||||
if self.is_yield_expression() {
|
||||
return self.parse_yield_expression();
|
||||
@@ -1549,12 +1554,6 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
let mut expr =
|
||||
self.parse_conditional_expression_rest(start, lhs, allow_return_type_in_arrow_function);
|
||||
|
||||
if let Some(index) = pure_comment_index
|
||||
&& !Self::set_pure_on_call_or_new_expr(&mut expr)
|
||||
{
|
||||
self.lexer.trivia_builder.mark_pure_comment_not_applied(index);
|
||||
}
|
||||
|
||||
if has_no_side_effects_comment {
|
||||
Self::set_pure_on_function_expr(&mut expr);
|
||||
}
|
||||
@@ -1572,17 +1571,6 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
new_expr.pure = true;
|
||||
true
|
||||
}
|
||||
Expression::BinaryExpression(binary_expr) => {
|
||||
Self::set_pure_on_call_or_new_expr(&mut binary_expr.left)
|
||||
}
|
||||
Expression::LogicalExpression(logical_expr) => {
|
||||
Self::set_pure_on_call_or_new_expr(&mut logical_expr.left)
|
||||
}
|
||||
Expression::ConditionalExpression(conditional_expr) => {
|
||||
Self::set_pure_on_call_or_new_expr(&mut conditional_expr.test)
|
||||
}
|
||||
// Recurse through member-access chains: `/* #__PURE__ */ foo().a.b.c`
|
||||
// applies PURE to the underlying call/new (Rollup/esbuild semantics).
|
||||
expr @ match_member_expression!(Expression) => {
|
||||
Self::set_pure_on_call_or_new_expr(expr.to_member_expression_mut().object_mut())
|
||||
}
|
||||
|
||||
@@ -134,7 +134,6 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
) -> Statement<'a> {
|
||||
let has_no_side_effects_comment =
|
||||
self.lexer.trivia_builder.previous_token_has_no_side_effects_comment();
|
||||
let pure_comment_index = self.lexer.trivia_builder.previous_token_has_pure_comment();
|
||||
|
||||
let mut stmt = match self.cur_kind() {
|
||||
Kind::LCurly => self.parse_block_statement(),
|
||||
@@ -196,14 +195,6 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
_ => self.parse_expression_or_labeled_statement(),
|
||||
};
|
||||
|
||||
// `/* #__PURE__ */ function foo() {}` - pure comment before non-expression statements cannot be applied.
|
||||
// Expression statements handle pure comments internally in `parse_assignment_expression_or_higher_impl`.
|
||||
if let Some(index) = pure_comment_index
|
||||
&& !matches!(stmt, Statement::ExpressionStatement(_))
|
||||
{
|
||||
self.lexer.trivia_builder.mark_pure_comment_not_applied(index);
|
||||
}
|
||||
|
||||
if has_no_side_effects_comment {
|
||||
Self::set_pure_on_function_stmt(&mut stmt);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ pub struct LexerCheckpoint<'a> {
|
||||
token: Token,
|
||||
errors_snapshot: ErrorSnapshot<'a>,
|
||||
tokens_len: usize,
|
||||
pure_comment: Option<usize>,
|
||||
pure_comments: Option<(usize, usize)>,
|
||||
has_no_side_effects_comment: bool,
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ impl<'a, C: Config> Lexer<'a, C> {
|
||||
token: self.token,
|
||||
errors_snapshot,
|
||||
tokens_len: self.tokens.len(),
|
||||
pure_comment: self.trivia_builder.pure_comment,
|
||||
pure_comments: self.trivia_builder.previous_token_pure_comments(),
|
||||
has_no_side_effects_comment: self.trivia_builder.has_no_side_effects_comment,
|
||||
}
|
||||
}
|
||||
@@ -216,7 +216,7 @@ impl<'a, C: Config> Lexer<'a, C> {
|
||||
token: self.token,
|
||||
errors_snapshot,
|
||||
tokens_len: self.tokens.len(),
|
||||
pure_comment: self.trivia_builder.pure_comment,
|
||||
pure_comments: self.trivia_builder.previous_token_pure_comments(),
|
||||
has_no_side_effects_comment: self.trivia_builder.has_no_side_effects_comment,
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@ impl<'a, C: Config> Lexer<'a, C> {
|
||||
self.tokens.truncate(checkpoint.tokens_len);
|
||||
self.source.set_position(checkpoint.source_position);
|
||||
self.token = checkpoint.token;
|
||||
self.trivia_builder.pure_comment = checkpoint.pure_comment;
|
||||
self.trivia_builder.set_pure_comments(checkpoint.pure_comments);
|
||||
self.trivia_builder.has_no_side_effects_comment = checkpoint.has_no_side_effects_comment;
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ impl<'a, C: Config> Lexer<'a, C> {
|
||||
/// Whitespace and line terminators are skipped
|
||||
#[inline] // Make sure is inlined into `next_token`
|
||||
fn read_next_token(&mut self) -> Kind {
|
||||
self.trivia_builder.pure_comment = None;
|
||||
self.trivia_builder.clear_pure_comments();
|
||||
self.trivia_builder.has_no_side_effects_comment = false;
|
||||
|
||||
let end_pos = self.source.end();
|
||||
@@ -544,7 +544,7 @@ impl<'a, C: Config> Lexer<'a, C> {
|
||||
|
||||
#[inline]
|
||||
fn read_next_jsx_attribute_value(&mut self) -> Kind {
|
||||
self.trivia_builder.pure_comment = None;
|
||||
self.trivia_builder.clear_pure_comments();
|
||||
self.trivia_builder.has_no_side_effects_comment = false;
|
||||
|
||||
let end_pos = self.source.end();
|
||||
|
||||
@@ -29,8 +29,8 @@ pub struct TriviaBuilder<'a> {
|
||||
/// Previous token, used to attach trailing comments to its end.
|
||||
previous_token: Token,
|
||||
|
||||
/// Index of the pure comment in `comments` vec, or `None` if no pure comment for the current token.
|
||||
pub(super) pure_comment: Option<usize>,
|
||||
/// Range of comments preceding the current token that contains pure comments.
|
||||
pure_comments: Option<(usize, usize)>,
|
||||
|
||||
pub(super) has_no_side_effects_comment: bool,
|
||||
}
|
||||
@@ -46,30 +46,32 @@ impl<'a> TriviaBuilder<'a> {
|
||||
saw_newline: true,
|
||||
saw_newline_for_comment: true,
|
||||
previous_token,
|
||||
pure_comment: None,
|
||||
pure_comments: None,
|
||||
has_no_side_effects_comment: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous_token_has_pure_comment(&self) -> Option<usize> {
|
||||
self.pure_comment
|
||||
pub fn previous_token_pure_comments(&self) -> Option<(usize, usize)> {
|
||||
self.pure_comments
|
||||
}
|
||||
|
||||
pub fn previous_token_has_no_side_effects_comment(&self) -> bool {
|
||||
self.has_no_side_effects_comment
|
||||
}
|
||||
|
||||
pub fn mark_pure_comment_not_applied(&mut self, index: usize) {
|
||||
if let Some(comment) = self.comments.get_mut(index) {
|
||||
debug_assert!(comment.is_pure());
|
||||
comment.content = CommentContent::PureNotApplied;
|
||||
}
|
||||
pub(super) fn set_pure_comments(&mut self, pure_comments: Option<(usize, usize)>) {
|
||||
self.pure_comments = pure_comments;
|
||||
}
|
||||
|
||||
/// Mark the current token's pure comment (if any) as not applied.
|
||||
pub fn mark_current_pure_comment_not_applied(&mut self) {
|
||||
if let Some(index) = self.pure_comment {
|
||||
self.mark_pure_comment_not_applied(index);
|
||||
pub(super) fn clear_pure_comments(&mut self) {
|
||||
self.pure_comments = None;
|
||||
}
|
||||
|
||||
pub fn mark_pure_comments_applied(&mut self, (start, end): (usize, usize)) {
|
||||
for comment in &mut self.comments[start..end] {
|
||||
if comment.content == CommentContent::PureNotApplied {
|
||||
comment.content = CommentContent::Pure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,10 +253,14 @@ impl<'a> TriviaBuilder<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Update `pure_comment` / `has_no_side_effects_comment` to point to the comment at `index`.
|
||||
/// Update annotation state for the comment at `index`.
|
||||
fn set_annotation_flags(&mut self, comment: &Comment, index: usize) {
|
||||
if comment.is_pure() {
|
||||
self.pure_comment = Some(index);
|
||||
if comment.content == CommentContent::PureNotApplied {
|
||||
if let Some((_, end)) = &mut self.pure_comments {
|
||||
*end = index + 1;
|
||||
} else {
|
||||
self.pure_comments = Some((index, index + 1));
|
||||
}
|
||||
} else if comment.is_no_side_effects() {
|
||||
self.has_no_side_effects_comment = true;
|
||||
}
|
||||
@@ -269,7 +275,13 @@ impl<'a> TriviaBuilder<'a> {
|
||||
{
|
||||
// Duplicate from parser lookahead/rewind — update annotation flags
|
||||
// to point to the existing comment.
|
||||
self.set_annotation_flags(&comment, self.comments.len() - 1);
|
||||
if let Ok(index) = self
|
||||
.comments
|
||||
.binary_search_by_key(&comment.span.start, |existing| existing.span.start)
|
||||
&& self.comments[index].span == comment.span
|
||||
{
|
||||
self.set_annotation_flags(&comment, index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -430,7 +442,7 @@ impl<'a> TriviaBuilder<'a> {
|
||||
if start < bytes.len() && bytes[start..].starts_with(b"__") {
|
||||
let rest = &bytes[start + 2..];
|
||||
if rest.starts_with(b"PURE__") {
|
||||
comment.content = CommentContent::Pure;
|
||||
comment.content = CommentContent::PureNotApplied;
|
||||
return;
|
||||
} else if rest.starts_with(b"NO_SIDE_EFFECTS__") {
|
||||
comment.content = CommentContent::NoSideEffects;
|
||||
@@ -517,6 +529,14 @@ mod test {
|
||||
ret.program.comments.iter().copied().collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn get_comments_typescript(source_text: &str) -> Vec<Comment> {
|
||||
let allocator = Allocator::default();
|
||||
let source_type = SourceType::default().with_typescript(true);
|
||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||
assert!(ret.diagnostics.is_empty());
|
||||
ret.program.comments.iter().copied().collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_attachments() {
|
||||
let source_text = "
|
||||
@@ -889,21 +909,79 @@ function bar() {}";
|
||||
"const foo /* #__PURE__ */ = pureOperation();",
|
||||
// Pure comment before object literal (triggers parser lookahead/rewind for arrow detection)
|
||||
"export const X = /* @__PURE__ */ { a: 1 };",
|
||||
"foo /* #__PURE__ */ = pureOperation();",
|
||||
"foo /* #__PURE__ */ + pureOperation();",
|
||||
"foo /* #__PURE__ */ && pureOperation();",
|
||||
"foo /* #__PURE__ */ in pureOperation();",
|
||||
"foo /* #__PURE__ */ as T;",
|
||||
"foo /* #__PURE__ */ satisfies T;",
|
||||
"foo /* #__PURE__ */ ? bar : baz;",
|
||||
"foo /* #__PURE__ */, bar;",
|
||||
"({ x /*#__PURE__*/: sideEffect() });",
|
||||
"true ? x /*#__PURE__*/ : sideEffect();",
|
||||
"foo /* #__PURE__ */++;",
|
||||
"foo /* #__PURE__ */--;",
|
||||
"foo /* #__PURE__ */.bar;",
|
||||
"foo /* #__PURE__ */[bar];",
|
||||
"foo /* #__PURE__ */();",
|
||||
"foo /* #__PURE__ */?.bar;",
|
||||
"foo /* #__PURE__ */!;",
|
||||
r"foo /* #__PURE__ */ `bar`;",
|
||||
"42 /* #__PURE__ */ + foo;",
|
||||
"(foo) /* #__PURE__ */ + bar;",
|
||||
"[foo] /* #__PURE__ */ + bar;",
|
||||
"foo\n/* #__PURE__ */ + bar;",
|
||||
"foo // #__PURE__\n+ bar;",
|
||||
"(a = foo /* #__PURE__ */ + bar)",
|
||||
];
|
||||
for source_text in cases {
|
||||
let comments = get_comments(source_text);
|
||||
let comments = get_comments_typescript(source_text);
|
||||
assert_eq!(comments[0].content, CommentContent::PureNotApplied, "{source_text}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_comment_applied_after_lookahead() {
|
||||
// `export const X = /* @__PURE__ */ foo()` triggers arrow-function lookahead
|
||||
// due to the `{`-ambiguity path. The pure comment must still be correctly
|
||||
// applied to the call expression after the parser rewinds.
|
||||
let source_text = "export const X = /* @__PURE__ */ foo();";
|
||||
// The ordinary comment is read during arrow-function lookahead before the parser rewinds.
|
||||
// Re-lexing the pure comment must recover its index rather than the later comment's index.
|
||||
let source_text = "export const X = /* @__PURE__ */ foo(/* comment */);";
|
||||
let comments = get_comments(source_text);
|
||||
assert_eq!(comments.len(), 2);
|
||||
assert_eq!(comments[0].content, CommentContent::Pure, "{source_text}");
|
||||
assert_eq!(comments[1].content, CommentContent::None, "{source_text}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_comment_applied() {
|
||||
let cases = [
|
||||
"/* #__PURE__ */ foo();",
|
||||
"/* #__PURE__ */ (foo)();",
|
||||
"/* #__PURE__ */ (new Foo());",
|
||||
"a + /* #__PURE__ */ <T>foo(), bar;",
|
||||
"x = /* #__PURE__ */ pureOperation() || y;",
|
||||
"y || (x = /* #__PURE__ */ pureOperation());",
|
||||
"y || /* #__PURE__ */ pureOperation();",
|
||||
"function f() {} /* #__PURE__ */ (foo)();",
|
||||
"/*#__PURE__*/ [foo][0]()",
|
||||
"if (cond) /*#__PURE__*/ (foo)();",
|
||||
"foo++\n/*#__PURE__*/ (bar)();",
|
||||
"function f() {} /*#__PURE__*/ [foo][0]();",
|
||||
];
|
||||
for source_text in cases {
|
||||
let comments = get_comments_typescript(source_text);
|
||||
assert_eq!(comments[0].content, CommentContent::Pure, "{source_text}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_pure_comments_applied() {
|
||||
let source_text = "export const X = /*#__PURE__*/ /* comment */ /*@__PURE__*/ foo();";
|
||||
let comments = get_comments(source_text);
|
||||
|
||||
assert_eq!(comments.len(), 3);
|
||||
assert_eq!(comments[0].content, CommentContent::Pure);
|
||||
assert_eq!(comments[1].content, CommentContent::None);
|
||||
assert_eq!(comments[2].content, CommentContent::Pure);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -927,9 +1005,8 @@ function bar() {}";
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_comment_not_applied_marks_correct_comment() {
|
||||
fn pure_comments_are_applied_independently() {
|
||||
// The first pure comment is invalid (before `foo`), the second is valid (before `bar()`).
|
||||
// `mark_pure_comment_not_applied` must retag the first comment, not the second.
|
||||
let source_text = "/*#__PURE__*/ foo + /*#__PURE__*/ bar()";
|
||||
let comments = get_comments(source_text);
|
||||
assert_eq!(
|
||||
@@ -974,9 +1051,9 @@ function bar() {}";
|
||||
("/* @vite-xxx */", CommentContent::Vite),
|
||||
("/* webpackChunkName: 'my-chunk-name' */", CommentContent::Webpack),
|
||||
("/* webpack */", CommentContent::None),
|
||||
("/* @__PURE__ */", CommentContent::Pure),
|
||||
("/* @__PURE__ */", CommentContent::PureNotApplied),
|
||||
("/* @__NO_SIDE_EFFECTS__ */", CommentContent::NoSideEffects),
|
||||
("/* #__PURE__ */", CommentContent::Pure),
|
||||
("/* #__PURE__ */", CommentContent::PureNotApplied),
|
||||
("/* #__NO_SIDE_EFFECTS__ */", CommentContent::NoSideEffects),
|
||||
("/* @__KEY__ */", CommentContent::PropertyKey),
|
||||
("/* #__KEY__ */", CommentContent::PropertyKey),
|
||||
|
||||
Reference in new issue
Block a user