1#![deny(clippy::print_stderr)]
4#![deny(clippy::print_stdout)]
5#![deny(clippy::unused_async)]
6#![deny(clippy::unnecessary_wraps)]
7
8use deno_error::JsError;
9use std::borrow::Cow;
10use std::path::Component;
11use std::path::Path;
12use std::path::PathBuf;
13use sys_traits::SystemRandom;
14use thiserror::Error;
15use url::Url;
16
17pub mod fs;
18
19pub fn url_parent(url: &Url) -> Url {
21 let mut url = url.clone();
22 let mut segments = url.path().split('/').collect::<Vec<_>>();
24 if segments.iter().all(|s| s.is_empty()) {
25 return url;
26 }
27 if let Some(last) = segments.last() {
28 if last.is_empty() {
29 segments.pop();
30 }
31 segments.pop();
32 let new_path = format!("{}/", segments.join("/"));
33 url.set_path(&new_path);
34 }
35 url
36}
37
38#[derive(Debug, Error, deno_error::JsError)]
39#[class(uri)]
40#[error("Could not convert URL to file path.\n URL: {0}")]
41pub struct UrlToFilePathError(pub Url);
42
43pub fn url_to_file_path(url: &Url) -> Result<PathBuf, UrlToFilePathError> {
47 let result = if url.scheme() != "file" {
48 Err(())
49 } else {
50 url_to_file_path_inner(url)
51 };
52 match result {
53 Ok(path) => Ok(path),
54 Err(()) => Err(UrlToFilePathError(url.clone())),
55 }
56}
57
58fn url_to_file_path_inner(url: &Url) -> Result<PathBuf, ()> {
59 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
60 return url_to_file_path_real(url);
61 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))]
62 url_to_file_path_wasm(url)
63}
64
65#[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
66fn url_to_file_path_real(url: &Url) -> Result<PathBuf, ()> {
67 if cfg!(windows) {
68 match url.to_file_path() {
69 Ok(path) => Ok(path),
70 Err(()) => {
71 if url.scheme() == "file"
75 && url.host().is_none()
76 && url.port().is_none()
77 && url.path_segments().is_some()
78 {
79 let path_str = url.path();
80 match String::from_utf8(
81 percent_encoding::percent_decode(path_str.as_bytes()).collect(),
82 ) {
83 Ok(path_str) => Ok(PathBuf::from(path_str)),
84 Err(_) => Err(()),
85 }
86 } else {
87 Err(())
88 }
89 }
90 }
91 } else {
92 url.to_file_path()
93 }
94}
95
96#[cfg(any(
97 test,
98 not(any(unix, windows, target_os = "redox", target_os = "wasi"))
99))]
100#[allow(clippy::unnecessary_wraps)]
101fn url_to_file_path_wasm(url: &Url) -> Result<PathBuf, ()> {
102 fn is_windows_path_segment(url: &str) -> bool {
103 let mut chars = url.chars();
104
105 let first_char = chars.next();
106 if first_char.is_none() || !first_char.unwrap().is_ascii_alphabetic() {
107 return false;
108 }
109
110 if chars.next() != Some(':') {
111 return false;
112 }
113
114 chars.next().is_none()
115 }
116
117 let path_segments = url.path_segments().unwrap().collect::<Vec<_>>();
118 let mut final_text = String::new();
119 let mut is_windows_share = false;
120 if let Some(host) = url.host_str() {
121 final_text.push_str("\\\\");
122 final_text.push_str(host);
123 is_windows_share = true;
124 }
125 for segment in path_segments.iter() {
126 if is_windows_share {
127 final_text.push('\\');
128 } else if !final_text.is_empty() {
129 final_text.push('/');
130 }
131 final_text.push_str(
132 &percent_encoding::percent_decode_str(segment).decode_utf8_lossy(),
133 );
134 }
135 if !is_windows_share && !is_windows_path_segment(path_segments[0]) {
136 final_text = format!("/{}", final_text);
137 }
138 Ok(PathBuf::from(final_text))
139}
140
141#[inline]
147pub fn normalize_path(path: Cow<Path>) -> Cow<Path> {
148 fn should_normalize(path: &Path) -> bool {
149 if path_has_trailing_separator(path) {
150 return true;
151 }
152
153 for component in path.components() {
154 match component {
155 Component::CurDir | Component::ParentDir => {
156 return true;
157 }
158 Component::Prefix(..) | Component::RootDir | Component::Normal(_) => {
159 }
161 }
162 }
163
164 path_has_cur_dir_separator(path)
165 }
166
167 fn path_has_trailing_separator(path: &Path) -> bool {
168 #[cfg(unix)]
169 let raw = std::os::unix::ffi::OsStrExt::as_bytes(path.as_os_str());
170 #[cfg(windows)]
171 let raw = path.as_os_str().as_encoded_bytes();
172 #[cfg(target_arch = "wasm32")]
173 let raw = path.to_string_lossy();
174 #[cfg(target_arch = "wasm32")]
175 let raw = raw.as_bytes();
176
177 if sys_traits::impls::is_windows() {
178 raw.contains(&b'/') || raw.ends_with(b"\\")
179 } else {
180 raw.ends_with(b"/")
181 }
182 }
183
184 fn path_has_cur_dir_separator(path: &Path) -> bool {
187 #[cfg(unix)]
188 let raw = std::os::unix::ffi::OsStrExt::as_bytes(path.as_os_str());
189 #[cfg(windows)]
190 let raw = path.as_os_str().as_encoded_bytes();
191 #[cfg(target_arch = "wasm32")]
192 let raw = path.to_string_lossy();
193 #[cfg(target_arch = "wasm32")]
194 let raw = raw.as_bytes();
195
196 if sys_traits::impls::is_windows() {
197 for window in raw.windows(3) {
198 if matches!(window, [b'\\', b'.', b'\\']) {
199 return true;
200 }
201 }
202 } else {
203 for window in raw.windows(3) {
204 if matches!(window, [b'/', b'.', b'/']) {
205 return true;
206 }
207 }
208 }
209
210 false
211 }
212
213 fn inner(path: &Path) -> PathBuf {
214 let mut components = path.components().peekable();
215 let mut ret =
216 if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
217 components.next();
218 PathBuf::from(c.as_os_str())
219 } else {
220 PathBuf::new()
221 };
222
223 for component in components {
224 match component {
225 Component::Prefix(..) => unreachable!(),
226 Component::RootDir => {
227 ret.push(component.as_os_str());
228 }
229 Component::CurDir => {}
230 Component::ParentDir => {
231 ret.pop();
232 }
233 Component::Normal(c) => {
234 ret.push(c);
235 }
236 }
237 }
238 ret
239 }
240
241 if should_normalize(&path) {
242 Cow::Owned(inner(&path))
243 } else {
244 path
245 }
246}
247
248#[derive(Debug, Clone, Error, deno_error::JsError, PartialEq, Eq)]
249#[class(uri)]
250#[error("Could not convert path to URL.\n Path: {0}")]
251pub struct PathToUrlError(pub PathBuf);
252
253#[allow(clippy::result_unit_err)]
254pub fn url_from_file_path(path: &Path) -> Result<Url, PathToUrlError> {
255 let path = normalize_path(Cow::Borrowed(path));
256 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
257 return Url::from_file_path(&path)
258 .map_err(|()| PathToUrlError(path.to_path_buf()));
259 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))]
260 url_from_file_path_wasm(&path)
261 .map_err(|()| PathToUrlError(path.to_path_buf()))
262}
263
264#[allow(clippy::result_unit_err)]
265pub fn url_from_directory_path(path: &Path) -> Result<Url, PathToUrlError> {
266 let path = normalize_path(Cow::Borrowed(path));
267 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
268 return Url::from_directory_path(&path)
269 .map_err(|()| PathToUrlError(path.to_path_buf()));
270 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))]
271 url_from_directory_path_wasm(&path)
272 .map_err(|()| PathToUrlError(path.to_path_buf()))
273}
274
275#[cfg(any(
276 test,
277 not(any(unix, windows, target_os = "redox", target_os = "wasi"))
278))]
279fn url_from_directory_path_wasm(path: &Path) -> Result<Url, ()> {
280 let mut url = url_from_file_path_wasm(path)?;
281 url.path_segments_mut().unwrap().push("");
282 Ok(url)
283}
284
285#[cfg(any(
286 test,
287 not(any(unix, windows, target_os = "redox", target_os = "wasi"))
288))]
289fn url_from_file_path_wasm(path: &Path) -> Result<Url, ()> {
290 use std::path::Component;
291
292 let original_path = path.to_string_lossy();
293 let mut path_str = original_path;
294 if path_str.contains('\\') {
296 let mut url = Url::parse("file://").unwrap();
297 if let Some(next) = path_str.strip_prefix(r#"\\?\UNC\"#) {
298 if let Some((host, rest)) = next.split_once('\\') {
299 if url.set_host(Some(host)).is_ok() {
300 path_str = rest.to_string().into();
301 }
302 }
303 } else if let Some(next) = path_str.strip_prefix(r#"\\?\"#) {
304 path_str = next.to_string().into();
305 } else if let Some(next) = path_str.strip_prefix(r#"\\"#) {
306 if let Some((host, rest)) = next.split_once('\\') {
307 if url.set_host(Some(host)).is_ok() {
308 path_str = rest.to_string().into();
309 }
310 }
311 }
312
313 for component in path_str.split('\\') {
314 url.path_segments_mut().unwrap().push(component);
315 }
316
317 Ok(url)
318 } else {
319 let mut url = Url::parse("file://").unwrap();
320 for component in path.components() {
321 match component {
322 Component::RootDir => {
323 url.path_segments_mut().unwrap().push("");
324 }
325 Component::Normal(segment) => {
326 url
327 .path_segments_mut()
328 .unwrap()
329 .push(&segment.to_string_lossy());
330 }
331 Component::Prefix(_) | Component::CurDir | Component::ParentDir => {
332 return Err(());
333 }
334 }
335 }
336
337 Ok(url)
338 }
339}
340
341#[cfg(not(windows))]
342#[inline]
343pub fn strip_unc_prefix(path: PathBuf) -> PathBuf {
344 path
345}
346
347#[cfg(windows)]
349pub fn strip_unc_prefix(path: PathBuf) -> PathBuf {
350 use std::path::Component;
351 use std::path::Prefix;
352
353 let mut components = path.components();
354 match components.next() {
355 Some(Component::Prefix(prefix)) => {
356 match prefix.kind() {
357 Prefix::Verbatim(device) => {
359 let mut path = PathBuf::new();
360 path.push(format!(r"\\{}\", device.to_string_lossy()));
361 path.extend(components.filter(|c| !matches!(c, Component::RootDir)));
362 path
363 }
364 Prefix::VerbatimDisk(_) => {
366 let mut path = PathBuf::new();
367 path.push(prefix.as_os_str().to_string_lossy().replace(r"\\?\", ""));
368 path.extend(components);
369 path
370 }
371 Prefix::VerbatimUNC(hostname, share_name) => {
373 let mut path = PathBuf::new();
374 path.push(format!(
375 r"\\{}\{}\",
376 hostname.to_string_lossy(),
377 share_name.to_string_lossy()
378 ));
379 path.extend(components.filter(|c| !matches!(c, Component::RootDir)));
380 path
381 }
382 _ => path,
383 }
384 }
385 _ => path,
386 }
387}
388
389pub fn specifier_has_uri_scheme(specifier: &str) -> bool {
400 let mut chars = specifier.chars();
401 let mut len = 0usize;
402 match chars.next() {
404 Some(c) if c.is_ascii_alphabetic() => len += 1,
405 _ => return false,
406 }
407 loop {
410 match chars.next() {
411 Some(c) if c.is_ascii_alphanumeric() || "+-.".contains(c) => len += 1,
412 Some(':') if len >= 2 => return true,
413 _ => return false,
414 }
415 }
416}
417
418#[derive(Debug, Clone, Error, JsError, PartialEq, Eq)]
419pub enum ResolveUrlOrPathError {
420 #[error(transparent)]
421 #[class(inherit)]
422 UrlParse(url::ParseError),
423 #[error(transparent)]
424 #[class(inherit)]
425 PathToUrl(PathToUrlError),
426}
427
428pub fn resolve_url_or_path(
435 specifier: &str,
436 current_dir: &Path,
437) -> Result<Url, ResolveUrlOrPathError> {
438 if specifier_has_uri_scheme(specifier) {
439 Url::parse(specifier).map_err(ResolveUrlOrPathError::UrlParse)
440 } else {
441 resolve_path(specifier, current_dir)
442 .map_err(ResolveUrlOrPathError::PathToUrl)
443 }
444}
445
446pub fn resolve_path(
450 path_str: &str,
451 current_dir: &Path,
452) -> Result<Url, PathToUrlError> {
453 let path = current_dir.join(path_str);
454 url_from_file_path(&path)
455}
456
457pub fn get_atomic_path(sys: &impl SystemRandom, path: &Path) -> PathBuf {
458 let rand = gen_rand_path_component(sys);
459 let extension = format!("{rand}.tmp");
460 path.with_extension(extension)
461}
462
463fn gen_rand_path_component(sys: &impl SystemRandom) -> String {
464 use std::fmt::Write;
465 (0..4).fold(String::with_capacity(8), |mut output, _| {
466 write!(&mut output, "{:02x}", sys.sys_random_u8().unwrap()).unwrap();
467 output
468 })
469}
470
471pub fn is_relative_specifier(specifier: &str) -> bool {
472 let mut specifier_chars = specifier.chars();
473 let Some(first_char) = specifier_chars.next() else {
474 return false;
475 };
476 if first_char != '.' {
477 return false;
478 }
479 let Some(second_char) = specifier_chars.next() else {
480 return true;
481 };
482 if second_char == '/' {
483 return true;
484 }
485 let Some(third_char) = specifier_chars.next() else {
486 return second_char == '.';
487 };
488 second_char == '.' && third_char == '/'
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn test_url_parent() {
497 run_test("file:///", "file:///");
498 run_test("file:///test", "file:///");
499 run_test("file:///test/", "file:///");
500 run_test("file:///test/other", "file:///test/");
501 run_test("file:///test/other.txt", "file:///test/");
502 run_test("file:///test/other/", "file:///test/");
503
504 fn run_test(url: &str, expected: &str) {
505 let result = url_parent(&Url::parse(url).unwrap());
506 assert_eq!(result.to_string(), expected);
507 }
508 }
509
510 #[test]
511 fn test_url_to_file_path() {
512 run_success_test("file:///", "/");
513 run_success_test("file:///test", "/test");
514 run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt");
515 #[cfg(not(debug_assertions))]
516 run_success_test(
517 "file:///dir/test%20test/test.txt",
518 "/dir/test test/test.txt",
519 );
520
521 assert_no_panic_url_to_file_path("file:/");
522 assert_no_panic_url_to_file_path("file://");
523 #[cfg(not(debug_assertions))]
524 assert_no_panic_url_to_file_path("file://asdf/");
525 assert_no_panic_url_to_file_path("file://asdf/66666/a.ts");
526
527 #[track_caller]
528 fn run_success_test(url: &str, expected_path: &str) {
529 let result = url_to_file_path(&Url::parse(url).unwrap()).unwrap();
530 assert_eq!(result, PathBuf::from(expected_path));
531 }
532
533 #[track_caller]
534 fn assert_no_panic_url_to_file_path(url: &str) {
535 let _result = url_to_file_path(&Url::parse(url).unwrap());
536 }
537 }
538
539 #[test]
540 fn test_url_to_file_path_wasm() {
541 #[track_caller]
542 fn convert(path: &str) -> String {
543 url_to_file_path_wasm(&Url::parse(path).unwrap())
544 .unwrap()
545 .to_string_lossy()
546 .into_owned()
547 }
548
549 assert_eq!(convert("file:///a/b/c.json"), "/a/b/c.json");
550 assert_eq!(convert("file:///D:/test/other.json"), "D:/test/other.json");
551 assert_eq!(
552 convert("file:///path%20with%20spaces/and%23special%25chars!.json"),
553 "/path with spaces/and#special%chars!.json",
554 );
555 assert_eq!(
556 convert("file:///C:/My%20Documents/file.txt"),
557 "C:/My Documents/file.txt"
558 );
559 assert_eq!(
560 convert("file:///a/b/%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80.txt"),
561 "/a/b/пример.txt"
562 );
563 assert_eq!(
564 convert("file://server/share/folder/file.txt"),
565 "\\\\server\\share\\folder\\file.txt"
566 );
567 }
568
569 #[test]
570 fn test_url_from_file_path_wasm() {
571 #[track_caller]
572 fn convert(path: &str) -> String {
573 url_from_file_path_wasm(Path::new(path))
574 .unwrap()
575 .to_string()
576 }
577
578 assert_eq!(convert("/a/b/c.json"), "file:///a/b/c.json");
579 assert_eq!(
580 convert("D:\\test\\other.json"),
581 "file:///D:/test/other.json"
582 );
583 assert_eq!(
584 convert("/path with spaces/and#special%chars!.json"),
585 "file:///path%20with%20spaces/and%23special%25chars!.json"
586 );
587 assert_eq!(
588 convert("C:\\My Documents\\file.txt"),
589 "file:///C:/My%20Documents/file.txt"
590 );
591 assert_eq!(
592 convert("/a/b/пример.txt"),
593 "file:///a/b/%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80.txt"
594 );
595 assert_eq!(
596 convert("\\\\server\\share\\folder\\file.txt"),
597 "file://server/share/folder/file.txt"
598 );
599 assert_eq!(convert(r#"\\?\UNC\server\share"#), "file://server/share");
600 assert_eq!(
601 convert(r"\\?\cat_pics\subfolder\file.jpg"),
602 "file:///cat_pics/subfolder/file.jpg"
603 );
604 assert_eq!(convert(r"\\?\cat_pics"), "file:///cat_pics");
605 }
606
607 #[test]
608 fn test_url_from_directory_path_wasm() {
609 #[track_caller]
610 fn convert(path: &str) -> String {
611 url_from_directory_path_wasm(Path::new(path))
612 .unwrap()
613 .to_string()
614 }
615
616 assert_eq!(convert("/a/b/c"), "file:///a/b/c/");
617 assert_eq!(convert("D:\\test\\other"), "file:///D:/test/other/");
618 }
619
620 #[cfg(windows)]
621 #[test]
622 fn test_strip_unc_prefix() {
623 use std::path::PathBuf;
624
625 run_test(r"C:\", r"C:\");
626 run_test(r"C:\test\file.txt", r"C:\test\file.txt");
627
628 run_test(r"\\?\C:\", r"C:\");
629 run_test(r"\\?\C:\test\file.txt", r"C:\test\file.txt");
630
631 run_test(r"\\.\C:\", r"\\.\C:\");
632 run_test(r"\\.\C:\Test\file.txt", r"\\.\C:\Test\file.txt");
633
634 run_test(r"\\?\UNC\localhost\", r"\\localhost");
635 run_test(r"\\?\UNC\localhost\c$\", r"\\localhost\c$");
636 run_test(
637 r"\\?\UNC\localhost\c$\Windows\file.txt",
638 r"\\localhost\c$\Windows\file.txt",
639 );
640 run_test(r"\\?\UNC\wsl$\deno.json", r"\\wsl$\deno.json");
641
642 run_test(r"\\?\server1", r"\\server1");
643 run_test(r"\\?\server1\e$\", r"\\server1\e$\");
644 run_test(
645 r"\\?\server1\e$\test\file.txt",
646 r"\\server1\e$\test\file.txt",
647 );
648
649 fn run_test(input: &str, expected: &str) {
650 assert_eq!(
651 super::strip_unc_prefix(PathBuf::from(input)),
652 PathBuf::from(expected)
653 );
654 }
655 }
656
657 #[test]
658 fn test_normalize_path_basic() {
659 let run_test = run_normalize_path_test;
660 run_test("a/../b", "b");
661 run_test("a/./b/", &PathBuf::from("a").join("b").to_string_lossy());
662 run_test(
663 "a/./b/../c",
664 &PathBuf::from("a").join("c").to_string_lossy(),
665 );
666 }
667
668 #[cfg(windows)]
669 #[test]
670 fn test_normalize_path_win() {
671 let run_test = run_normalize_path_test;
672
673 run_test("C:\\test\\file.txt", "C:\\test\\file.txt");
674 run_test("C:\\test\\./file.txt", "C:\\test\\file.txt");
675 run_test("C:\\test\\../other/file.txt", "C:\\other\\file.txt");
676 run_test("C:\\test\\../other\\file.txt", "C:\\other\\file.txt");
677 run_test(
678 "C:\\test\\removes_trailing_slash\\",
679 "C:\\test\\removes_trailing_slash",
680 );
681 run_test("C:\\a\\.\\b\\..\\c", "C:\\a\\c");
682 }
683
684 #[track_caller]
685 fn run_normalize_path_test(input: &str, expected: &str) {
686 assert_eq!(
687 normalize_path(Cow::Owned(PathBuf::from(input))).to_string_lossy(),
688 expected
689 );
690 }
691
692 #[test]
693 fn test_atomic_path() {
694 let sys = sys_traits::impls::InMemorySys::default();
695 sys.set_seed(Some(10));
696 let path = Path::new("/a/b/c.txt");
697 let atomic_path = get_atomic_path(&sys, path);
698 assert_eq!(atomic_path.parent().unwrap(), path.parent().unwrap());
699 assert_eq!(atomic_path.file_name().unwrap(), "c.3d3d3d3d.tmp");
700 }
701
702 #[test]
703 fn test_specifier_has_uri_scheme() {
704 let tests = vec![
705 ("http://foo.bar/etc", true),
706 ("HTTP://foo.bar/etc", true),
707 ("http:ftp:", true),
708 ("http:", true),
709 ("hTtP:", true),
710 ("ftp:", true),
711 ("mailto:spam@please.me", true),
712 ("git+ssh://git@github.com/denoland/deno", true),
713 ("blob:https://whatwg.org/mumbojumbo", true),
714 ("abc.123+DEF-ghi:", true),
715 ("abc.123+def-ghi:@", true),
716 ("", false),
717 (":not", false),
718 ("http", false),
719 ("c:dir", false),
720 ("X:", false),
721 ("./http://not", false),
722 ("1abc://kinda/but/no", false),
723 ("schluẞ://no/more", false),
724 ];
725
726 for (specifier, expected) in tests {
727 let result = specifier_has_uri_scheme(specifier);
728 assert_eq!(result, expected);
729 }
730 }
731
732 #[test]
733 fn test_resolve_url_or_path() {
734 let mut tests: Vec<(&str, String)> = vec![
736 (
737 "http://deno.land/core/tests/006_url_imports.ts",
738 "http://deno.land/core/tests/006_url_imports.ts".to_string(),
739 ),
740 (
741 "https://deno.land/core/tests/006_url_imports.ts",
742 "https://deno.land/core/tests/006_url_imports.ts".to_string(),
743 ),
744 ];
745
746 let cwd = if cfg!(miri) {
749 PathBuf::from("/miri")
750 } else {
751 std::env::current_dir().unwrap()
752 };
753 let cwd_str = cwd.to_str().unwrap();
754
755 if cfg!(target_os = "windows") {
756 let expected_url = "file:///C:/deno/tests/006_url_imports.ts";
758 tests.extend(vec![
759 (
760 r"C:/deno/tests/006_url_imports.ts",
761 expected_url.to_string(),
762 ),
763 (
764 r"C:\deno\tests\006_url_imports.ts",
765 expected_url.to_string(),
766 ),
767 (
768 r"\\?\C:\deno\tests\006_url_imports.ts",
769 expected_url.to_string(),
770 ),
771 ]);
776
777 let expected_url = format!(
779 "file:///{}:/deno/tests/006_url_imports.ts",
780 cwd_str.get(..1).unwrap(),
781 );
782 tests.extend(vec![
783 (r"/deno/tests/006_url_imports.ts", expected_url.to_string()),
784 (r"\deno\tests\006_url_imports.ts", expected_url.to_string()),
785 (
786 r"\deno\..\deno\tests\006_url_imports.ts",
787 expected_url.to_string(),
788 ),
789 (r"\deno\.\tests\006_url_imports.ts", expected_url),
790 ]);
791
792 let expected_url = format!(
794 "file:///{}/tests/006_url_imports.ts",
795 cwd_str.replace('\\', "/")
796 );
797 tests.extend(vec![
798 (r"tests/006_url_imports.ts", expected_url.to_string()),
799 (r"tests\006_url_imports.ts", expected_url.to_string()),
800 (r"./tests/006_url_imports.ts", (*expected_url).to_string()),
801 (r".\tests\006_url_imports.ts", (*expected_url).to_string()),
802 ]);
803
804 let expected_url = "file://server/share/deno/cool";
806 tests.extend(vec![
807 (r"\\server\share\deno\cool", expected_url.to_string()),
808 (r"\\server/share/deno/cool", expected_url.to_string()),
809 ]);
812 } else {
813 let expected_url = "file:///deno/tests/006_url_imports.ts";
815 tests.extend(vec![
816 ("/deno/tests/006_url_imports.ts", expected_url.to_string()),
817 ("//deno/tests/006_url_imports.ts", expected_url.to_string()),
818 ]);
819
820 let expected_url = format!("file://{cwd_str}/tests/006_url_imports.ts");
822 tests.extend(vec![
823 ("tests/006_url_imports.ts", expected_url.to_string()),
824 ("./tests/006_url_imports.ts", expected_url.to_string()),
825 (
826 "tests/../tests/006_url_imports.ts",
827 expected_url.to_string(),
828 ),
829 ("tests/./006_url_imports.ts", expected_url),
830 ]);
831 }
832
833 for (specifier, expected_url) in tests {
834 let url = resolve_url_or_path(specifier, &cwd).unwrap().to_string();
835 assert_eq!(url, expected_url);
836 }
837 }
838
839 #[test]
840 fn test_resolve_url_or_path_deprecated_error() {
841 use url::ParseError::*;
842 use ResolveUrlOrPathError::*;
843
844 let mut tests = vec![
845 ("https://eggplant:b/c", UrlParse(InvalidPort)),
846 ("https://:8080/a/b/c", UrlParse(EmptyHost)),
847 ];
848 if cfg!(target_os = "windows") {
849 let p = r"\\.\c:/stuff/deno/script.ts";
850 tests.push((p, PathToUrl(PathToUrlError(PathBuf::from(p)))));
851 }
852
853 for (specifier, expected_err) in tests {
854 let err =
855 resolve_url_or_path(specifier, &PathBuf::from("/")).unwrap_err();
856 assert_eq!(err, expected_err);
857 }
858 }
859
860 #[test]
861 fn test_is_relative_specifier() {
862 assert!(is_relative_specifier("."));
863 assert!(is_relative_specifier(".."));
864 assert!(is_relative_specifier("../"));
865 assert!(is_relative_specifier("../test"));
866 assert!(is_relative_specifier("./"));
867 assert!(is_relative_specifier("./test"));
868 assert!(!is_relative_specifier(""));
869 assert!(!is_relative_specifier("a"));
870 assert!(!is_relative_specifier(".test"));
871 assert!(!is_relative_specifier("..test"));
872 assert!(!is_relative_specifier("/test"));
873 assert!(!is_relative_specifier("test"));
874 }
875}