1use typst::diag::HintedStrResult;
4use typst::foundations::PathOrStr;
5use typst::syntax::{FileId, RootedPath};
6
7pub fn resolve_path_from_id(within: FileId, path: &str) -> HintedStrResult<RootedPath> {
13 PathOrStr::Str(path.into()).resolve(within)
14}
15
16#[cfg(test)]
17mod tests {
18 use std::str::FromStr;
19
20 use typst::syntax::package::PackageSpec;
21 use typst::syntax::{FileId, RootedPath, VirtualPath, VirtualRoot};
22
23 use super::resolve_path_from_id;
24
25 fn file_id(root: VirtualRoot, path: &str) -> FileId {
26 RootedPath::new(root, VirtualPath::new(path).expect("valid virtual path")).intern()
27 }
28
29 fn package(spec: &str) -> PackageSpec {
30 PackageSpec::from_str(spec).expect("valid package spec")
31 }
32
33 #[test]
34 fn resolve_path_from_id_uses_parent_directory_for_relative_paths() {
35 let current = file_id(VirtualRoot::Project, "/chapter/main.typ");
36 let resolved = resolve_path_from_id(current, "assets/logo.svg").unwrap();
37
38 assert_eq!(resolved.root(), current.root());
39 assert_eq!(
40 resolved.vpath().get_with_slash(),
41 "/chapter/assets/logo.svg"
42 );
43 }
44
45 #[test]
46 fn resolve_path_from_id_keeps_root_fallback_when_base_has_no_parent() {
47 let current = file_id(VirtualRoot::Project, "/");
48 let resolved = resolve_path_from_id(current, "main.typ").unwrap();
49
50 assert_eq!(resolved.root(), current.root());
51 assert_eq!(resolved.vpath().get_with_slash(), "/main.typ");
52 }
53
54 #[test]
55 fn resolve_path_from_id_resolves_absolute_paths_within_same_root() {
56 let root = VirtualRoot::Package(package("@preview/example:0.1.0"));
57 let current = file_id(root, "/chapter/main.typ");
58 let resolved = resolve_path_from_id(current, "/assets/logo.svg").unwrap();
59
60 assert_eq!(resolved.root(), current.root());
61 assert_eq!(resolved.vpath().get_with_slash(), "/assets/logo.svg");
62 }
63
64 #[test]
65 fn resolve_path_from_id_rejects_paths_that_escape_the_root() {
66 let current = file_id(VirtualRoot::Project, "/main.typ");
67
68 assert!(resolve_path_from_id(current, "../outside.typ").is_err());
69 }
70}