1use std::path::{Path, PathBuf};
8
9pub fn normalize_for_module_path(file_path: &Path, workspace_root: &Path) -> PathBuf {
18 if file_path.is_relative() {
19 workspace_root.join(file_path)
20 } else {
21 file_path.to_path_buf()
22 }
23}
24
25pub fn strip_source_root<'a>(path: &'a Path, source_roots: &[&str]) -> &'a Path {
37 for root in source_roots {
38 if let Ok(stripped) = path.strip_prefix(root) {
39 return stripped;
40 }
41 }
42 path
43}
44
45pub fn strip_source_root_owned(path: &Path, source_roots: &[&str]) -> PathBuf {
49 strip_source_root(path, source_roots).to_path_buf()
50}
51
52pub fn strip_extension<'a>(path_str: &'a str, extensions: &[&str]) -> &'a str {
64 for ext in extensions {
65 let suffix = format!(".{ext}");
67 if let Some(stripped) = path_str.strip_suffix(&suffix) {
68 return stripped;
69 }
70 }
71 path_str
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use tempfile::TempDir;
78
79 #[test]
80 fn test_normalize_relative_path() {
81 let file_path = Path::new("src/foo/bar.rs");
82 let temp_dir = TempDir::new().unwrap();
83 let workspace_root = temp_dir.path();
84
85 let result = normalize_for_module_path(file_path, workspace_root);
86
87 assert!(result.is_absolute());
88 assert!(result.ends_with("src/foo/bar.rs"));
89 }
90
91 #[test]
92 fn test_normalize_absolute_path() {
93 let temp_dir = TempDir::new().unwrap();
94 let workspace_root = temp_dir.path();
95 let file_path = workspace_root.join("src/foo/bar.rs");
96
97 let result = normalize_for_module_path(&file_path, workspace_root);
98
99 assert_eq!(result, file_path);
100 }
101
102 #[test]
103 fn test_strip_source_root_matches_first() {
104 let path = Path::new("src/foo/bar.rs");
105 let source_roots = &["src", "lib", "app"];
106
107 let result = strip_source_root(path, source_roots);
108
109 assert_eq!(result, Path::new("foo/bar.rs"));
110 }
111
112 #[test]
113 fn test_strip_source_root_matches_second() {
114 let path = Path::new("lib/utils/helper.rs");
115 let source_roots = &["src", "lib", "app"];
116
117 let result = strip_source_root(path, source_roots);
118
119 assert_eq!(result, Path::new("utils/helper.rs"));
120 }
121
122 #[test]
123 fn test_strip_source_root_no_match() {
124 let path = Path::new("tests/integration.rs");
125 let source_roots = &["src", "lib", "app"];
126
127 let result = strip_source_root(path, source_roots);
128
129 assert_eq!(result, path);
130 }
131
132 #[test]
133 fn test_strip_source_root_empty_roots() {
134 let path = Path::new("src/foo/bar.rs");
135 let source_roots: &[&str] = &[];
136
137 let result = strip_source_root(path, source_roots);
138
139 assert_eq!(result, path);
140 }
141
142 #[test]
143 fn test_strip_extension_simple() {
144 assert_eq!(strip_extension("foo.rs", &["rs"]), "foo");
145 assert_eq!(strip_extension("bar.py", &["py", "pyi"]), "bar");
146 }
147
148 #[test]
149 fn test_strip_extension_compound_typescript() {
150 let ts_extensions = &["d.ts", "tsx", "ts", "mts", "cts"];
152 assert_eq!(strip_extension("types.d.ts", ts_extensions), "types");
153 assert_eq!(strip_extension("component.tsx", ts_extensions), "component");
154 assert_eq!(strip_extension("main.ts", ts_extensions), "main");
155 }
156
157 #[test]
158 fn test_strip_extension_compound_php() {
159 let php_extensions = &["class.php", "inc.php", "php", "inc"];
161 assert_eq!(strip_extension("User.class.php", php_extensions), "User");
162 assert_eq!(strip_extension("config.inc.php", php_extensions), "config");
163 assert_eq!(strip_extension("index.php", php_extensions), "index");
164 }
165
166 #[test]
167 fn test_strip_extension_no_match() {
168 assert_eq!(strip_extension("README.md", &["rs", "py"]), "README.md");
169 assert_eq!(strip_extension("no_extension", &["rs"]), "no_extension");
170 }
171
172 #[test]
173 fn test_strip_extension_priority() {
174 let extensions = &["ts", "d.ts"]; assert_eq!(strip_extension("types.d.ts", extensions), "types.d");
177
178 let extensions = &["d.ts", "ts"];
180 assert_eq!(strip_extension("types.d.ts", extensions), "types");
181 }
182}