intlayer_swc_plugin/paths.rs
1//! Path helpers used to build the module specifiers of the injected
2//! dictionary imports.
3
4use pathdiff::diff_paths;
5use std::{borrow::Cow, path::Path};
6
7/// Computes the module specifier for an injected dictionary import: the path
8/// of `dict_file_abs` relative to `from_dir_abs`, using forward slashes and a
9/// leading `./` when the path is not already relative. Falls back to the
10/// absolute path when no relative path exists (e.g. different drives).
11pub fn relative_import_path(dict_file_abs: &Path, from_dir_abs: &Path) -> String {
12 if let Some(relative) = diff_paths(dict_file_abs, from_dir_abs) {
13 let path = relative.to_string_lossy().replace('\\', "/");
14 if path.starts_with('.') {
15 path
16 } else {
17 format!("./{}", path)
18 }
19 } else {
20 dict_file_abs.to_string_lossy().replace('\\', "/")
21 }
22}
23
24/// Whether `path` is already normalised: forward slashes throughout and, if it
25/// carries a drive letter, a lower-case one.
26fn is_normalized(path: &str) -> bool {
27 if path.as_bytes().contains(&b'\\') {
28 return false;
29 }
30
31 !has_upper_case_drive_letter(path)
32}
33
34/// Whether `path` starts with an upper-case Windows drive letter (`C:`).
35fn has_upper_case_drive_letter(path: &str) -> bool {
36 let bytes = path.as_bytes();
37
38 bytes.len() >= 2 && bytes[1] == b':' && bytes[0].is_ascii_uppercase()
39}
40
41/// Normalises a path string to use forward slashes and consistent drive-letter
42/// casing so that [`pathdiff::diff_paths`] works correctly in Wasm / cross-platform
43/// contexts where Windows-style paths may arrive from the JS host.
44///
45/// Borrows when the path is already normalised — the case for every POSIX path,
46/// and the allowlist is re-scanned for each compiled file, so allocating there
47/// would cost one `String` per entry per file.
48pub fn normalize_path(path: &str) -> Cow<'_, str> {
49 if is_normalized(path) {
50 return Cow::Borrowed(path);
51 }
52
53 let mut normalized = path.replace('\\', "/");
54
55 if has_upper_case_drive_letter(&normalized) {
56 normalized[0..1].make_ascii_lowercase();
57 }
58
59 Cow::Owned(normalized)
60}