@@ -289,136 +289,46 @@ impl SourceMapProcessor {
289289 /// Collect references to sourcemaps in minified source files
290290 /// and saves them in `self.sourcemap_references`.
291291 fn collect_sourcemap_references ( & mut self ) {
292- // Collect available sourcemaps
293- let sourcemaps: HashSet < _ > = self
292+ let sourcemaps = self
294293 . sources
295- . iter ( )
296- . map ( |x| x. 1 )
294+ . values ( )
297295 . filter ( |x| x. ty == SourceFileType :: SourceMap )
298296 . map ( |x| x. url . clone ( ) )
299- . collect ( ) ;
300-
301- let mut explicitly_associated_sourcemaps = HashMap :: new ( ) ;
297+ . collect :: < HashSet < _ > > ( ) ;
302298
303299 let ( sources_with_location, sources_without_location) = self
304300 . sources
305301 . values_mut ( )
306- . filter ( |source| source. ty == SourceFileType :: MinifiedSource )
307- . filter ( |source| !self . sourcemap_references . contains_key ( & source. url ) )
308- . filter_map ( |source| {
309- str:: from_utf8 ( & source. contents . clone ( ) )
310- . map ( |contents| {
311- (
312- source,
313- discover_sourcemaps_location ( contents)
314- . filter ( |loc| !is_remote_sourcemap ( loc) )
315- . map ( String :: from) ,
316- )
317- } )
318- . ok ( )
302+ . filter ( |source| {
303+ source. ty == SourceFileType :: MinifiedSource
304+ && !self . sourcemap_references . contains_key ( & source. url )
319305 } )
320- . fold (
321- ( HashMap :: new ( ) , HashSet :: new ( ) ) ,
322- |( mut sources_with_location, mut sources_without_location) , ( source, location) | {
323- match location {
324- Some ( location) => {
325- sources_with_location. insert ( source, location) ;
326- }
327- None => {
328- sources_without_location. insert ( source) ;
329- }
330- }
331- ( sources_with_location, sources_without_location)
332- } ,
333- ) ;
306+ . collect_sourcemap_locations ( ) ;
334307
335- // First pass: if location discovered, add to sourcemap_references
336- sources_with_location. iter ( ) . for_each ( |( source, location) | {
337- let full_sourcemap_path = source
338- . path
339- . parent ( )
340- . expect ( "source path has a parent" )
341- . join ( location) ;
342-
343- // Add location to already associated sourcemaps, so we cannot guess it again.
344- explicitly_associated_sourcemaps
345- . insert ( fs:: path_as_url ( & full_sourcemap_path) , source. url . clone ( ) ) ;
346-
347- self . sourcemap_references . insert (
348- source. url . clone ( ) ,
349- Some ( SourceMapReference :: from_url ( location. to_owned ( ) ) ) ,
350- ) ;
351- } ) ;
308+ // First pass: if location discovered, add to sourcemap_references. Also, keep track of
309+ // the sourcemaps we associate, and the source file we associate them with.
310+ let explicitly_associated_sourcemaps = sources_with_location
311+ . iter ( )
312+ . map ( |( source, location) | {
313+ let sourcemap_reference = SourceMapReference :: from_url ( location. clone ( ) ) ;
314+ let sourcemap_path = full_sourcemap_path ( source, location) ;
315+
316+ self . sourcemap_references
317+ . insert ( source. url . clone ( ) , Some ( sourcemap_reference) ) ;
318+
319+ ( fs:: path_as_url ( & sourcemap_path) , source. url . clone ( ) )
320+ } )
321+ . collect :: < HashMap < _ , _ > > ( ) ;
352322
353323 // Second pass: for remaining sourcemaps, try to guess the location
354- sources_without_location
355- . into_iter ( )
356- . fold (
357- // Collect sources guessed as associated with each sourcemap. This way, we ensure
358- // we only associate the sourcemap with any sources if it is only guessed once.
359- HashMap :: new ( ) ,
360- |mut sources_associated_with_sm, source| {
361- let sourcemap_reference = guess_sourcemap_reference ( & sourcemaps, & source. url )
362- . inspect_err ( |err| {
363- source. warn ( format ! (
364- "could not determine a source map reference ({err})"
365- ) ) ;
366- } )
367- . ok ( )
368- . filter ( |sourcemap_reference| {
369- explicitly_associated_sourcemaps
370- . get (
371- sourcemap_reference
372- . original_url
373- . as_ref ( )
374- . expect ( "original url set in guess_sourcemap_reference" ) ,
375- )
376- . inspect ( |url| {
377- source. warn ( format ! (
378- "based on the file name, we guessed a source map \
379- reference ({}), which is already associated with source \
380- {url}. Please explicitly set the sourcemap URL with a \
381- `//# sourceMappingURL=...` comment in the source file.",
382- sourcemap_reference. url
383- ) ) ;
384- } )
385- . is_none ( )
386- } ) ;
387-
388- if let Some ( sourcemap_reference) = sourcemap_reference {
389- sources_associated_with_sm
390- . entry ( sourcemap_reference)
391- . or_insert_with ( Vec :: new)
392- . push ( source) ;
393- } else {
394- self . sourcemap_references . insert ( source. url . clone ( ) , None ) ;
395- }
324+ let guessed_sourcemap_references = guess_sourcemap_references (
325+ sources_without_location,
326+ sourcemaps,
327+ explicitly_associated_sourcemaps,
328+ ) ;
396329
397- sources_associated_with_sm
398- } ,
399- )
400- . into_iter ( )
401- . for_each ( |( sourcemap_reference, mut sources) | {
402- if let [ source] = sources. as_slice ( ) {
403- // One source -> we can safely associate the sourcemap with it.
404- self . sourcemap_references
405- . insert ( source. url . clone ( ) , Some ( sourcemap_reference) ) ;
406- } else {
407- // Multiple sources -> it is unclear which source we should associate
408- // the sourcemap with, so don't associate it with any of them.
409- sources. iter_mut ( ) . for_each ( |source| {
410- source. warn ( format ! (
411- "Could not associate this source with a source map. We \
412- guessed the sourcemap reference {} for multiple sources, including \
413- this one. Please explicitly set the sourcemap URL with a \
414- `//# sourceMappingURL=...` comment in the source file, to make the \
415- association clear.",
416- sourcemap_reference. url
417- ) ) ;
418- self . sourcemap_references . insert ( source. url . clone ( ) , None ) ;
419- } ) ;
420- }
421- } ) ;
330+ self . sourcemap_references
331+ . extend ( guessed_sourcemap_references) ;
422332 }
423333
424334 pub fn dump_log ( & self , title : & str ) {
@@ -1173,6 +1083,164 @@ impl SourceMapProcessor {
11731083 }
11741084}
11751085
1086+ /// For a set of source files without a sourcemap location, guess the sourcemap references.
1087+ ///
1088+ /// Parameters:
1089+ /// - `sources_without_location`: The set of source files without a sourcemap location.
1090+ /// - `sourcemaps`: The set of available sourcemaps.
1091+ /// - `explicitly_associated_sourcemaps`: The set of sourcemaps that are explicitly associated
1092+ /// with a source file, and thus, cannot be guessed. If we guess such a sourcemap, we will
1093+ /// warn the user. This is stored in a map, where the key is the sourcemap URL, and the value
1094+ /// is the source file URL that is explicitly associated with the sourcemap.
1095+ ///
1096+ /// Returns:
1097+ /// - A map from sourcemap URLs to the sourcemap references, which may be `None` if we couldn't
1098+ /// guess the sourcemap reference.
1099+ fn guess_sourcemap_references (
1100+ sources_without_location : HashSet < & mut SourceFile > ,
1101+ sourcemaps : HashSet < String > ,
1102+ explicitly_associated_sourcemaps : HashMap < String , String > ,
1103+ ) -> HashMap < String , Option < SourceMapReference > > {
1104+ let mut sourcemap_references = HashMap :: new ( ) ;
1105+
1106+ sources_without_location
1107+ . into_iter ( )
1108+ . fold (
1109+ // Collect sources guessed as associated with each sourcemap. This way, we ensure
1110+ // we only associate the sourcemap with any sources if it is only guessed once.
1111+ HashMap :: new ( ) ,
1112+ |mut sources_associated_with_sm, source| {
1113+ let sourcemap_reference = guess_sourcemap_reference ( & sourcemaps, & source. url )
1114+ . inspect_err ( |err| {
1115+ source. warn ( format ! (
1116+ "could not determine a source map reference ({err})"
1117+ ) ) ;
1118+ } )
1119+ . ok ( )
1120+ . filter ( |sourcemap_reference| {
1121+ explicitly_associated_sourcemaps
1122+ . get (
1123+ sourcemap_reference
1124+ . original_url
1125+ . as_ref ( )
1126+ . expect ( "original url set in guess_sourcemap_reference" ) ,
1127+ )
1128+ . inspect ( |url| {
1129+ source. warn ( format ! (
1130+ "based on the file name, we guessed a source map \
1131+ reference ({}), which is already associated with source \
1132+ {url}. Please explicitly set the sourcemap URL with a \
1133+ `//# sourceMappingURL=...` comment in the source file.",
1134+ sourcemap_reference. url
1135+ ) ) ;
1136+ } )
1137+ . is_none ( )
1138+ } ) ;
1139+
1140+ if let Some ( sourcemap_reference) = sourcemap_reference {
1141+ sources_associated_with_sm
1142+ . entry ( sourcemap_reference)
1143+ . or_insert_with ( Vec :: new)
1144+ . push ( source) ;
1145+ } else {
1146+ sourcemap_references. insert ( source. url . clone ( ) , None ) ;
1147+ }
1148+
1149+ sources_associated_with_sm
1150+ } ,
1151+ )
1152+ . into_iter ( )
1153+ . for_each ( |( sourcemap_reference, mut sources) | {
1154+ if let [ source] = sources. as_slice ( ) {
1155+ // One source -> we can safely associate the sourcemap with it.
1156+ sourcemap_references. insert ( source. url . clone ( ) , Some ( sourcemap_reference) ) ;
1157+ } else {
1158+ // Multiple sources -> it is unclear which source we should associate
1159+ // the sourcemap with, so don't associate it with any of them.
1160+ sources. iter_mut ( ) . for_each ( |source| {
1161+ source. warn ( format ! (
1162+ "Could not associate this source with a source map. We \
1163+ guessed the sourcemap reference {} for multiple sources, including \
1164+ this one. Please explicitly set the sourcemap URL with a \
1165+ `//# sourceMappingURL=...` comment in the source file, to make the \
1166+ association clear.",
1167+ sourcemap_reference. url
1168+ ) ) ;
1169+ sourcemap_references. insert ( source. url . clone ( ) , None ) ;
1170+ } ) ;
1171+ }
1172+ } ) ;
1173+
1174+ sourcemap_references
1175+ }
1176+
1177+ /// Compute the full path to a sourcemap file, given the sourcemap's source file and the relative
1178+ /// path to the sourcemap from the source file.
1179+ fn full_sourcemap_path ( source : & SourceFile , sourcemap_relative_path : & String ) -> PathBuf {
1180+ let full_sourcemap_path = source
1181+ . path
1182+ . parent ( )
1183+ . expect ( "source path has a parent" )
1184+ . join ( sourcemap_relative_path) ;
1185+
1186+ full_sourcemap_path
1187+ }
1188+
1189+ /// A tuple of a map and a set, returned by `collect_sourcemap_locations`.
1190+ /// The map contains the sourcefiles for which we found a sourcemap location listed in the file, with
1191+ /// the sourcemap location as the value.
1192+ /// The set contains the sourcefiles for which we did not find a sourcemap location listed in the file.
1193+ type SourcemapLocations < ' s > = (
1194+ HashMap < & ' s mut SourceFile , String > ,
1195+ HashSet < & ' s mut SourceFile > ,
1196+ ) ;
1197+
1198+ trait SourcesIteratorExt < ' s > {
1199+ fn collect_sourcemap_locations ( self ) -> SourcemapLocations < ' s > ;
1200+ }
1201+
1202+ impl < ' s , I > SourcesIteratorExt < ' s > for I
1203+ where
1204+ I : IntoIterator < Item = & ' s mut SourceFile > ,
1205+ {
1206+ /// Consume this iterator of sources, collecting the sourcemap locations for them.
1207+ fn collect_sourcemap_locations ( self ) -> SourcemapLocations < ' s > {
1208+ self . into_iter ( )
1209+ . map ( |source| {
1210+ let location = location_from_contents ( & source. contents ) . map ( String :: from) ;
1211+ ( source, location)
1212+ } )
1213+ . fold (
1214+ ( HashMap :: new ( ) , HashSet :: new ( ) ) ,
1215+ |( mut sources_with_location, mut sources_without_location) , ( source, location) | {
1216+ match location {
1217+ Some ( location) => {
1218+ sources_with_location. insert ( source, location) ;
1219+ }
1220+ None => {
1221+ sources_without_location. insert ( source) ;
1222+ }
1223+ }
1224+ ( sources_with_location, sources_without_location)
1225+ } ,
1226+ )
1227+ }
1228+ }
1229+
1230+ /// Extract the sourcemap location from the contents of a source file.
1231+ fn location_from_contents ( contents : & [ u8 ] ) -> Option < & str > {
1232+ str:: from_utf8 ( contents)
1233+ . map ( discover_sourcemaps_location)
1234+ . ok ( )
1235+ . flatten ( )
1236+ // If this is a full external URL, the code above is going to attempt
1237+ // to "normalize" it with the source path, resulting in a bogus path
1238+ // like "path/to/source/dir/https://some-static-host.example.com/path/to/foo.js.map"
1239+ // that can't be resolved to a source map file.
1240+ // So, we filter out such locations.
1241+ . filter ( |loc| !is_remote_sourcemap ( loc) )
1242+ }
1243+
11761244fn adjust_regular_sourcemap (
11771245 sourcemap : & mut SourceMap ,
11781246 source_file : & mut SourceFile ,
0 commit comments