Skip to content

Commit 932108d

Browse files
authored
fix: parse preserved magic comments (#14536)
1 parent b4fdefc commit 932108d

3 files changed

Lines changed: 113 additions & 26 deletions

File tree

crates/rspack_plugin_javascript/src/magic_comment.rs

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use std::{borrow::Cow, fmt};
1+
use std::{borrow::Cow, fmt, sync::LazyLock};
22

3+
use regex::Regex;
34
use rspack_core::DependencyRange;
45
use rspack_error::{Diagnostic, Error, Severity};
56
use rspack_regex::RspackRegex;
@@ -304,7 +305,11 @@ pub fn try_extract_magic_comment(
304305
/// Block comment text does not include `/*` and `*/`. We parse it by wrapping
305306
/// it as `({<comment_text>})`, so synthetic expression spans are offset by the
306307
/// two-byte `({` prefix.
307-
fn value_span_to_error_span(comment_span: Span, value_span: Span) -> Option<DependencyRange> {
308+
fn value_span_to_error_span_with_offset(
309+
comment_span: Span,
310+
value_span: Span,
311+
comment_offset: usize,
312+
) -> Option<DependencyRange> {
308313
// Block comment format: /* comment_text */
309314
// The comment_text doesn't include the "/*" and "*/" delimiters
310315
// So we need to add 2 bytes for "/*" to get the actual position in source
@@ -318,34 +323,49 @@ fn value_span_to_error_span(comment_span: Span, value_span: Span) -> Option<Depe
318323
}
319324

320325
let comment_start = comment_span.real_lo() as usize;
321-
let start = comment_start + BLOCK_COMMENT_START_LEN + value_start - OBJECT_LITERAL_PREFIX_LEN;
322-
let end = comment_start + BLOCK_COMMENT_START_LEN + value_end - OBJECT_LITERAL_PREFIX_LEN;
326+
let start = comment_start + BLOCK_COMMENT_START_LEN + comment_offset + value_start
327+
- OBJECT_LITERAL_PREFIX_LEN;
328+
let end = comment_start + BLOCK_COMMENT_START_LEN + comment_offset + value_end
329+
- OBJECT_LITERAL_PREFIX_LEN;
323330

324331
Some(DependencyRange::new(start as u32, end as u32))
325332
}
326333

327-
fn value_span_to_comment_offsets(comment_text: &str, value_span: Span) -> Option<(usize, usize)> {
334+
fn value_span_to_comment_offsets_with_offset(
335+
comment_text: &str,
336+
value_span: Span,
337+
comment_offset: usize,
338+
) -> Option<(usize, usize)> {
328339
const OBJECT_LITERAL_PREFIX_LEN: usize = 2; // Length of "({"
329340

330-
let start = value_span
331-
.real_lo()
332-
.checked_sub(OBJECT_LITERAL_PREFIX_LEN as u32)? as usize;
333-
let end = value_span
334-
.real_hi()
335-
.checked_sub(OBJECT_LITERAL_PREFIX_LEN as u32)? as usize;
341+
let start = comment_offset
342+
+ value_span
343+
.real_lo()
344+
.checked_sub(OBJECT_LITERAL_PREFIX_LEN as u32)? as usize;
345+
let end = comment_offset
346+
+ value_span
347+
.real_hi()
348+
.checked_sub(OBJECT_LITERAL_PREFIX_LEN as u32)? as usize;
336349

337350
(start <= end && end <= comment_text.len()).then_some((start, end))
338351
}
339352

340-
fn raw_value<'a>(comment_text: &'a str, value: &Expr) -> Option<&'a str> {
341-
let (start, end) = value_span_to_comment_offsets(comment_text, value.span())?;
353+
fn raw_value_with_offset<'a>(
354+
comment_text: &'a str,
355+
value: &Expr,
356+
comment_offset: usize,
357+
) -> Option<&'a str> {
358+
let (start, end) =
359+
value_span_to_comment_offsets_with_offset(comment_text, value.span(), comment_offset)?;
342360
comment_text.get(start..end).map(str::trim)
343361
}
344362

345363
fn parse_magic_comment_object<'a>(
346364
allocator: &'a Allocator,
347365
comment_text: &str,
348-
) -> Option<Expr<'a>> {
366+
) -> Option<(Expr<'a>, usize)> {
367+
let magic_comment_start = find_magic_comment_start(comment_text)?;
368+
let comment_text = comment_text.get(magic_comment_start..)?;
349369
let source = format!("({{{comment_text}}})");
350370
let source = allocator.alloc_str(&source);
351371
let mut expr = parse_file_as_expr(
@@ -357,7 +377,20 @@ fn parse_magic_comment_object<'a>(
357377
)
358378
.ok()?;
359379
remove_paren(&mut expr, allocator, None);
360-
Some(expr)
380+
Some((expr, magic_comment_start))
381+
}
382+
383+
static WEBPACK_COMMENT_REGEXP: LazyLock<Regex> = LazyLock::new(|| {
384+
Regex::new(
385+
r#"(^|[^\w])(?P<key>(?:webpack|rspack)[A-Z][A-Za-z]+|"(?:webpack|rspack)[A-Z][A-Za-z]+"|'(?:webpack|rspack)[A-Z][A-Za-z]+')\s*:"#,
386+
)
387+
.expect("invalid regex")
388+
});
389+
390+
fn find_magic_comment_start(comment_text: &str) -> Option<usize> {
391+
WEBPACK_COMMENT_REGEXP
392+
.captures(comment_text)
393+
.and_then(|captures| captures.name("key").map(|matched| matched.start()))
361394
}
362395

363396
fn prop_name_to_str<'a>(name: &'a PropName<'a>) -> Option<Cow<'a, str>> {
@@ -401,9 +434,18 @@ fn is_number_expr(expr: &Expr) -> bool {
401434
}
402435
}
403436

437+
#[cfg(test)]
404438
fn expr_to_order_str<'a>(comment_text: &'a str, expr: &Expr) -> Option<&'a str> {
439+
expr_to_order_str_with_offset(comment_text, expr, 0)
440+
}
441+
442+
fn expr_to_order_str_with_offset<'a>(
443+
comment_text: &'a str,
444+
expr: &Expr,
445+
comment_offset: usize,
446+
) -> Option<&'a str> {
405447
if expr_to_bool(expr).is_some() || is_number_expr(expr) {
406-
raw_value(comment_text, expr)
448+
raw_value_with_offset(comment_text, expr, comment_offset)
407449
} else {
408450
None
409451
}
@@ -419,7 +461,11 @@ fn expr_to_regexp<'a>(expr: &'a Expr<'a>) -> Option<(&'a str, &'a str)> {
419461
}
420462
}
421463

422-
fn expr_to_magic_comment_value(comment_text: &str, expr: &Expr) -> Option<MagicCommentValue> {
464+
fn expr_to_magic_comment_value_with_offset(
465+
comment_text: &str,
466+
expr: &Expr,
467+
comment_offset: usize,
468+
) -> Option<MagicCommentValue> {
423469
if let Some(value) = expr_to_bool(expr) {
424470
return Some(MagicCommentValue::Bool(value));
425471
}
@@ -429,7 +475,8 @@ fn expr_to_magic_comment_value(comment_text: &str, expr: &Expr) -> Option<MagicC
429475
}
430476

431477
if is_number_expr(expr) {
432-
return raw_value(comment_text, expr).map(|value| MagicCommentValue::Number(value.to_string()));
478+
return raw_value_with_offset(comment_text, expr, comment_offset)
479+
.map(|value| MagicCommentValue::Number(value.to_string()));
433480
}
434481

435482
if let Some((source, flags)) = expr_to_regexp(expr) {
@@ -530,7 +577,7 @@ fn analyze_comments(
530577
continue;
531578
}
532579
parsed_comment.insert(comment.span);
533-
let Some(expr) = parse_magic_comment_object(allocator, &comment.text) else {
580+
let Some((expr, comment_offset)) = parse_magic_comment_object(allocator, &comment.text) else {
534581
continue;
535582
};
536583
let Expr::Object(object) = &expr else {
@@ -551,9 +598,11 @@ fn analyze_comments(
551598
};
552599
let value = &prop.value;
553600
let item_name = rspack_comment.prefixed_name(prefix);
554-
let received = raw_value(&comment.text, value).unwrap_or_default();
601+
let received =
602+
raw_value_with_offset(&comment.text, value, comment_offset).unwrap_or_default();
555603
let item_span =
556-
value_span_to_error_span(comment.span, value.span()).unwrap_or(error_span.into());
604+
value_span_to_error_span_with_offset(comment.span, value.span(), comment_offset)
605+
.unwrap_or(error_span.into());
557606
let push_parse_warning = |comment_type| {
558607
push_magic_comment_parse_warning(
559608
source,
@@ -575,7 +624,7 @@ fn analyze_comments(
575624
}
576625
}
577626
RspackComment::Prefetch => {
578-
if let Some(value) = expr_to_order_str(&comment.text, value) {
627+
if let Some(value) = expr_to_order_str_with_offset(&comment.text, value, comment_offset) {
579628
if value == "true" {
580629
MagicCommentValue::Bool(true)
581630
} else {
@@ -587,7 +636,7 @@ fn analyze_comments(
587636
}
588637
}
589638
RspackComment::Preload => {
590-
if let Some(value) = expr_to_order_str(&comment.text, value) {
639+
if let Some(value) = expr_to_order_str_with_offset(&comment.text, value, comment_offset) {
591640
if value == "true" {
592641
MagicCommentValue::Bool(true)
593642
} else {
@@ -602,8 +651,9 @@ fn analyze_comments(
602651
if let Some(value) = expr_to_bool(value) {
603652
MagicCommentValue::Bool(value)
604653
} else {
605-
let value = expr_to_magic_comment_value(&comment.text, value)
606-
.unwrap_or(MagicCommentValue::Unknown);
654+
let value =
655+
expr_to_magic_comment_value_with_offset(&comment.text, value, comment_offset)
656+
.unwrap_or(MagicCommentValue::Unknown);
607657
push_parse_warning("a boolean");
608658
value
609659
}
@@ -679,7 +729,7 @@ mod tests_extract_magic_comment_object {
679729

680730
fn with_value<R>(raw: &str, name: &str, f: impl FnOnce(&Expr<'_>) -> Option<R>) -> Option<R> {
681731
let allocator = Allocator::new();
682-
let expr = parse_magic_comment_object(&allocator, raw)?;
732+
let (expr, _) = parse_magic_comment_object(&allocator, raw)?;
683733
let Expr::Object(object) = &expr else {
684734
return None;
685735
};
@@ -944,6 +994,19 @@ mod tests_extract_magic_comment_object {
944994
assert_eq!(comments.get_exports(), Some(vec!["a".into(), "b".into()]));
945995
}
946996

997+
#[test]
998+
fn test_extract_preserved_magic_comments() {
999+
let (comments, warnings) = extract("@preserve webpackIgnore: true");
1000+
1001+
assert!(warnings.is_empty());
1002+
assert_eq!(comments.get_ignore(), Some(true));
1003+
1004+
let (comments, warnings) = extract("@license MIT webpackIgnore: true");
1005+
1006+
assert!(warnings.is_empty());
1007+
assert_eq!(comments.get_ignore(), Some(true));
1008+
}
1009+
9471010
#[test]
9481011
fn test_rspack_prefixed_magic_comments_override_webpack_prefixed_comments() {
9491012
let (comments, warnings) = extract(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
it("should honor webpackIgnore when preserved by annotation comments", () => {
5+
const source = fs.readFileSync(path.join(__dirname, "bundle0.js"), "utf-8");
6+
7+
expect(source).toMatch(`import(/* @preserve webpackIgnore: true */ url)`);
8+
expect(source).toMatch(`import(/* @license webpackIgnore: true */ url)`);
9+
});
10+
11+
globalThis.__issue14533__ = {
12+
loadPreserve(url) {
13+
return import(/* @preserve webpackIgnore: true */ url);
14+
},
15+
loadLicense(url) {
16+
return import(/* @license webpackIgnore: true */ url);
17+
}
18+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import("@rspack/core").Configuration} */
2+
module.exports = {
3+
output: {
4+
filename: 'bundle0.js',
5+
},
6+
};

0 commit comments

Comments
 (0)