11// Copyright 2018-2025 the Deno authors. MIT license.
22
3- use std:: borrow:: Cow ;
43use std:: io:: Error ;
54use std:: io:: ErrorKind ;
65use std:: io:: Write ;
@@ -18,7 +17,6 @@ use sys_traits::SystemRandom;
1817use sys_traits:: ThreadSleep ;
1918
2019use crate :: get_atomic_path;
21- use crate :: normalize_path;
2220
2321/// Canonicalizes a path which might be non-existent by going up the
2422/// ancestors until it finds a directory that exists, canonicalizes
@@ -28,10 +26,8 @@ use crate::normalize_path;
2826/// subsequently be created along this path by some other code.
2927pub fn canonicalize_path_maybe_not_exists (
3028 sys : & impl FsCanonicalize ,
31- path : & Path ,
29+ mut path : & Path ,
3230) -> std:: io:: Result < PathBuf > {
33- let path = normalize_path ( Cow :: Borrowed ( path) ) ;
34- let mut path = path. as_ref ( ) ;
3531 let mut names_stack = Vec :: new ( ) ;
3632 loop {
3733 match sys. fs_canonicalize ( path) {
@@ -47,6 +43,13 @@ pub fn canonicalize_path_maybe_not_exists(
4743 None => return Err ( err) ,
4844 } ) ;
4945 path = match path. parent ( ) {
46+ // When the provided path is a relative path (e.g. `foo/bar.txt`),
47+ // `path.parent()` ends up being the empty string as documented in
48+ // `std::path::Path::parent()` after going up the ancestor path.
49+ // In this case, what this function should return is a path joining
50+ // the current path with the provided path i.e. `{cwd}/foo/bar.txt`,
51+ // so we set `path` to `.` to indicate the current directory.
52+ Some ( parent) if parent. as_os_str ( ) . is_empty ( ) => Path :: new ( "." ) ,
5053 Some ( parent) => parent,
5154 None => return Err ( err) ,
5255 } ;
@@ -168,29 +171,110 @@ fn add_file_context_to_err(file_path: &Path, err: Error) -> Error {
168171
169172#[ cfg( test) ]
170173mod test {
174+ use std:: path:: Path ;
171175 use std:: path:: PathBuf ;
172176
173177 use sys_traits:: impls:: InMemorySys ;
174178 use sys_traits:: impls:: RealSys ;
179+ use sys_traits:: EnvCurrentDir ;
175180 use sys_traits:: EnvSetCurrentDir ;
181+ use sys_traits:: FsCanonicalize ;
176182 use sys_traits:: FsCreateDirAll ;
177183 use sys_traits:: FsRead ;
184+ use sys_traits:: FsSymlinkDir ;
178185
179186 use super :: atomic_write_file_with_retries;
180187 use super :: canonicalize_path_maybe_not_exists;
181188
182189 #[ test]
183- fn test_canonicalize_path_maybe_not_exists ( ) {
190+ fn test_canonicalize_path_maybe_not_exists_in_memory ( ) {
184191 let sys = InMemorySys :: default ( ) ;
192+
193+ // .
194+ // └── a
195+ // └── b (cwd)
196+ // └── c
185197 sys. fs_create_dir_all ( "/a/b/c" ) . unwrap ( ) ;
186198 sys. env_set_current_dir ( "/a/b" ) . unwrap ( ) ;
199+
200+ let path = canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "" ) ) . unwrap ( ) ;
201+ assert_eq ! ( path, PathBuf :: from( "/a/b" ) ) ;
202+ let path =
203+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "." ) ) . unwrap ( ) ;
204+ assert_eq ! ( path, PathBuf :: from( "/a/b" ) ) ;
205+ let path =
206+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "d" ) ) . unwrap ( ) ;
207+ assert_eq ! ( path, PathBuf :: from( "/a/b/d" ) ) ;
187208 let path =
188- canonicalize_path_maybe_not_exists ( & sys, & PathBuf :: from ( "./c" ) ) . unwrap ( ) ;
209+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "./d" ) ) . unwrap ( ) ;
210+ assert_eq ! ( path, PathBuf :: from( "/a/b/d" ) ) ;
211+ let path =
212+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "c" ) ) . unwrap ( ) ;
213+ assert_eq ! ( path, PathBuf :: from( "/a/b/c" ) ) ;
214+ let path =
215+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "./c" ) ) . unwrap ( ) ;
189216 assert_eq ! ( path, PathBuf :: from( "/a/b/c" ) ) ;
190217 let path =
191- canonicalize_path_maybe_not_exists ( & sys, & PathBuf :: from ( "./c/d/e" ) )
192- . unwrap ( ) ;
218+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "c/d/e" ) ) . unwrap ( ) ;
193219 assert_eq ! ( path, PathBuf :: from( "/a/b/c/d/e" ) ) ;
220+ let path =
221+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "./c/d/e" ) ) . unwrap ( ) ;
222+ assert_eq ! ( path, PathBuf :: from( "/a/b/c/d/e" ) ) ;
223+ }
224+
225+ // Note: this test mutates the current working directory. Although it will be
226+ // restored at the end, if other tests relying on the cwd are run in parallel
227+ // to this test, they may fail.
228+ #[ test]
229+ fn test_canonicalize_path_maybe_not_exists_real ( ) {
230+ let sys = RealSys ;
231+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
232+
233+ // Save the original working directory to restore it later
234+ let original_cwd = sys. env_current_dir ( ) . unwrap ( ) ;
235+
236+ // .
237+ // ├── a
238+ // │ └── b
239+ // │ └── c
240+ // └── link -> a/b/c (cwd)
241+ sys
242+ . fs_create_dir_all ( temp_dir. path ( ) . join ( "a/b/c" ) )
243+ . unwrap ( ) ;
244+ sys
245+ . fs_symlink_dir (
246+ temp_dir. path ( ) . join ( "a/b/c" ) ,
247+ temp_dir. path ( ) . join ( "link" ) ,
248+ )
249+ . unwrap ( ) ;
250+ let cwd = temp_dir. path ( ) . join ( "link" ) ;
251+ sys. env_set_current_dir ( & cwd) . unwrap ( ) ;
252+
253+ let canonicalized_temp_dir_path =
254+ sys. fs_canonicalize ( temp_dir. path ( ) ) . unwrap ( ) ;
255+
256+ let path =
257+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "." ) ) . unwrap ( ) ;
258+ assert_eq ! ( path, canonicalized_temp_dir_path. join( "a/b/c" ) ) ;
259+
260+ let path =
261+ canonicalize_path_maybe_not_exists ( & sys, & PathBuf :: from ( "d" ) ) . unwrap ( ) ;
262+ assert_eq ! ( path, canonicalized_temp_dir_path. join( "a/b/c/d" ) ) ;
263+
264+ let path =
265+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "./d" ) ) . unwrap ( ) ;
266+ assert_eq ! ( path, canonicalized_temp_dir_path. join( "a/b/c/d" ) ) ;
267+
268+ let path =
269+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "d/e" ) ) . unwrap ( ) ;
270+ assert_eq ! ( path, canonicalized_temp_dir_path. join( "a/b/c/d/e" ) ) ;
271+
272+ let path =
273+ canonicalize_path_maybe_not_exists ( & sys, Path :: new ( "./d/e" ) ) . unwrap ( ) ;
274+ assert_eq ! ( path, canonicalized_temp_dir_path. join( "a/b/c/d/e" ) ) ;
275+
276+ // Restore the original working directory
277+ sys. env_set_current_dir ( & original_cwd) . unwrap ( ) ;
194278 }
195279
196280 #[ test]
0 commit comments