Skip to content

Commit 47ee423

Browse files
More refactor
1 parent 01e12e1 commit 47ee423

1 file changed

Lines changed: 186 additions & 118 deletions

File tree

src/utils/sourcemaps.rs

Lines changed: 186 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
11761244
fn adjust_regular_sourcemap(
11771245
sourcemap: &mut SourceMap,
11781246
source_file: &mut SourceFile,

0 commit comments

Comments
 (0)