1- use std:: { borrow:: Cow , fmt} ;
1+ use std:: { borrow:: Cow , fmt, sync :: LazyLock } ;
22
3+ use regex:: Regex ;
34use rspack_core:: DependencyRange ;
45use rspack_error:: { Diagnostic , Error , Severity } ;
56use 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
345363fn 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
363396fn 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) ]
404438fn 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 (
0 commit comments