@@ -276,6 +276,9 @@ pub struct ModuleInfo {
276276 /// or `@import { SomeType } from "npm:some-module"`.
277277 #[ serde( skip_serializing_if = "Vec::is_empty" , default ) ]
278278 pub jsdoc_imports : Vec < JsDocImportInfo > ,
279+ /// Source map URL extracted from sourceMappingURL comment
280+ #[ serde( skip_serializing_if = "Option::is_none" , default ) ]
281+ pub source_map_url : Option < SpecifierWithRange > ,
279282}
280283
281284fn is_false ( v : & bool ) -> bool {
@@ -442,6 +445,14 @@ pub fn find_jsx_import_source_types(text: &str) -> Option<regex::Match<'_>> {
442445 . and_then ( |c| c. get ( 1 ) )
443446}
444447
448+ /// Matches the `sourceMappingURL` comment.
449+ pub fn find_source_mapping_url ( text : & str ) -> Option < regex:: Match < ' _ > > {
450+ static SOURCE_MAPPING_URL_RE : Lazy < Regex > = Lazy :: new ( || {
451+ Regex :: new ( r"(?i)^[#@]\s*sourceMappingURL\s*=\s*(\S+)" ) . unwrap ( )
452+ } ) ;
453+ SOURCE_MAPPING_URL_RE . captures ( text) . and_then ( |c| c. get ( 1 ) )
454+ }
455+
445456/// Matches the `@ts-self-types` pragma.
446457pub fn find_ts_self_types ( text : & str ) -> Option < regex:: Match < ' _ > > {
447458 static TS_SELF_TYPES_RE : Lazy < Regex > = Lazy :: new ( || {
@@ -505,6 +516,7 @@ mod test {
505516 jsx_import_source : None ,
506517 jsx_import_source_types : None ,
507518 jsdoc_imports : Vec :: new ( ) ,
519+ source_map_url : None ,
508520 } ;
509521 run_serialization_test ( & module_info, json ! ( { } ) ) ;
510522 }
@@ -574,6 +586,7 @@ mod test {
574586 jsx_import_source : None ,
575587 jsx_import_source_types : None ,
576588 jsdoc_imports : Vec :: new ( ) ,
589+ source_map_url : None ,
577590 } ;
578591 run_serialization_test (
579592 & module_info,
@@ -659,6 +672,7 @@ mod test {
659672 jsx_import_source : None ,
660673 jsx_import_source_types : None ,
661674 jsdoc_imports : Vec :: new ( ) ,
675+ source_map_url : None ,
662676 } ;
663677 run_serialization_test (
664678 & module_info,
@@ -704,6 +718,7 @@ mod test {
704718 jsx_import_source : None ,
705719 jsx_import_source_types : None ,
706720 jsdoc_imports : Vec :: new ( ) ,
721+ source_map_url : None ,
707722 } ;
708723 run_serialization_test (
709724 & module_info,
@@ -734,6 +749,7 @@ mod test {
734749 } ) ,
735750 jsx_import_source_types : None ,
736751 jsdoc_imports : Vec :: new ( ) ,
752+ source_map_url : None ,
737753 } ;
738754 run_serialization_test (
739755 & module_info,
@@ -764,6 +780,7 @@ mod test {
764780 } ,
765781 } ) ,
766782 jsdoc_imports : Vec :: new ( ) ,
783+ source_map_url : None ,
767784 } ;
768785 run_serialization_test (
769786 & module_info,
@@ -819,6 +836,7 @@ mod test {
819836 resolution_mode : Some ( TypeScriptTypesResolutionMode :: Require ) ,
820837 } ,
821838 ] ) ,
839+ source_map_url : None ,
822840 } ;
823841 run_serialization_test (
824842 & module_info,
@@ -1053,6 +1071,7 @@ mod test {
10531071 jsx_import_source : None ,
10541072 jsx_import_source_types : None ,
10551073 jsdoc_imports : Vec :: new ( ) ,
1074+ source_map_url : None ,
10561075 } ;
10571076 let json = json ! ( {
10581077 "dependencies" : [ {
@@ -1097,6 +1116,7 @@ mod test {
10971116 jsx_import_source : None ,
10981117 jsx_import_source_types : None ,
10991118 jsdoc_imports : Vec :: new ( ) ,
1119+ source_map_url : None ,
11001120 } ;
11011121 let json = json ! ( {
11021122 "dependencies" : [ {
@@ -1109,6 +1129,36 @@ mod test {
11091129 run_v1_deserialization_test ( json, & expected) ;
11101130 }
11111131
1132+ #[ test]
1133+ fn module_info_serialization_source_map_url ( ) {
1134+ let module_info = ModuleInfo {
1135+ is_script : false ,
1136+ dependencies : Vec :: new ( ) ,
1137+ ts_references : Vec :: new ( ) ,
1138+ self_types_specifier : None ,
1139+ jsx_import_source : None ,
1140+ jsx_import_source_types : None ,
1141+ jsdoc_imports : Vec :: new ( ) ,
1142+ source_map_url : Some ( SpecifierWithRange {
1143+ text : "file.js.map" . to_string ( ) ,
1144+ range : PositionRange {
1145+ start : Position :: zeroed ( ) ,
1146+ end : Position :: zeroed ( ) ,
1147+ } ,
1148+ } ) ,
1149+ } ;
1150+
1151+ run_serialization_test (
1152+ & module_info,
1153+ json ! ( {
1154+ "sourceMapUrl" : {
1155+ "text" : "file.js.map" ,
1156+ "range" : [ [ 0 , 0 ] , [ 0 , 0 ] ] ,
1157+ }
1158+ } ) ,
1159+ ) ;
1160+ }
1161+
11121162 #[ track_caller]
11131163 fn run_serialization_test <
11141164 T : DeserializeOwned + Serialize + std:: fmt:: Debug + PartialEq + Eq ,
@@ -1133,4 +1183,59 @@ mod test {
11331183 let deserialized_value = serde_json:: from_value :: < T > ( json) . unwrap ( ) ;
11341184 assert_eq ! ( deserialized_value, * value) ;
11351185 }
1186+
1187+ #[ test]
1188+ fn test_find_source_mapping_url ( ) {
1189+ // Test with # prefix
1190+ let m = find_source_mapping_url ( "# sourceMappingURL=file.js.map" ) ;
1191+ assert ! ( m. is_some( ) ) ;
1192+ assert_eq ! ( m. unwrap( ) . as_str( ) , "file.js.map" ) ;
1193+
1194+ // Test with @ prefix
1195+ let m = find_source_mapping_url ( "@ sourceMappingURL=file.js.map" ) ;
1196+ assert ! ( m. is_some( ) ) ;
1197+ assert_eq ! ( m. unwrap( ) . as_str( ) , "file.js.map" ) ;
1198+
1199+ // Test case insensitivity
1200+ let m = find_source_mapping_url ( "# SOURCEMAPPINGURL=file.js.map" ) ;
1201+ assert ! ( m. is_some( ) ) ;
1202+ assert_eq ! ( m. unwrap( ) . as_str( ) , "file.js.map" ) ;
1203+
1204+ // Test with no spaces
1205+ let m = find_source_mapping_url ( "#sourceMappingURL=file.js.map" ) ;
1206+ assert ! ( m. is_some( ) ) ;
1207+ assert_eq ! ( m. unwrap( ) . as_str( ) , "file.js.map" ) ;
1208+
1209+ // Test with multiple spaces
1210+ let m = find_source_mapping_url ( "# sourceMappingURL = file.js.map" ) ;
1211+ assert ! ( m. is_some( ) ) ;
1212+ assert_eq ! ( m. unwrap( ) . as_str( ) , "file.js.map" ) ;
1213+
1214+ // Test with URL containing query params
1215+ let m = find_source_mapping_url ( "# sourceMappingURL=file.js.map?v=123" ) ;
1216+ assert ! ( m. is_some( ) ) ;
1217+ assert_eq ! ( m. unwrap( ) . as_str( ) , "file.js.map?v=123" ) ;
1218+
1219+ // Test with data URL
1220+ let m = find_source_mapping_url (
1221+ "# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==" ,
1222+ ) ;
1223+ assert ! ( m. is_some( ) ) ;
1224+ assert_eq ! (
1225+ m. unwrap( ) . as_str( ) ,
1226+ "data:application/json;base64,eyJ2ZXJzaW9uIjozfQ=="
1227+ ) ;
1228+
1229+ // Test with relative path
1230+ let m = find_source_mapping_url ( "# sourceMappingURL=../maps/file.js.map" ) ;
1231+ assert ! ( m. is_some( ) ) ;
1232+ assert_eq ! ( m. unwrap( ) . as_str( ) , "../maps/file.js.map" ) ;
1233+
1234+ // Test invalid formats
1235+ assert ! ( find_source_mapping_url( "sourceMappingURL=file.js.map" ) . is_none( ) ) ;
1236+ assert ! (
1237+ find_source_mapping_url( "// sourceMappingURL=file.js.map" ) . is_none( )
1238+ ) ;
1239+ assert ! ( find_source_mapping_url( "# sourceMappingURL" ) . is_none( ) ) ;
1240+ }
11361241}
0 commit comments