Skip to main content
Module

x/deno/cli/fs_util.rs

A modern runtime for JavaScript and TypeScript.
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use deno_core::anyhow::Context;use deno_core::error::{uri_error, AnyError};pub use deno_core::normalize_path;use deno_core::ModuleSpecifier;use deno_runtime::deno_crypto::rand;use std::borrow::Cow;use std::env::current_dir;use std::fs::OpenOptions;use std::io::{Error, Write};use std::path::{Path, PathBuf};use walkdir::WalkDir;
pub fn atomic_write_file<T: AsRef<[u8]>>( filename: &Path, data: T, mode: u32,) -> std::io::Result<()> { let rand: String = (0..4) .map(|_| format!("{:02x}", rand::random::<u8>())) .collect(); let extension = format!("{}.tmp", rand); let tmp_file = filename.with_extension(extension); write_file(&tmp_file, data, mode)?; std::fs::rename(tmp_file, filename)?; Ok(())}
pub fn write_file<T: AsRef<[u8]>>( filename: &Path, data: T, mode: u32,) -> std::io::Result<()> { write_file_2(filename, data, true, mode, true, false)}
pub fn write_file_2<T: AsRef<[u8]>>( filename: &Path, data: T, update_mode: bool, mode: u32, is_create: bool, is_append: bool,) -> std::io::Result<()> { let mut file = OpenOptions::new() .read(false) .write(true) .append(is_append) .truncate(!is_append) .create(is_create) .open(filename)?;
if update_mode { #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mode = mode & 0o777; let permissions = PermissionsExt::from_mode(mode); file.set_permissions(permissions)?; } #[cfg(not(unix))] let _ = mode; }
file.write_all(data.as_ref())}
/// Similar to `std::fs::canonicalize()` but strips UNC prefixes on Windows.pub fn canonicalize_path(path: &Path) -> Result<PathBuf, Error> { let path = path.canonicalize()?; #[cfg(windows)] return Ok(strip_unc_prefix(path)); #[cfg(not(windows))] return Ok(path);}
#[cfg(windows)]fn strip_unc_prefix(path: PathBuf) -> PathBuf { use std::path::Component; use std::path::Prefix;
let mut components = path.components(); match components.next() { Some(Component::Prefix(prefix)) => { match prefix.kind() { // \\?\device Prefix::Verbatim(device) => { let mut path = PathBuf::new(); path.push(format!(r"\\{}\", device.to_string_lossy())); path.extend(components.filter(|c| !matches!(c, Component::RootDir))); path } // \\?\c:\path Prefix::VerbatimDisk(_) => { let mut path = PathBuf::new(); path.push(prefix.as_os_str().to_string_lossy().replace(r"\\?\", "")); path.extend(components); path } // \\?\UNC\hostname\share_name\path Prefix::VerbatimUNC(hostname, share_name) => { let mut path = PathBuf::new(); path.push(format!( r"\\{}\{}\", hostname.to_string_lossy(), share_name.to_string_lossy() )); path.extend(components.filter(|c| !matches!(c, Component::RootDir))); path } _ => path, } } _ => path, }}
pub fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> { let resolved_path = if path.is_absolute() { path.to_owned() } else { let cwd = current_dir().context("Failed to get current working directory")?; cwd.join(path) };
Ok(normalize_path(&resolved_path))}
/// Checks if the path has extension Deno supports.pub fn is_supported_ext(path: &Path) -> bool { if let Some(ext) = get_extension(path) { matches!( ext.as_str(), "ts" | "tsx" | "js" | "jsx" | "mjs" | "mts" | "cjs" | "cts" ) } else { false }}
/// Checks if the path has a basename and extension Deno supports for tests.pub fn is_supported_test_path(path: &Path) -> bool { if let Some(name) = path.file_stem() { let basename = name.to_string_lossy(); (basename.ends_with("_test") || basename.ends_with(".test") || basename == "test") && is_supported_ext(path) } else { false }}
/// Checks if the path has a basename and extension Deno supports for benches.pub fn is_supported_bench_path(path: &Path) -> bool { if let Some(name) = path.file_stem() { let basename = name.to_string_lossy(); (basename.ends_with("_bench") || basename.ends_with(".bench") || basename == "bench") && is_supported_ext(path) } else { false }}
/// Checks if the path has an extension Deno supports for tests.pub fn is_supported_test_ext(path: &Path) -> bool { if let Some(ext) = get_extension(path) { matches!( ext.as_str(), "ts" | "tsx" | "js" | "jsx" | "mjs" | "mts" | "cjs" | "cts" | "md" | "mkd" | "mkdn" | "mdwn" | "mdown" | "markdown" ) } else { false }}
/// Get the extension of a file in lowercase.pub fn get_extension(file_path: &Path) -> Option<String> { return file_path .extension() .and_then(|e| e.to_str()) .map(|e| e.to_lowercase());}
/// Collects file paths that satisfy the given predicate, by recursively walking `files`./// If the walker visits a path that is listed in `ignore`, it skips descending into the directory.pub fn collect_files<P>( files: &[PathBuf], ignore: &[PathBuf], predicate: P,) -> Result<Vec<PathBuf>, AnyError>where P: Fn(&Path) -> bool,{ let mut target_files = Vec::new();
// retain only the paths which exist and ignore the rest let canonicalized_ignore: Vec<PathBuf> = ignore .iter() .filter_map(|i| canonicalize_path(i).ok()) .collect();
for file in files { for entry in WalkDir::new(file) .into_iter() .filter_entry(|e| { canonicalize_path(e.path()).map_or(false, |c| { !canonicalized_ignore.iter().any(|i| c.starts_with(i)) }) }) .filter_map(|e| match e { Ok(e) if !e.file_type().is_dir() && predicate(e.path()) => Some(e), _ => None, }) { target_files.push(canonicalize_path(entry.path())?) } }
Ok(target_files)}
/// Collects module specifiers that satisfy the given predicate as a file path, by recursively walking `include`./// Specifiers that start with http and https are left intact.pub fn collect_specifiers<P>( include: Vec<String>, ignore: &[PathBuf], predicate: P,) -> Result<Vec<ModuleSpecifier>, AnyError>where P: Fn(&Path) -> bool,{ let mut prepared = vec![];
let root_path = std::env::current_dir()?; for path in include { let lowercase_path = path.to_lowercase(); if lowercase_path.starts_with("http://") || lowercase_path.starts_with("https://") || lowercase_path.starts_with("file://") { let url = ModuleSpecifier::parse(&path)?; prepared.push(url); continue; }
let p = normalize_path(&root_path.join(path)); if p.is_dir() { let test_files = collect_files(&[p], ignore, &predicate).unwrap(); let mut test_files_as_urls = test_files .iter() .map(|f| ModuleSpecifier::from_file_path(f).unwrap()) .collect::<Vec<ModuleSpecifier>>();
test_files_as_urls.sort(); prepared.extend(test_files_as_urls); } else { let url = ModuleSpecifier::from_file_path(p).unwrap(); prepared.push(url); } }
Ok(prepared)}
/// Asynchronously removes a directory and all its descendants, but does not error/// when the directory does not exist.pub async fn remove_dir_all_if_exists(path: &Path) -> std::io::Result<()> { let result = tokio::fs::remove_dir_all(path).await; match result { Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()), _ => result, }}
/// Attempts to convert a specifier to a file path. By default, uses the Url/// crate's `to_file_path()` method, but falls back to try and resolve unix-style/// paths on Windows.pub fn specifier_to_file_path( specifier: &ModuleSpecifier,) -> Result<PathBuf, AnyError> { let result = if cfg!(windows) { match specifier.to_file_path() { Ok(path) => Ok(path), Err(()) => { // This might be a unix-style path which is used in the tests even on Windows. // Attempt to see if we can convert it to a `PathBuf`. This code should be removed // once/if https://github.com/servo/rust-url/issues/730 is implemented. if specifier.scheme() == "file" && specifier.host().is_none() && specifier.port().is_none() && specifier.path_segments().is_some() { let path_str = specifier.path(); match String::from_utf8( percent_encoding::percent_decode(path_str.as_bytes()).collect(), ) { Ok(path_str) => Ok(PathBuf::from(path_str)), Err(_) => Err(()), } } else { Err(()) } } } } else { specifier.to_file_path() }; match result { Ok(path) => Ok(path), Err(()) => Err(uri_error(format!( "Invalid file path.\n Specifier: {}", specifier ))), }}
/// Ensures a specifier that will definitely be a directory has a trailing slash.pub fn ensure_directory_specifier( mut specifier: ModuleSpecifier,) -> ModuleSpecifier { let path = specifier.path(); if !path.ends_with('/') { let new_path = format!("{}/", path); specifier.set_path(&new_path); } specifier}
/// Gets the parent of this module specifier.pub fn specifier_parent(specifier: &ModuleSpecifier) -> ModuleSpecifier { let mut specifier = specifier.clone(); // don't use specifier.segments() because it will strip the leading slash let mut segments = specifier.path().split('/').collect::<Vec<_>>(); if segments.iter().all(|s| s.is_empty()) { return specifier; } if let Some(last) = segments.last() { if last.is_empty() { segments.pop(); } segments.pop(); let new_path = format!("{}/", segments.join("/")); specifier.set_path(&new_path); } specifier}
/// `from.make_relative(to)` but with fixes.pub fn relative_specifier( from: &ModuleSpecifier, to: &ModuleSpecifier,) -> Option<String> { let is_dir = to.path().ends_with('/');
if is_dir && from == to { return Some("./".to_string()); }
// workaround using parent directory until https://github.com/servo/rust-url/pull/754 is merged let from = if !from.path().ends_with('/') { if let Some(end_slash) = from.path().rfind('/') { let mut new_from = from.clone(); new_from.set_path(&from.path()[..end_slash + 1]); Cow::Owned(new_from) } else { Cow::Borrowed(from) } } else { Cow::Borrowed(from) };
// workaround for url crate not adding a trailing slash for a directory // it seems to be fixed once a version greater than 2.2.2 is released let mut text = from.make_relative(to)?; if is_dir && !text.ends_with('/') && to.query().is_none() { text.push('/'); }
Some(if text.starts_with("../") || text.starts_with("./") { text } else { format!("./{}", text) })}
/// This function checks if input path has trailing slash or not. If input path/// has trailing slash it will return true else it will return false.pub fn path_has_trailing_slash(path: &Path) -> bool { if let Some(path_str) = path.to_str() { if cfg!(windows) { path_str.ends_with('\\') } else { path_str.ends_with('/') } } else { false }}
/// Gets a path with the specified file stem suffix.////// Ex. `file.ts` with suffix `_2` returns `file_2.ts`pub fn path_with_stem_suffix(path: &Path, suffix: &str) -> PathBuf { if let Some(file_name) = path.file_name().map(|f| f.to_string_lossy()) { if let Some(file_stem) = path.file_stem().map(|f| f.to_string_lossy()) { if let Some(ext) = path.extension().map(|f| f.to_string_lossy()) { return if file_stem.to_lowercase().ends_with(".d") { path.with_file_name(format!( "{}{}.{}.{}", &file_stem[..file_stem.len() - ".d".len()], suffix, // maintain casing &file_stem[file_stem.len() - "d".len()..], ext )) } else { path.with_file_name(format!("{}{}.{}", file_stem, suffix, ext)) }; } }
path.with_file_name(format!("{}{}", file_name, suffix)) } else { path.with_file_name(suffix) }}
#[cfg(test)]mod tests { use super::*; use test_util::TempDir;
#[test] fn resolve_from_cwd_child() { let cwd = current_dir().unwrap(); assert_eq!(resolve_from_cwd(Path::new("a")).unwrap(), cwd.join("a")); }
#[test] fn resolve_from_cwd_dot() { let cwd = current_dir().unwrap(); assert_eq!(resolve_from_cwd(Path::new(".")).unwrap(), cwd); }
#[test] fn resolve_from_cwd_parent() { let cwd = current_dir().unwrap(); assert_eq!(resolve_from_cwd(Path::new("a/..")).unwrap(), cwd); }
#[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") ); } }
// TODO: Get a good expected value here for Windows. #[cfg(not(windows))] #[test] fn resolve_from_cwd_absolute() { let expected = Path::new("/a"); assert_eq!(resolve_from_cwd(expected).unwrap(), expected); }
#[test] fn test_is_supported_ext() { assert!(!is_supported_ext(Path::new("tests/subdir/redirects"))); assert!(!is_supported_ext(Path::new("README.md"))); assert!(is_supported_ext(Path::new("lib/typescript.d.ts"))); assert!(is_supported_ext(Path::new("testdata/001_hello.js"))); assert!(is_supported_ext(Path::new("testdata/002_hello.ts"))); assert!(is_supported_ext(Path::new("foo.jsx"))); assert!(is_supported_ext(Path::new("foo.tsx"))); assert!(is_supported_ext(Path::new("foo.TS"))); assert!(is_supported_ext(Path::new("foo.TSX"))); assert!(is_supported_ext(Path::new("foo.JS"))); assert!(is_supported_ext(Path::new("foo.JSX"))); assert!(is_supported_ext(Path::new("foo.mjs"))); assert!(is_supported_ext(Path::new("foo.mts"))); assert!(is_supported_ext(Path::new("foo.cjs"))); assert!(is_supported_ext(Path::new("foo.cts"))); assert!(!is_supported_ext(Path::new("foo.mjsx"))); }
#[test] fn test_is_supported_test_ext() { assert!(!is_supported_test_ext(Path::new("tests/subdir/redirects"))); assert!(is_supported_test_ext(Path::new("README.md"))); assert!(is_supported_test_ext(Path::new("readme.MD"))); assert!(is_supported_test_ext(Path::new("lib/typescript.d.ts"))); assert!(is_supported_test_ext(Path::new("testdata/001_hello.js"))); assert!(is_supported_test_ext(Path::new("testdata/002_hello.ts"))); assert!(is_supported_test_ext(Path::new("foo.jsx"))); assert!(is_supported_test_ext(Path::new("foo.tsx"))); assert!(is_supported_test_ext(Path::new("foo.TS"))); assert!(is_supported_test_ext(Path::new("foo.TSX"))); assert!(is_supported_test_ext(Path::new("foo.JS"))); assert!(is_supported_test_ext(Path::new("foo.JSX"))); assert!(is_supported_test_ext(Path::new("foo.mjs"))); assert!(is_supported_test_ext(Path::new("foo.mts"))); assert!(is_supported_test_ext(Path::new("foo.cjs"))); assert!(is_supported_test_ext(Path::new("foo.cts"))); assert!(!is_supported_test_ext(Path::new("foo.mjsx"))); assert!(!is_supported_test_ext(Path::new("foo.jsonc"))); assert!(!is_supported_test_ext(Path::new("foo.JSONC"))); assert!(!is_supported_test_ext(Path::new("foo.json"))); assert!(!is_supported_test_ext(Path::new("foo.JsON"))); }
#[test] fn test_is_supported_test_path() { assert!(is_supported_test_path(Path::new( "tests/subdir/foo_test.ts" ))); assert!(is_supported_test_path(Path::new( "tests/subdir/foo_test.tsx" ))); assert!(is_supported_test_path(Path::new( "tests/subdir/foo_test.js" ))); assert!(is_supported_test_path(Path::new( "tests/subdir/foo_test.jsx" ))); assert!(is_supported_test_path(Path::new("bar/foo.test.ts"))); assert!(is_supported_test_path(Path::new("bar/foo.test.tsx"))); assert!(is_supported_test_path(Path::new("bar/foo.test.js"))); assert!(is_supported_test_path(Path::new("bar/foo.test.jsx"))); assert!(is_supported_test_path(Path::new("foo/bar/test.js"))); assert!(is_supported_test_path(Path::new("foo/bar/test.jsx"))); assert!(is_supported_test_path(Path::new("foo/bar/test.ts"))); assert!(is_supported_test_path(Path::new("foo/bar/test.tsx"))); assert!(!is_supported_test_path(Path::new("README.md"))); assert!(!is_supported_test_path(Path::new("lib/typescript.d.ts"))); assert!(!is_supported_test_path(Path::new("notatest.js"))); assert!(!is_supported_test_path(Path::new("NotAtest.ts"))); }
#[test] fn test_collect_files() { fn create_files(dir_path: &Path, files: &[&str]) { std::fs::create_dir(dir_path).expect("Failed to create directory"); for f in files { let path = dir_path.join(f); std::fs::write(path, "").expect("Failed to create file"); } }
// dir.ts // ├── a.ts // ├── b.js // ├── child // │ ├── e.mjs // │ ├── f.mjsx // │ ├── .foo.TS // │ └── README.md // ├── c.tsx // ├── d.jsx // └── ignore // ├── g.d.ts // └── .gitignore
let t = TempDir::new();
let root_dir_path = t.path().join("dir.ts"); let root_dir_files = ["a.ts", "b.js", "c.tsx", "d.jsx"]; create_files(&root_dir_path, &root_dir_files);
let child_dir_path = root_dir_path.join("child"); let child_dir_files = ["e.mjs", "f.mjsx", ".foo.TS", "README.md"]; create_files(&child_dir_path, &child_dir_files);
let ignore_dir_path = root_dir_path.join("ignore"); let ignore_dir_files = ["g.d.ts", ".gitignore"]; create_files(&ignore_dir_path, &ignore_dir_files);
let result = collect_files(&[root_dir_path], &[ignore_dir_path], |path| { // exclude dotfiles path .file_name() .and_then(|f| f.to_str()) .map_or(false, |f| !f.starts_with('.')) }) .unwrap(); let expected = [ "a.ts", "b.js", "e.mjs", "f.mjsx", "README.md", "c.tsx", "d.jsx", ]; for e in expected.iter() { assert!(result.iter().any(|r| r.ends_with(e))); } assert_eq!(result.len(), expected.len()); }
#[test] fn test_collect_specifiers() { fn create_files(dir_path: &Path, files: &[&str]) { std::fs::create_dir(dir_path).expect("Failed to create directory"); for f in files { let path = dir_path.join(f); std::fs::write(path, "").expect("Failed to create file"); } }
// dir.ts // ├── a.ts // ├── b.js // ├── child // │ ├── e.mjs // │ ├── f.mjsx // │ ├── .foo.TS // │ └── README.md // ├── c.tsx // ├── d.jsx // └── ignore // ├── g.d.ts // └── .gitignore
let t = TempDir::new();
let root_dir_path = t.path().join("dir.ts"); let root_dir_files = ["a.ts", "b.js", "c.tsx", "d.jsx"]; create_files(&root_dir_path, &root_dir_files);
let child_dir_path = root_dir_path.join("child"); let child_dir_files = ["e.mjs", "f.mjsx", ".foo.TS", "README.md"]; create_files(&child_dir_path, &child_dir_files);
let ignore_dir_path = root_dir_path.join("ignore"); let ignore_dir_files = ["g.d.ts", ".gitignore"]; create_files(&ignore_dir_path, &ignore_dir_files);
let result = collect_specifiers( vec![ "http://localhost:8080".to_string(), root_dir_path.to_str().unwrap().to_string(), "https://localhost:8080".to_string(), ], &[ignore_dir_path], |path| { // exclude dotfiles path .file_name() .and_then(|f| f.to_str()) .map_or(false, |f| !f.starts_with('.')) }, ) .unwrap();
let root_dir_url = ModuleSpecifier::from_file_path( canonicalize_path(&root_dir_path).unwrap(), ) .unwrap() .to_string(); let expected: Vec<ModuleSpecifier> = [ "http://localhost:8080", &format!("{}/a.ts", root_dir_url), &format!("{}/b.js", root_dir_url), &format!("{}/c.tsx", root_dir_url), &format!("{}/child/README.md", root_dir_url), &format!("{}/child/e.mjs", root_dir_url), &format!("{}/child/f.mjsx", root_dir_url), &format!("{}/d.jsx", root_dir_url), "https://localhost:8080", ] .iter() .map(|f| ModuleSpecifier::parse(f).unwrap()) .collect::<Vec<ModuleSpecifier>>();
assert_eq!(result, expected); }
#[cfg(windows)] #[test] fn test_strip_unc_prefix() { run_test(r"C:\", r"C:\"); run_test(r"C:\test\file.txt", r"C:\test\file.txt");
run_test(r"\\?\C:\", r"C:\"); run_test(r"\\?\C:\test\file.txt", r"C:\test\file.txt");
run_test(r"\\.\C:\", r"\\.\C:\"); run_test(r"\\.\C:\Test\file.txt", r"\\.\C:\Test\file.txt");
run_test(r"\\?\UNC\localhost\", r"\\localhost"); run_test(r"\\?\UNC\localhost\c$\", r"\\localhost\c$"); run_test( r"\\?\UNC\localhost\c$\Windows\file.txt", r"\\localhost\c$\Windows\file.txt", ); run_test(r"\\?\UNC\wsl$\deno.json", r"\\wsl$\deno.json");
run_test(r"\\?\server1", r"\\server1"); run_test(r"\\?\server1\e$\", r"\\server1\e$\"); run_test( r"\\?\server1\e$\test\file.txt", r"\\server1\e$\test\file.txt", );
fn run_test(input: &str, expected: &str) { assert_eq!( strip_unc_prefix(PathBuf::from(input)), PathBuf::from(expected) ); } }
#[test] fn test_specifier_to_file_path() { run_success_test("file:///", "/"); run_success_test("file:///test", "/test"); run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt"); run_success_test( "file:///dir/test%20test/test.txt", "/dir/test test/test.txt", );
fn run_success_test(specifier: &str, expected_path: &str) { let result = specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap()) .unwrap(); assert_eq!(result, PathBuf::from(expected_path)); } }
#[test] fn test_ensure_directory_specifier() { run_test("file:///", "file:///"); run_test("file:///test", "file:///test/"); run_test("file:///test/", "file:///test/"); run_test("file:///test/other", "file:///test/other/"); run_test("file:///test/other/", "file:///test/other/");
fn run_test(specifier: &str, expected: &str) { let result = ensure_directory_specifier(ModuleSpecifier::parse(specifier).unwrap()); assert_eq!(result.to_string(), expected); } }
#[test] fn test_specifier_parent() { run_test("file:///", "file:///"); run_test("file:///test", "file:///"); run_test("file:///test/", "file:///"); run_test("file:///test/other", "file:///test/"); run_test("file:///test/other.txt", "file:///test/"); run_test("file:///test/other/", "file:///test/");
fn run_test(specifier: &str, expected: &str) { let result = specifier_parent(&ModuleSpecifier::parse(specifier).unwrap()); assert_eq!(result.to_string(), expected); } }
#[test] fn test_relative_specifier() { run_test("file:///from", "file:///to", Some("./to")); run_test("file:///from", "file:///from/other", Some("./from/other")); run_test("file:///from", "file:///from/other/", Some("./from/other/")); run_test("file:///from", "file:///other/from", Some("./other/from")); run_test("file:///from/", "file:///other/from", Some("../other/from")); run_test("file:///from", "file:///other/from/", Some("./other/from/")); run_test( "file:///from", "file:///to/other.txt", Some("./to/other.txt"), ); run_test( "file:///from/test", "file:///to/other.txt", Some("../to/other.txt"), ); run_test( "file:///from/other.txt", "file:///to/other.txt", Some("../to/other.txt"), );
fn run_test(from: &str, to: &str, expected: Option<&str>) { let result = relative_specifier( &ModuleSpecifier::parse(from).unwrap(), &ModuleSpecifier::parse(to).unwrap(), ); assert_eq!(result.as_deref(), expected); } }
#[test] fn test_path_has_trailing_slash() { #[cfg(not(windows))] { run_test("/Users/johndoe/Desktop/deno-project/target/", true); run_test(r"/Users/johndoe/deno-project/target//", true); run_test("/Users/johndoe/Desktop/deno-project", false); run_test(r"/Users/johndoe/deno-project\", false); }
#[cfg(windows)] { run_test(r"C:\test\deno-project\", true); run_test(r"C:\test\deno-project\\", true); run_test(r"C:\test\file.txt", false); run_test(r"C:\test\file.txt/", false); }
fn run_test(path_str: &str, expected: bool) { let path = Path::new(path_str); let result = path_has_trailing_slash(path); assert_eq!(result, expected); } }
#[test] fn test_path_with_stem_suffix() { assert_eq!( path_with_stem_suffix(&PathBuf::from("/"), "_2"), PathBuf::from("/_2") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test"), "_2"), PathBuf::from("/test_2") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test.txt"), "_2"), PathBuf::from("/test_2.txt") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test/subdir"), "_2"), PathBuf::from("/test/subdir_2") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test/subdir.other.txt"), "_2"), PathBuf::from("/test/subdir.other_2.txt") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test.d.ts"), "_2"), PathBuf::from("/test_2.d.ts") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test.D.TS"), "_2"), PathBuf::from("/test_2.D.TS") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test.d.mts"), "_2"), PathBuf::from("/test_2.d.mts") ); assert_eq!( path_with_stem_suffix(&PathBuf::from("/test.d.cts"), "_2"), PathBuf::from("/test_2.d.cts") ); }}