1use crate::models::Fragment;
10
11pub fn clean_source_id(source_id: &str) -> &str {
18 match source_id.rfind(':') {
19 Some(pos) if pos > 0 => {
20 let bytes = source_id.as_bytes();
21 let before = bytes[pos - 1];
22 if pos == 1 && before.is_ascii_alphabetic() {
25 return source_id;
26 }
27 if before == b'/' || before == b'\\' {
28 return source_id;
29 }
30 if matches!(bytes.get(pos + 1), Some(b'/') | Some(b'\\')) {
35 return source_id;
36 }
37 &source_id[..pos]
38 }
39 _ => source_id,
40 }
41}
42
43pub fn resolve_fragment_path(fragment: &Fragment) -> String {
47 let clean = clean_source_id(&fragment.source_id);
48 match &fragment.source_root {
49 Some(root) => {
50 let joined = std::path::Path::new(root).join(clean);
51 joined.to_string_lossy().into_owned()
52 }
53 None => clean.to_string(),
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use crate::models::Location;
61
62 fn frag(source_id: &str, source_root: Option<&str>) -> Fragment {
63 let loc = Location {
64 line: 1,
65 column: 0,
66 offset: 0,
67 };
68 Fragment {
69 source_id: source_id.to_string(),
70 source_root: source_root.map(str::to_string),
71 start: loc.clone(),
72 end: loc,
73 range: [0, 0],
74 blame: None,
75 }
76 }
77
78 #[test]
79 fn clean_source_id_strips_format_suffix() {
80 assert_eq!(clean_source_id("README.md:javascript"), "README.md");
81 }
82
83 #[test]
84 fn clean_source_id_preserves_bare_path() {
85 assert_eq!(clean_source_id("src/a.js"), "src/a.js");
86 }
87
88 #[test]
89 fn clean_source_id_preserves_windows_drive() {
90 assert_eq!(clean_source_id(r"C:\scan\a.rs"), r"C:\scan\a.rs");
91 }
92
93 #[test]
94 fn clean_source_id_strips_format_from_windows_path() {
95 assert_eq!(clean_source_id(r"C:\scan\a.md:javascript"), r"C:\scan\a.md");
96 }
97
98 #[test]
99 fn clean_source_id_preserves_windows_verbatim_path() {
100 assert_eq!(clean_source_id(r"\\?\C:\scan\a.rs"), r"\\?\C:\scan\a.rs");
105 }
106
107 #[test]
108 fn clean_source_id_strips_format_from_windows_verbatim_path() {
109 assert_eq!(
110 clean_source_id(r"\\?\C:\scan\a.md:javascript"),
111 r"\\?\C:\scan\a.md"
112 );
113 }
114
115 #[test]
116 fn resolve_fragment_path_joins_root() {
117 let expected = std::path::Path::new("/project")
120 .join("src/a.js")
121 .to_string_lossy()
122 .into_owned();
123 assert_eq!(
124 resolve_fragment_path(&frag("src/a.js", Some("/project"))),
125 expected
126 );
127 }
128
129 #[test]
130 fn resolve_fragment_path_falls_back_to_source_id() {
131 assert_eq!(
132 resolve_fragment_path(&frag("/absolute/path/a.js", None)),
133 "/absolute/path/a.js"
134 );
135 }
136
137 #[test]
138 fn resolve_fragment_path_cleans_format_suffix() {
139 let expected = std::path::Path::new("/repo")
140 .join("doc.md")
141 .to_string_lossy()
142 .into_owned();
143 assert_eq!(
144 resolve_fragment_path(&frag("doc.md:javascript", Some("/repo"))),
145 expected
146 );
147 }
148}