diff --git a/cli/fs.rs b/cli/fs.rs index 83dec49139..2aedf1e34b 100644 --- a/cli/fs.rs +++ b/cli/fs.rs @@ -46,6 +46,8 @@ pub fn write_file_2>( file.write_all(data.as_ref()) } +/// IMPORTANT: This method is duplicated in core/module_specifier.rs +/// /// Normalize all itermediate components of the path (ie. remove "./" and "../" components). /// Similar to `fs::canonicalize()` but doesn't resolve symlinks. /// diff --git a/cli/main.rs b/cli/main.rs index 088d65ecd1..cf25ed3246 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -440,16 +440,7 @@ async fn bundle_command( source_file: String, out_file: Option, ) -> Result<(), ErrBox> { - let mut module_specifier = - ModuleSpecifier::resolve_url_or_path(&source_file)?; - let url = module_specifier.as_url(); - - // TODO(bartlomieju): fix this hack in ModuleSpecifier - if url.scheme() == "file" { - let a = deno_fs::normalize_path(&url.to_file_path().unwrap()); - let u = Url::from_file_path(a).unwrap(); - module_specifier = ModuleSpecifier::from(u) - } + let module_specifier = ModuleSpecifier::resolve_url_or_path(&source_file)?; debug!(">>>>> bundle START"); let global_state = GlobalState::new(flags)?; diff --git a/core/module_specifier.rs b/core/module_specifier.rs index 9cf4493023..1ad4bcc93e 100644 --- a/core/module_specifier.rs +++ b/core/module_specifier.rs @@ -1,6 +1,8 @@ use std::env::current_dir; use std::error::Error; use std::fmt; +use std::path::Component; +use std::path::Path; use std::path::PathBuf; use url::ParseError; use url::Url; @@ -150,6 +152,7 @@ impl ModuleSpecifier { path_str: &str, ) -> Result { let path = current_dir().unwrap().join(path_str); + let path = normalize_path(&path); Url::from_file_path(path.clone()) .map(ModuleSpecifier) .map_err(|()| ModuleResolutionError::InvalidPath(path)) @@ -203,6 +206,39 @@ impl PartialEq for ModuleSpecifier { } } +/// Normalize all itermediate components of the path (ie. remove "./" and "../" components). +/// Similar to `fs::canonicalize()` but doesn't resolve symlinks. +/// +/// Taken from Cargo +/// https://github.com/rust-lang/cargo/blob/af307a38c20a753ec60f0ad18be5abed3db3c9ac/src/cargo/util/paths.rs#L60-L85 +pub fn normalize_path(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = + if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} + #[cfg(test)] mod tests { use super::*; @@ -415,7 +451,12 @@ mod tests { ); tests.extend(vec![ (r"/deno/tests/006_url_imports.ts", expected_url.to_string()), - (r"\deno\tests\006_url_imports.ts", expected_url), + (r"\deno\tests\006_url_imports.ts", expected_url.to_string()), + ( + r"\deno\..\deno\tests\006_url_imports.ts", + expected_url.to_string(), + ), + (r"\deno\.\tests\006_url_imports.ts", expected_url), ]); // Relative local path. @@ -450,7 +491,12 @@ mod tests { let expected_url = format!("file://{}/tests/006_url_imports.ts", cwd_str); tests.extend(vec![ ("tests/006_url_imports.ts", expected_url.to_string()), - ("./tests/006_url_imports.ts", expected_url), + ("./tests/006_url_imports.ts", expected_url.to_string()), + ( + "tests/../tests/006_url_imports.ts", + expected_url.to_string(), + ), + ("tests/./006_url_imports.ts", expected_url), ]); } @@ -511,4 +557,21 @@ mod tests { assert_eq!(result, expected); } } + + #[test] + fn test_normalize_path() { + assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b")); + assert_eq!(normalize_path(Path::new("a/./b/")), PathBuf::from("a/b/")); + assert_eq!( + normalize_path(Path::new("a/./b/../c")), + PathBuf::from("a/c") + ); + + if cfg!(windows) { + assert_eq!( + normalize_path(Path::new("C:\\a\\.\\b\\..\\c")), + PathBuf::from("C:\\a\\c") + ); + } + } }