diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/bindings.rs | 8 | ||||
-rw-r--r-- | core/lib.rs | 5 | ||||
-rw-r--r-- | core/module_specifier.rs | 282 | ||||
-rw-r--r-- | core/modules.rs | 54 | ||||
-rw-r--r-- | core/runtime.rs | 15 |
5 files changed, 155 insertions, 209 deletions
diff --git a/core/bindings.rs b/core/bindings.rs index 08f244166..1f6b85174 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -454,9 +454,11 @@ fn eval_context( } */ let tc_scope = &mut v8::TryCatch::new(scope); - let name = - v8::String::new(tc_scope, url.as_ref().map_or("<unknown>", Url::as_str)) - .unwrap(); + let name = v8::String::new( + tc_scope, + url.as_ref().map_or(crate::DUMMY_SPECIFIER, Url::as_str), + ) + .unwrap(); let origin = script_origin(tc_scope, name); let maybe_script = v8::Script::compile(tc_scope, source, Some(&origin)); diff --git a/core/lib.rs b/core/lib.rs index 9f4fec003..deea9d281 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -42,8 +42,13 @@ pub use crate::async_cell::AsyncRefFuture; pub use crate::async_cell::RcLike; pub use crate::async_cell::RcRef; pub use crate::flags::v8_set_flags; +pub use crate::module_specifier::resolve_import; +pub use crate::module_specifier::resolve_path; +pub use crate::module_specifier::resolve_url; +pub use crate::module_specifier::resolve_url_or_path; pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleSpecifier; +pub use crate::module_specifier::DUMMY_SPECIFIER; pub use crate::modules::FsModuleLoader; pub use crate::modules::ModuleId; pub use crate::modules::ModuleLoadId; diff --git a/core/module_specifier.rs b/core/module_specifier.rs index a9ce57099..dc6b4d6bf 100644 --- a/core/module_specifier.rs +++ b/core/module_specifier.rs @@ -1,9 +1,6 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use crate::normalize_path; -use serde::de; -use serde::Deserialize; -use serde::Deserializer; use std::env::current_dir; use std::error::Error; use std::fmt; @@ -11,6 +8,8 @@ use std::path::PathBuf; use url::ParseError; use url::Url; +pub const DUMMY_SPECIFIER: &str = "<unknown>"; + /// Error indicating the reason resolving a module specifier failed. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ModuleResolutionError { @@ -51,174 +50,126 @@ impl fmt::Display for ModuleResolutionError { } } -#[derive( - Debug, Clone, Eq, Hash, PartialEq, serde::Serialize, Ord, PartialOrd, -)] /// Resolved module specifier -pub struct ModuleSpecifier(Url); - -impl ModuleSpecifier { - fn is_dummy_specifier(specifier: &str) -> bool { - specifier == "<unknown>" - } - - pub fn as_url(&self) -> &Url { - &self.0 - } - - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - /// Resolves module using this algorithm: - /// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier - pub fn resolve_import( - specifier: &str, - base: &str, - ) -> Result<ModuleSpecifier, ModuleResolutionError> { - let url = match Url::parse(specifier) { - // 1. Apply the URL parser to specifier. - // If the result is not failure, return he result. - Ok(url) => url, - - // 2. If specifier does not start with the character U+002F SOLIDUS (/), - // the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), - // or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, - // U+002F SOLIDUS (../), return failure. - Err(ParseError::RelativeUrlWithoutBase) - if !(specifier.starts_with('/') - || specifier.starts_with("./") - || specifier.starts_with("../")) => - { - let maybe_referrer = if base.is_empty() { - None - } else { - Some(base.to_string()) - }; - return Err(ImportPrefixMissing(specifier.to_string(), maybe_referrer)); - } - - // 3. Return the result of applying the URL parser to specifier with base - // URL as the base URL. - Err(ParseError::RelativeUrlWithoutBase) => { - let base = if ModuleSpecifier::is_dummy_specifier(base) { - // Handle <unknown> case, happening under e.g. repl. - // Use CWD for such case. - - // Forcefully join base to current dir. - // Otherwise, later joining in Url would be interpreted in - // the parent directory (appending trailing slash does not work) - let path = current_dir().unwrap().join(base); - Url::from_file_path(path).unwrap() - } else { - Url::parse(base).map_err(InvalidBaseUrl)? - }; - base.join(&specifier).map_err(InvalidUrl)? - } - - // If parsing the specifier as a URL failed for a different reason than - // it being relative, always return the original error. We don't want to - // return `ImportPrefixMissing` or `InvalidBaseUrl` if the real - // problem lies somewhere else. - Err(err) => return Err(InvalidUrl(err)), - }; - - Ok(ModuleSpecifier(url)) - } - - /// Converts a string representing an absolute URL into a ModuleSpecifier. - pub fn resolve_url( - url_str: &str, - ) -> Result<ModuleSpecifier, ModuleResolutionError> { - Url::parse(url_str) - .map(ModuleSpecifier) - .map_err(ModuleResolutionError::InvalidUrl) - } +pub type ModuleSpecifier = Url; + +/// Resolves module using this algorithm: +/// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier +pub fn resolve_import( + specifier: &str, + base: &str, +) -> Result<ModuleSpecifier, ModuleResolutionError> { + let url = match Url::parse(specifier) { + // 1. Apply the URL parser to specifier. + // If the result is not failure, return he result. + Ok(url) => url, + + // 2. If specifier does not start with the character U+002F SOLIDUS (/), + // the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), + // or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, + // U+002F SOLIDUS (../), return failure. + Err(ParseError::RelativeUrlWithoutBase) + if !(specifier.starts_with('/') + || specifier.starts_with("./") + || specifier.starts_with("../")) => + { + let maybe_referrer = if base.is_empty() { + None + } else { + Some(base.to_string()) + }; + return Err(ImportPrefixMissing(specifier.to_string(), maybe_referrer)); + } - /// Takes a string representing either an absolute URL or a file path, - /// as it may be passed to deno as a command line argument. - /// The string is interpreted as a URL if it starts with a valid URI scheme, - /// e.g. 'http:' or 'file:' or 'git+ssh:'. If not, it's interpreted as a - /// file path; if it is a relative path it's resolved relative to the current - /// working directory. - pub fn resolve_url_or_path( - specifier: &str, - ) -> Result<ModuleSpecifier, ModuleResolutionError> { - if Self::specifier_has_uri_scheme(specifier) { - Self::resolve_url(specifier) - } else { - Self::resolve_path(specifier) + // 3. Return the result of applying the URL parser to specifier with base + // URL as the base URL. + Err(ParseError::RelativeUrlWithoutBase) => { + let base = if base == DUMMY_SPECIFIER { + // Handle <unknown> case, happening under e.g. repl. + // Use CWD for such case. + + // Forcefully join base to current dir. + // Otherwise, later joining in Url would be interpreted in + // the parent directory (appending trailing slash does not work) + let path = current_dir().unwrap().join(base); + Url::from_file_path(path).unwrap() + } else { + Url::parse(base).map_err(InvalidBaseUrl)? + }; + base.join(&specifier).map_err(InvalidUrl)? } - } - /// Converts a string representing a relative or absolute path into a - /// ModuleSpecifier. A relative path is considered relative to the current - /// working directory. - pub fn resolve_path( - path_str: &str, - ) -> Result<ModuleSpecifier, ModuleResolutionError> { - 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)) - } + // If parsing the specifier as a URL failed for a different reason than + // it being relative, always return the original error. We don't want to + // return `ImportPrefixMissing` or `InvalidBaseUrl` if the real + // problem lies somewhere else. + Err(err) => return Err(InvalidUrl(err)), + }; - /// Returns true if the input string starts with a sequence of characters - /// that could be a valid URI scheme, like 'https:', 'git+ssh:' or 'data:'. - /// - /// According to RFC 3986 (https://tools.ietf.org/html/rfc3986#section-3.1), - /// a valid scheme has the following format: - /// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - /// - /// We additionally require the scheme to be at least 2 characters long, - /// because otherwise a windows path like c:/foo would be treated as a URL, - /// while no schemes with a one-letter name actually exist. - fn specifier_has_uri_scheme(specifier: &str) -> bool { - let mut chars = specifier.chars(); - let mut len = 0usize; - // THe first character must be a letter. - match chars.next() { - Some(c) if c.is_ascii_alphabetic() => len += 1, - _ => return false, - } - // Second and following characters must be either a letter, number, - // plus sign, minus sign, or dot. - loop { - match chars.next() { - Some(c) if c.is_ascii_alphanumeric() || "+-.".contains(c) => len += 1, - Some(':') if len >= 2 => return true, - _ => return false, - } - } - } + Ok(url) } -impl fmt::Display for ModuleSpecifier { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } +/// Converts a string representing an absolute URL into a ModuleSpecifier. +pub fn resolve_url( + url_str: &str, +) -> Result<ModuleSpecifier, ModuleResolutionError> { + Url::parse(url_str).map_err(ModuleResolutionError::InvalidUrl) } -impl From<Url> for ModuleSpecifier { - fn from(url: Url) -> Self { - ModuleSpecifier(url) +/// Takes a string representing either an absolute URL or a file path, +/// as it may be passed to deno as a command line argument. +/// The string is interpreted as a URL if it starts with a valid URI scheme, +/// e.g. 'http:' or 'file:' or 'git+ssh:'. If not, it's interpreted as a +/// file path; if it is a relative path it's resolved relative to the current +/// working directory. +pub fn resolve_url_or_path( + specifier: &str, +) -> Result<ModuleSpecifier, ModuleResolutionError> { + if specifier_has_uri_scheme(specifier) { + resolve_url(specifier) + } else { + resolve_path(specifier) } } -impl PartialEq<String> for ModuleSpecifier { - fn eq(&self, other: &String) -> bool { - &self.to_string() == other - } +/// Converts a string representing a relative or absolute path into a +/// ModuleSpecifier. A relative path is considered relative to the current +/// working directory. +pub fn resolve_path( + path_str: &str, +) -> Result<ModuleSpecifier, ModuleResolutionError> { + let path = current_dir().unwrap().join(path_str); + let path = normalize_path(&path); + Url::from_file_path(path.clone()) + .map_err(|()| ModuleResolutionError::InvalidPath(path)) } -impl<'de> Deserialize<'de> for ModuleSpecifier { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let url_str: String = Deserialize::deserialize(deserializer)?; - ModuleSpecifier::resolve_url(&url_str).map_err(de::Error::custom) +/// Returns true if the input string starts with a sequence of characters +/// that could be a valid URI scheme, like 'https:', 'git+ssh:' or 'data:'. +/// +/// According to RFC 3986 (https://tools.ietf.org/html/rfc3986#section-3.1), +/// a valid scheme has the following format: +/// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +/// +/// We additionally require the scheme to be at least 2 characters long, +/// because otherwise a windows path like c:/foo would be treated as a URL, +/// while no schemes with a one-letter name actually exist. +fn specifier_has_uri_scheme(specifier: &str) -> bool { + let mut chars = specifier.chars(); + let mut len = 0usize; + // THe first character must be a letter. + match chars.next() { + Some(c) if c.is_ascii_alphabetic() => len += 1, + _ => return false, + } + // Second and following characters must be either a letter, number, + // plus sign, minus sign, or dot. + loop { + match chars.next() { + Some(c) if c.is_ascii_alphanumeric() || "+-.".contains(c) => len += 1, + Some(':') if len >= 2 => return true, + _ => return false, + } } } @@ -306,9 +257,7 @@ mod tests { ]; for (specifier, base, expected_url) in tests { - let url = ModuleSpecifier::resolve_import(specifier, base) - .unwrap() - .to_string(); + let url = resolve_import(specifier, base).unwrap().to_string(); assert_eq!(url, expected_url); } } @@ -385,7 +334,7 @@ mod tests { ]; for (specifier, base, expected_err) in tests { - let err = ModuleSpecifier::resolve_import(specifier, base).unwrap_err(); + let err = resolve_import(specifier, base).unwrap_err(); assert_eq!(err, expected_err); } } @@ -487,9 +436,7 @@ mod tests { } for (specifier, expected_url) in tests { - let url = ModuleSpecifier::resolve_url_or_path(specifier) - .unwrap() - .to_string(); + let url = resolve_url_or_path(specifier).unwrap().to_string(); assert_eq!(url, expected_url); } } @@ -509,7 +456,7 @@ mod tests { } for (specifier, expected_err) in tests { - let err = ModuleSpecifier::resolve_url_or_path(specifier).unwrap_err(); + let err = resolve_url_or_path(specifier).unwrap_err(); assert_eq!(err, expected_err); } } @@ -539,7 +486,7 @@ mod tests { ]; for (specifier, expected) in tests { - let result = ModuleSpecifier::specifier_has_uri_scheme(specifier); + let result = specifier_has_uri_scheme(specifier); assert_eq!(result, expected); } } @@ -565,8 +512,7 @@ mod tests { fn test_deserialize_module_specifier() { let actual: ModuleSpecifier = from_value(json!("http://deno.land/x/mod.ts")).unwrap(); - let expected = - ModuleSpecifier::resolve_url("http://deno.land/x/mod.ts").unwrap(); + let expected = resolve_url("http://deno.land/x/mod.ts").unwrap(); assert_eq!(actual, expected); } } diff --git a/core/modules.rs b/core/modules.rs index 815db2fb9..aefb3e491 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -145,7 +145,7 @@ impl ModuleLoader for FsModuleLoader { referrer: &str, _is_main: bool, ) -> Result<ModuleSpecifier, AnyError> { - Ok(ModuleSpecifier::resolve_import(specifier, referrer)?) + Ok(crate::resolve_import(specifier, referrer)?) } fn load( @@ -157,7 +157,7 @@ impl ModuleLoader for FsModuleLoader { ) -> Pin<Box<ModuleSourceFuture>> { let module_specifier = module_specifier.clone(); async move { - let path = module_specifier.as_url().to_file_path().map_err(|_| { + let path = module_specifier.to_file_path().map_err(|_| { generic_error(format!( "Provided module specifier \"{}\" is not a file URL.", module_specifier @@ -649,11 +649,10 @@ mod tests { eprintln!(">> RESOLVING, S: {}, R: {}", specifier, referrer); - let output_specifier = - match ModuleSpecifier::resolve_import(specifier, referrer) { - Ok(specifier) => specifier, - Err(..) => return Err(MockError::ResolveErr.into()), - }; + let output_specifier = match crate::resolve_import(specifier, referrer) { + Ok(specifier) => specifier, + Err(..) => return Err(MockError::ResolveErr.into()), + }; if mock_source_code(&output_specifier.to_string()).is_some() { Ok(output_specifier) @@ -715,7 +714,7 @@ mod tests { module_loader: Some(loader), ..Default::default() }); - let spec = ModuleSpecifier::resolve_url("file:///a.js").unwrap(); + let spec = crate::resolve_url("file:///a.js").unwrap(); let a_id_fut = runtime.load_module(&spec, None); let a_id = futures::executor::block_on(a_id_fut).expect("Failed to load"); @@ -741,17 +740,17 @@ mod tests { assert_eq!( modules.get_children(a_id), Some(&vec![ - ModuleSpecifier::resolve_url("file:///b.js").unwrap(), - ModuleSpecifier::resolve_url("file:///c.js").unwrap() + crate::resolve_url("file:///b.js").unwrap(), + crate::resolve_url("file:///c.js").unwrap() ]) ); assert_eq!( modules.get_children(b_id), - Some(&vec![ModuleSpecifier::resolve_url("file:///c.js").unwrap()]) + Some(&vec![crate::resolve_url("file:///c.js").unwrap()]) ); assert_eq!( modules.get_children(c_id), - Some(&vec![ModuleSpecifier::resolve_url("file:///d.js").unwrap()]) + Some(&vec![crate::resolve_url("file:///d.js").unwrap()]) ); assert_eq!(modules.get_children(d_id), Some(&vec![])); } @@ -782,7 +781,7 @@ mod tests { }); let fut = async move { - let spec = ModuleSpecifier::resolve_url("file:///circular1.js").unwrap(); + let spec = crate::resolve_url("file:///circular1.js").unwrap(); let result = runtime.load_module(&spec, None).await; assert!(result.is_ok()); let circular1_id = result.unwrap(); @@ -807,16 +806,12 @@ mod tests { assert_eq!( modules.get_children(circular1_id), - Some(&vec![ - ModuleSpecifier::resolve_url("file:///circular2.js").unwrap() - ]) + Some(&vec![crate::resolve_url("file:///circular2.js").unwrap()]) ); assert_eq!( modules.get_children(circular2_id), - Some(&vec![ - ModuleSpecifier::resolve_url("file:///circular3.js").unwrap() - ]) + Some(&vec![crate::resolve_url("file:///circular3.js").unwrap()]) ); assert!(modules.get_id("file:///circular3.js").is_some()); @@ -824,8 +819,8 @@ mod tests { assert_eq!( modules.get_children(circular3_id), Some(&vec![ - ModuleSpecifier::resolve_url("file:///circular1.js").unwrap(), - ModuleSpecifier::resolve_url("file:///circular2.js").unwrap() + crate::resolve_url("file:///circular1.js").unwrap(), + crate::resolve_url("file:///circular2.js").unwrap() ]) ); } @@ -858,7 +853,7 @@ mod tests { }); let fut = async move { - let spec = ModuleSpecifier::resolve_url("file:///redirect1.js").unwrap(); + let spec = crate::resolve_url("file:///redirect1.js").unwrap(); let result = runtime.load_module(&spec, None).await; println!(">> result {:?}", result); assert!(result.is_ok()); @@ -923,7 +918,7 @@ mod tests { module_loader: Some(loader), ..Default::default() }); - let spec = ModuleSpecifier::resolve_url("file:///main.js").unwrap(); + let spec = crate::resolve_url("file:///main.js").unwrap(); let mut recursive_load = runtime.load_module(&spec, None).boxed_local(); let result = recursive_load.poll_unpin(&mut cx); @@ -971,7 +966,7 @@ mod tests { module_loader: Some(loader), ..Default::default() }); - let spec = ModuleSpecifier::resolve_url("file:///bad_import.js").unwrap(); + let spec = crate::resolve_url("file:///bad_import.js").unwrap(); let mut load_fut = runtime.load_module(&spec, None).boxed_local(); let result = load_fut.poll_unpin(&mut cx); if let Poll::Ready(Err(err)) = result { @@ -1005,8 +1000,7 @@ mod tests { // In default resolution code should be empty. // Instead we explicitly pass in our own code. // The behavior should be very similar to /a.js. - let spec = - ModuleSpecifier::resolve_url("file:///main_with_code.js").unwrap(); + let spec = crate::resolve_url("file:///main_with_code.js").unwrap(); let main_id_fut = runtime .load_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned())) .boxed_local(); @@ -1033,17 +1027,17 @@ mod tests { assert_eq!( modules.get_children(main_id), Some(&vec![ - ModuleSpecifier::resolve_url("file:///b.js").unwrap(), - ModuleSpecifier::resolve_url("file:///c.js").unwrap() + crate::resolve_url("file:///b.js").unwrap(), + crate::resolve_url("file:///c.js").unwrap() ]) ); assert_eq!( modules.get_children(b_id), - Some(&vec![ModuleSpecifier::resolve_url("file:///c.js").unwrap()]) + Some(&vec![crate::resolve_url("file:///c.js").unwrap()]) ); assert_eq!( modules.get_children(c_id), - Some(&vec![ModuleSpecifier::resolve_url("file:///d.js").unwrap()]) + Some(&vec![crate::resolve_url("file:///d.js").unwrap()]) ); assert_eq!(modules.get_children(d_id), Some(&vec![])); } diff --git a/core/runtime.rs b/core/runtime.rs index 9db1669cd..31229aaa3 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -1224,8 +1224,7 @@ impl JsRuntime { let is_main = load.state == LoadState::LoadingRoot && !load.is_dynamic_import(); - let referrer_specifier = - ModuleSpecifier::resolve_url(&module_url_found).unwrap(); + let referrer_specifier = crate::resolve_url(&module_url_found).unwrap(); let state_rc = Self::state(self.v8_isolate()); // #A There are 3 cases to handle at this moment: @@ -2200,7 +2199,7 @@ pub mod tests { self.count.fetch_add(1, Ordering::Relaxed); assert_eq!(specifier, "./b.js"); assert_eq!(referrer, "file:///a.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } @@ -2272,7 +2271,7 @@ pub mod tests { let imports = state.modules.get_children(mod_a); assert_eq!( imports, - Some(&vec![ModuleSpecifier::resolve_url("file:///b.js").unwrap()]) + Some(&vec![crate::resolve_url("file:///b.js").unwrap()]) ); } let mod_b = runtime @@ -2313,7 +2312,7 @@ pub mod tests { self.count.fetch_add(1, Ordering::Relaxed); assert_eq!(specifier, "/foo.js"); assert_eq!(referrer, "file:///dyn_import2.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } @@ -2377,7 +2376,7 @@ pub mod tests { assert!(c < 4); assert_eq!(specifier, "./b.js"); assert_eq!(referrer, "file:///dyn_import3.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } @@ -2504,7 +2503,7 @@ pub mod tests { ) -> Result<ModuleSpecifier, AnyError> { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } @@ -2526,7 +2525,7 @@ pub mod tests { ..Default::default() }); - let specifier = ModuleSpecifier::resolve_url("file:///main.js").unwrap(); + let specifier = crate::resolve_url("file:///main.js").unwrap(); let source_code = "Deno.core.print('hello\\n')".to_string(); let module_id = futures::executor::block_on( |