diff options
-rw-r--r-- | cli/compiler.rs | 8 | ||||
-rw-r--r-- | cli/flags.rs | 4 | ||||
-rw-r--r-- | cli/import_map.rs | 642 | ||||
-rw-r--r-- | cli/main.rs | 90 | ||||
-rw-r--r-- | cli/module_specifier.rs | 81 | ||||
-rw-r--r-- | cli/ops.rs | 15 | ||||
-rw-r--r-- | cli/state.rs | 116 | ||||
-rw-r--r-- | cli/worker.rs | 108 |
8 files changed, 520 insertions, 544 deletions
diff --git a/cli/compiler.rs b/cli/compiler.rs index 4233262a3..a33ccfcb0 100644 --- a/cli/compiler.rs +++ b/cli/compiler.rs @@ -247,8 +247,8 @@ mod tests { fn test_compile_sync() { tokio_util::init(|| { let specifier = "./tests/002_hello.ts"; - use crate::worker; - let module_name = worker::root_specifier_to_url(specifier) + use crate::module_specifier::ModuleSpecifier; + let module_name = ModuleSpecifier::resolve_root(specifier) .unwrap() .to_string(); @@ -294,8 +294,8 @@ mod tests { #[test] fn test_bundle_async() { let specifier = "./tests/002_hello.ts"; - use crate::worker; - let module_name = worker::root_specifier_to_url(specifier) + use crate::module_specifier::ModuleSpecifier; + let module_name = ModuleSpecifier::resolve_root(specifier) .unwrap() .to_string(); diff --git a/cli/flags.rs b/cli/flags.rs index 41d3f9308..6d4a95f9e 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -502,8 +502,8 @@ pub enum DenoSubcommand { } fn get_default_bundle_filename(source_file: &str) -> String { - use crate::worker::root_specifier_to_url; - let url = root_specifier_to_url(source_file).unwrap(); + use crate::module_specifier::ModuleSpecifier; + let url = ModuleSpecifier::resolve_root(source_file).unwrap().to_url(); let path_segments = url.path_segments().unwrap(); let last = path_segments.last().unwrap(); String::from(last.trim_end_matches(".ts").trim_end_matches(".js")) diff --git a/cli/import_map.rs b/cli/import_map.rs index 5fe423595..6f4095a90 100644 --- a/cli/import_map.rs +++ b/cli/import_map.rs @@ -1,3 +1,4 @@ +use crate::module_specifier::ModuleSpecifier; use indexmap::IndexMap; use serde_json::Map; use serde_json::Value; @@ -22,7 +23,7 @@ impl ImportMapError { // can't resolve URL with other schemes (eg. data:, about:, blob:) const SUPPORTED_FETCH_SCHEMES: [&str; 3] = ["http", "https", "file"]; -type SpecifierMap = IndexMap<String, Vec<String>>; +type SpecifierMap = IndexMap<String, Vec<ModuleSpecifier>>; type ScopesMap = IndexMap<String, SpecifierMap>; #[derive(Debug)] @@ -157,8 +158,8 @@ impl ImportMap { specifier_key: &str, base_url: &str, potential_addresses: Vec<String>, - ) -> Vec<String> { - let mut normalized_addresses: Vec<String> = vec![]; + ) -> Vec<ModuleSpecifier> { + let mut normalized_addresses: Vec<ModuleSpecifier> = vec![]; for potential_address in potential_addresses { let url = @@ -177,7 +178,9 @@ impl ImportMap { continue; } - normalized_addresses.push(url_string); + let normalized_address = ModuleSpecifier::resolve(&url_string, ".") + .expect("Address should be valid module specifier"); + normalized_addresses.push(normalized_address); } normalized_addresses @@ -311,7 +314,7 @@ impl ImportMap { scopes: &ScopesMap, normalized_specifier: &str, referrer: &str, - ) -> Result<Option<String>, ImportMapError> { + ) -> Result<Option<ModuleSpecifier>, ImportMapError> { // exact-match if let Some(scope_imports) = scopes.get(referrer) { if let Ok(scope_match) = @@ -347,7 +350,7 @@ impl ImportMap { pub fn resolve_imports_match( imports: &SpecifierMap, normalized_specifier: &str, - ) -> Result<Option<String>, ImportMapError> { + ) -> Result<Option<ModuleSpecifier>, ImportMapError> { // exact-match if let Some(address_vec) = imports.get(normalized_specifier) { if address_vec.is_empty() { @@ -361,7 +364,7 @@ impl ImportMap { "Specifier {:?} was mapped to {:?}.", normalized_specifier, address ); - return Ok(Some(address.to_string())); + return Ok(Some(address.clone())); } else { return Err(ImportMapError::new( "Multi-address mappings are not yet supported", @@ -383,12 +386,10 @@ impl ImportMap { let address = address_vec.first().unwrap(); let after_prefix = &normalized_specifier[specifier_key.len()..]; - if let Ok(base_url) = Url::parse(address) { - if let Ok(url) = base_url.join(after_prefix) { - let resolved_url = url.to_string(); - debug!("Specifier {:?} was mapped to {:?} (via prefix specifier key {:?}).", normalized_specifier, resolved_url, address); - return Ok(Some(resolved_url)); - } + let base_url = address.to_url(); + if let Ok(url) = base_url.join(after_prefix) { + debug!("Specifier {:?} was mapped to {:?} (via prefix specifier key {:?}).", normalized_specifier, url, address); + return Ok(Some(ModuleSpecifier::from(url))); } unreachable!(); @@ -420,7 +421,7 @@ impl ImportMap { &self, specifier: &str, referrer: &str, - ) -> Result<Option<String>, ImportMapError> { + ) -> Result<Option<ModuleSpecifier>, ImportMapError> { let resolved_url: Option<Url> = ImportMap::try_url_like_specifier(specifier, referrer); let normalized_specifier = match &resolved_url { @@ -449,7 +450,7 @@ impl ImportMap { // no match in import map but we got resolvable URL if let Some(resolved_url) = resolved_url { - return Ok(Some(resolved_url.to_string())); + return Ok(Some(ModuleSpecifier::from(resolved_url))); } Err(ImportMapError::new(&format!( @@ -1250,43 +1251,52 @@ mod tests { } } + fn assert_resolve( + result: Result<Option<ModuleSpecifier>, ImportMapError>, + expected_url: &str, + ) { + let maybe_url = result + .unwrap_or_else(|err| panic!("ImportMap::resolve failed: {:?}", err)); + let resolved_url = + maybe_url.unwrap_or_else(|| panic!("Unexpected None resolved URL")); + assert_eq!(resolved_url, expected_url.to_string()); + } + #[test] fn resolve_unmapped_relative_specifiers() { let referrer_url = "https://example.com/js/script.ts"; let import_map = get_empty_import_map(); // Should resolve ./ specifiers as URLs. - assert_eq!( - import_map.resolve("./foo", referrer_url).unwrap(), - Some("https://example.com/js/foo".to_string()) + assert_resolve( + import_map.resolve("./foo", referrer_url), + "https://example.com/js/foo", ); - assert_eq!( - import_map.resolve("./foo/bar", referrer_url).unwrap(), - Some("https://example.com/js/foo/bar".to_string()) + assert_resolve( + import_map.resolve("./foo/bar", referrer_url), + "https://example.com/js/foo/bar", ); - assert_eq!( - import_map.resolve("./foo/../bar", referrer_url).unwrap(), - Some("https://example.com/js/bar".to_string()) + assert_resolve( + import_map.resolve("./foo/../bar", referrer_url), + "https://example.com/js/bar", ); - assert_eq!( - import_map.resolve("./foo/../../bar", referrer_url).unwrap(), - Some("https://example.com/bar".to_string()) + assert_resolve( + import_map.resolve("./foo/../../bar", referrer_url), + "https://example.com/bar", ); // Should resolve ../ specifiers as URLs. - assert_eq!( - import_map.resolve("../foo", referrer_url).unwrap(), - Some("https://example.com/foo".to_string()) + assert_resolve( + import_map.resolve("../foo", referrer_url), + "https://example.com/foo", ); - assert_eq!( - import_map.resolve("../foo/bar", referrer_url).unwrap(), - Some("https://example.com/foo/bar".to_string()) + assert_resolve( + import_map.resolve("../foo/bar", referrer_url), + "https://example.com/foo/bar", ); - assert_eq!( - import_map - .resolve("../../../foo/bar", referrer_url) - .unwrap(), - Some("https://example.com/foo/bar".to_string()) + assert_resolve( + import_map.resolve("../../../foo/bar", referrer_url), + "https://example.com/foo/bar", ); } @@ -1296,47 +1306,39 @@ mod tests { let import_map = get_empty_import_map(); // Should resolve / specifiers as URLs. - assert_eq!( - import_map.resolve("/foo", referrer_url).unwrap(), - Some("https://example.com/foo".to_string()) + assert_resolve( + import_map.resolve("/foo", referrer_url), + "https://example.com/foo", ); - assert_eq!( - import_map.resolve("/foo/bar", referrer_url).unwrap(), - Some("https://example.com/foo/bar".to_string()) + assert_resolve( + import_map.resolve("/foo/bar", referrer_url), + "https://example.com/foo/bar", ); - assert_eq!( - import_map.resolve("../../foo/bar", referrer_url).unwrap(), - Some("https://example.com/foo/bar".to_string()) + assert_resolve( + import_map.resolve("../../foo/bar", referrer_url), + "https://example.com/foo/bar", ); - assert_eq!( - import_map.resolve("/../foo/../bar", referrer_url).unwrap(), - Some("https://example.com/bar".to_string()) + assert_resolve( + import_map.resolve("/../foo/../bar", referrer_url), + "https://example.com/bar", ); // Should parse absolute fetch-scheme URLs. - assert_eq!( - import_map - .resolve("https://example.net", referrer_url) - .unwrap(), - Some("https://example.net/".to_string()) + assert_resolve( + import_map.resolve("https://example.net", referrer_url), + "https://example.net/", ); - assert_eq!( - import_map - .resolve("https://ex%41mple.com/", referrer_url) - .unwrap(), - Some("https://example.com/".to_string()) + assert_resolve( + import_map.resolve("https://ex%41mple.com/", referrer_url), + "https://example.com/", ); - assert_eq!( - import_map - .resolve("https:example.org", referrer_url) - .unwrap(), - Some("https://example.org/".to_string()) + assert_resolve( + import_map.resolve("https:example.org", referrer_url), + "https://example.org/", ); - assert_eq!( - import_map - .resolve("https://///example.com///", referrer_url) - .unwrap(), - Some("https://example.com///".to_string()) + assert_resolve( + import_map.resolve("https://///example.com///", referrer_url), + "https://example.com///", ); } @@ -1414,39 +1416,37 @@ mod tests { let import_map = ImportMap::from_json(base_url, json_map).unwrap(); // Should work for package main modules. - assert_eq!( - import_map.resolve("moment", referrer_url).unwrap(), - Some("https://example.com/deps/moment/src/moment.js".to_string()) + assert_resolve( + import_map.resolve("moment", referrer_url), + "https://example.com/deps/moment/src/moment.js", ); - assert_eq!( - import_map.resolve("lodash-dot", referrer_url).unwrap(), - Some("https://example.com/app/deps/lodash-es/lodash.js".to_string()) + assert_resolve( + import_map.resolve("lodash-dot", referrer_url), + "https://example.com/app/deps/lodash-es/lodash.js", ); - assert_eq!( - import_map.resolve("lodash-dotdot", referrer_url).unwrap(), - Some("https://example.com/deps/lodash-es/lodash.js".to_string()) + assert_resolve( + import_map.resolve("lodash-dotdot", referrer_url), + "https://example.com/deps/lodash-es/lodash.js", ); // Should work for package submodules. - assert_eq!( - import_map.resolve("moment/foo", referrer_url).unwrap(), - Some("https://example.com/deps/moment/src/foo".to_string()) + assert_resolve( + import_map.resolve("moment/foo", referrer_url), + "https://example.com/deps/moment/src/foo", ); - assert_eq!( - import_map.resolve("lodash-dot/foo", referrer_url).unwrap(), - Some("https://example.com/app/deps/lodash-es/foo".to_string()) + assert_resolve( + import_map.resolve("lodash-dot/foo", referrer_url), + "https://example.com/app/deps/lodash-es/foo", ); - assert_eq!( - import_map - .resolve("lodash-dotdot/foo", referrer_url) - .unwrap(), - Some("https://example.com/deps/lodash-es/foo".to_string()) + assert_resolve( + import_map.resolve("lodash-dotdot/foo", referrer_url), + "https://example.com/deps/lodash-es/foo", ); // Should work for package names that end in a slash. - assert_eq!( - import_map.resolve("moment/", referrer_url).unwrap(), - Some("https://example.com/deps/moment/src/".to_string()) + assert_resolve( + import_map.resolve("moment/", referrer_url), + "https://example.com/deps/moment/src/", ); // Should fail for package modules that are not declared. @@ -1476,33 +1476,31 @@ mod tests { let import_map = ImportMap::from_json(base_url, json_map).unwrap(); // Should work for explicitly-mapped specifiers that happen to have a slash. - assert_eq!( - import_map - .resolve("package/withslash", referrer_url) - .unwrap(), - Some("https://example.com/deps/package-with-slash/index.mjs".to_string()) + assert_resolve( + import_map.resolve("package/withslash", referrer_url), + "https://example.com/deps/package-with-slash/index.mjs", ); // Should work when the specifier has punctuation. - assert_eq!( - import_map.resolve(".", referrer_url).unwrap(), - Some("https://example.com/lib/dot.mjs".to_string()) + assert_resolve( + import_map.resolve(".", referrer_url), + "https://example.com/lib/dot.mjs", ); - assert_eq!( - import_map.resolve("..", referrer_url).unwrap(), - Some("https://example.com/lib/dotdot.mjs".to_string()) + assert_resolve( + import_map.resolve("..", referrer_url), + "https://example.com/lib/dotdot.mjs", ); - assert_eq!( - import_map.resolve("..\\\\", referrer_url).unwrap(), - Some("https://example.com/lib/dotdotbackslash.mjs".to_string()) + assert_resolve( + import_map.resolve("..\\\\", referrer_url), + "https://example.com/lib/dotdotbackslash.mjs", ); - assert_eq!( - import_map.resolve("%2E", referrer_url).unwrap(), - Some("https://example.com/lib/percent2e.mjs".to_string()) + assert_resolve( + import_map.resolve("%2E", referrer_url), + "https://example.com/lib/percent2e.mjs", ); - assert_eq!( - import_map.resolve("%2F", referrer_url).unwrap(), - Some("https://example.com/lib/percent2f.mjs".to_string()) + assert_resolve( + import_map.resolve("%2F", referrer_url), + "https://example.com/lib/percent2f.mjs", ); // Should fail for attempting to get a submodule of something not declared with a trailing slash. @@ -1537,45 +1535,35 @@ mod tests { let import_map = ImportMap::from_json(base_url, json_map).unwrap(); // Should remap to other URLs. - assert_eq!( - import_map - .resolve("https://example.com/lib/foo.mjs", referrer_url) - .unwrap(), - Some("https://example.com/app/more/bar.mjs".to_string()) + assert_resolve( + import_map.resolve("https://example.com/lib/foo.mjs", referrer_url), + "https://example.com/app/more/bar.mjs", ); - assert_eq!( - import_map - .resolve("https://///example.com/lib/foo.mjs", referrer_url) - .unwrap(), - Some("https://example.com/app/more/bar.mjs".to_string()) + assert_resolve( + import_map.resolve("https://///example.com/lib/foo.mjs", referrer_url), + "https://example.com/app/more/bar.mjs", ); - assert_eq!( - import_map.resolve("/lib/foo.mjs", referrer_url).unwrap(), - Some("https://example.com/app/more/bar.mjs".to_string()) + assert_resolve( + import_map.resolve("/lib/foo.mjs", referrer_url), + "https://example.com/app/more/bar.mjs", ); - assert_eq!( + assert_resolve( import_map - .resolve("https://example.com/app/dotrelative/foo.mjs", referrer_url) - .unwrap(), - Some("https://example.com/lib/dot.mjs".to_string()) + .resolve("https://example.com/app/dotrelative/foo.mjs", referrer_url), + "https://example.com/lib/dot.mjs", ); - assert_eq!( - import_map - .resolve("../app/dotrelative/foo.mjs", referrer_url) - .unwrap(), - Some("https://example.com/lib/dot.mjs".to_string()) + assert_resolve( + import_map.resolve("../app/dotrelative/foo.mjs", referrer_url), + "https://example.com/lib/dot.mjs", ); - assert_eq!( + assert_resolve( import_map - .resolve("https://example.com/dotdotrelative/foo.mjs", referrer_url) - .unwrap(), - Some("https://example.com/lib/dotdot.mjs".to_string()) + .resolve("https://example.com/dotdotrelative/foo.mjs", referrer_url), + "https://example.com/lib/dotdot.mjs", ); - assert_eq!( - import_map - .resolve("../dotdotrelative/foo.mjs", referrer_url) - .unwrap(), - Some("https://example.com/lib/dotdot.mjs".to_string()) + assert_resolve( + import_map.resolve("../dotdotrelative/foo.mjs", referrer_url), + "https://example.com/lib/dotdot.mjs", ); // Should fail for URLs that remap to empty arrays. @@ -1603,55 +1591,47 @@ mod tests { ); // Should remap URLs that are just composed from / and .. - assert_eq!( - import_map - .resolve("https://example.com/", referrer_url) - .unwrap(), - Some("https://example.com/lib/slash-only/".to_string()) + assert_resolve( + import_map.resolve("https://example.com/", referrer_url), + "https://example.com/lib/slash-only/", ); - assert_eq!( - import_map.resolve("/", referrer_url).unwrap(), - Some("https://example.com/lib/slash-only/".to_string()) + assert_resolve( + import_map.resolve("/", referrer_url), + "https://example.com/lib/slash-only/", ); - assert_eq!( - import_map.resolve("../", referrer_url).unwrap(), - Some("https://example.com/lib/slash-only/".to_string()) + assert_resolve( + import_map.resolve("../", referrer_url), + "https://example.com/lib/slash-only/", ); - assert_eq!( - import_map - .resolve("https://example.com/app/", referrer_url) - .unwrap(), - Some("https://example.com/lib/dotslash-only/".to_string()) + assert_resolve( + import_map.resolve("https://example.com/app/", referrer_url), + "https://example.com/lib/dotslash-only/", ); - assert_eq!( - import_map.resolve("/app/", referrer_url).unwrap(), - Some("https://example.com/lib/dotslash-only/".to_string()) + assert_resolve( + import_map.resolve("/app/", referrer_url), + "https://example.com/lib/dotslash-only/", ); - assert_eq!( - import_map.resolve("../app/", referrer_url).unwrap(), - Some("https://example.com/lib/dotslash-only/".to_string()) + assert_resolve( + import_map.resolve("../app/", referrer_url), + "https://example.com/lib/dotslash-only/", ); // Should remap URLs that are prefix-matched by keys with trailing slashes. - assert_eq!( - import_map.resolve("/test/foo.mjs", referrer_url).unwrap(), - Some("https://example.com/lib/url-trailing-slash/foo.mjs".to_string()) + assert_resolve( + import_map.resolve("/test/foo.mjs", referrer_url), + "https://example.com/lib/url-trailing-slash/foo.mjs", ); - assert_eq!( - import_map - .resolve("https://example.com/app/test/foo.mjs", referrer_url) - .unwrap(), - Some( - "https://example.com/lib/url-trailing-slash-dot/foo.mjs".to_string() - ) + assert_resolve( + import_map.resolve("https://example.com/app/test/foo.mjs", referrer_url), + "https://example.com/lib/url-trailing-slash-dot/foo.mjs", ); // Should use the last entry's address when URL-like specifiers parse to the same absolute URL. // // NOTE: this works properly because of "preserve_order" feature flag to "serde_json" crate - assert_eq!( - import_map.resolve("/test", referrer_url).unwrap(), - Some("https://example.com/lib/test2.mjs".to_string()) + assert_resolve( + import_map.resolve("/test", referrer_url), + "https://example.com/lib/test2.mjs", ); } @@ -1672,25 +1652,25 @@ mod tests { }"#; let import_map = ImportMap::from_json(base_url, json_map).unwrap(); - assert_eq!( - import_map.resolve("a", referrer_url).unwrap(), - Some("https://example.com/1".to_string()) + assert_resolve( + import_map.resolve("a", referrer_url), + "https://example.com/1", ); - assert_eq!( - import_map.resolve("a/", referrer_url).unwrap(), - Some("https://example.com/2/".to_string()) + assert_resolve( + import_map.resolve("a/", referrer_url), + "https://example.com/2/", ); - assert_eq!( - import_map.resolve("a/b", referrer_url).unwrap(), - Some("https://example.com/3".to_string()) + assert_resolve( + import_map.resolve("a/b", referrer_url), + "https://example.com/3", ); - assert_eq!( - import_map.resolve("a/b/", referrer_url).unwrap(), - Some("https://example.com/4/".to_string()) + assert_resolve( + import_map.resolve("a/b/", referrer_url), + "https://example.com/4/", ); - assert_eq!( - import_map.resolve("a/b/c", referrer_url).unwrap(), - Some("https://example.com/4/c".to_string()) + assert_resolve( + import_map.resolve("a/b/c", referrer_url), + "https://example.com/4/c", ); } @@ -1709,17 +1689,17 @@ mod tests { assert!(import_map.resolve("a", referrer_url).is_err()); assert!(import_map.resolve("a/", referrer_url).is_err()); assert!(import_map.resolve("a/x", referrer_url).is_err()); - assert_eq!( - import_map.resolve("a/b", referrer_url).unwrap(), - Some("https://example.com/3".to_string()) + assert_resolve( + import_map.resolve("a/b", referrer_url), + "https://example.com/3", ); - assert_eq!( - import_map.resolve("a/b/", referrer_url).unwrap(), - Some("https://example.com/4/".to_string()) + assert_resolve( + import_map.resolve("a/b/", referrer_url), + "https://example.com/4/", ); - assert_eq!( - import_map.resolve("a/b/c", referrer_url).unwrap(), - Some("https://example.com/4/c".to_string()) + assert_resolve( + import_map.resolve("a/b/c", referrer_url), + "https://example.com/4/c", ); assert!(import_map.resolve("a/x/c", referrer_url).is_err()); } @@ -1766,25 +1746,21 @@ mod tests { let js_in_dir = "https://example.com/js/app.mjs"; let with_js_prefix = "https://example.com/jsiscool"; - assert_eq!( - import_map.resolve("moment", js_non_dir).unwrap(), - Some("https://example.com/only-triggered-by-exact/moment".to_string()) + assert_resolve( + import_map.resolve("moment", js_non_dir), + "https://example.com/only-triggered-by-exact/moment", ); - assert_eq!( - import_map.resolve("moment/foo", js_non_dir).unwrap(), - Some( - "https://example.com/only-triggered-by-exact/moment/foo".to_string() - ) + assert_resolve( + import_map.resolve("moment/foo", js_non_dir), + "https://example.com/only-triggered-by-exact/moment/foo", ); - assert_eq!( - import_map.resolve("moment", js_in_dir).unwrap(), - Some("https://example.com/triggered-by-any-subpath/moment".to_string()) + assert_resolve( + import_map.resolve("moment", js_in_dir), + "https://example.com/triggered-by-any-subpath/moment", ); - assert_eq!( - import_map.resolve("moment/foo", js_in_dir).unwrap(), - Some( - "https://example.com/triggered-by-any-subpath/moment/foo".to_string() - ) + assert_resolve( + import_map.resolve("moment/foo", js_in_dir), + "https://example.com/triggered-by-any-subpath/moment/foo", ); assert!(import_map.resolve("moment", with_js_prefix).is_err()); assert!(import_map.resolve("moment/foo", with_js_prefix).is_err()); @@ -1809,15 +1785,13 @@ mod tests { let js_in_dir = "https://example.com/js/app.mjs"; let with_js_prefix = "https://example.com/jsiscool"; - assert_eq!( - import_map.resolve("moment", js_non_dir).unwrap(), - Some("https://example.com/only-triggered-by-exact/moment".to_string()) + assert_resolve( + import_map.resolve("moment", js_non_dir), + "https://example.com/only-triggered-by-exact/moment", ); - assert_eq!( - import_map.resolve("moment/foo", js_non_dir).unwrap(), - Some( - "https://example.com/only-triggered-by-exact/moment/foo".to_string() - ) + assert_resolve( + import_map.resolve("moment/foo", js_non_dir), + "https://example.com/only-triggered-by-exact/moment/foo", ); assert!(import_map.resolve("moment", js_in_dir).is_err()); assert!(import_map.resolve("moment/foo", js_in_dir).is_err()); @@ -1846,15 +1820,13 @@ mod tests { assert!(import_map.resolve("moment", js_non_dir).is_err()); assert!(import_map.resolve("moment/foo", js_non_dir).is_err()); - assert_eq!( - import_map.resolve("moment", js_in_dir).unwrap(), - Some("https://example.com/triggered-by-any-subpath/moment".to_string()) + assert_resolve( + import_map.resolve("moment", js_in_dir), + "https://example.com/triggered-by-any-subpath/moment", ); - assert_eq!( - import_map.resolve("moment/foo", js_in_dir).unwrap(), - Some( - "https://example.com/triggered-by-any-subpath/moment/foo".to_string() - ) + assert_resolve( + import_map.resolve("moment/foo", js_in_dir), + "https://example.com/triggered-by-any-subpath/moment/foo", ); assert!(import_map.resolve("moment", with_js_prefix).is_err()); assert!(import_map.resolve("moment/foo", with_js_prefix).is_err()); @@ -1893,75 +1865,61 @@ mod tests { let top_level = "https://example.com/app.mjs"; // Should resolve scoped. - assert_eq!( - import_map.resolve("lodash-dot", js_in_dir).unwrap(), - Some( - "https://example.com/app/node_modules_2/lodash-es/lodash.js" - .to_string() - ) + assert_resolve( + import_map.resolve("lodash-dot", js_in_dir), + "https://example.com/app/node_modules_2/lodash-es/lodash.js", ); - assert_eq!( - import_map.resolve("lodash-dotdot", js_in_dir).unwrap(), - Some( - "https://example.com/node_modules_2/lodash-es/lodash.js".to_string() - ) + assert_resolve( + import_map.resolve("lodash-dotdot", js_in_dir), + "https://example.com/node_modules_2/lodash-es/lodash.js", ); - assert_eq!( - import_map.resolve("lodash-dot/foo", js_in_dir).unwrap(), - Some("https://example.com/app/node_modules_2/lodash-es/foo".to_string()) + assert_resolve( + import_map.resolve("lodash-dot/foo", js_in_dir), + "https://example.com/app/node_modules_2/lodash-es/foo", ); - assert_eq!( - import_map.resolve("lodash-dotdot/foo", js_in_dir).unwrap(), - Some("https://example.com/node_modules_2/lodash-es/foo".to_string()) + assert_resolve( + import_map.resolve("lodash-dotdot/foo", js_in_dir), + "https://example.com/node_modules_2/lodash-es/foo", ); // Should apply best scope match. - assert_eq!( - import_map.resolve("moment", top_level).unwrap(), - Some( - "https://example.com/node_modules_3/moment/src/moment.js".to_string() - ) + assert_resolve( + import_map.resolve("moment", top_level), + "https://example.com/node_modules_3/moment/src/moment.js", ); - assert_eq!( - import_map.resolve("moment", js_in_dir).unwrap(), - Some( - "https://example.com/node_modules_3/moment/src/moment.js".to_string() - ) + assert_resolve( + import_map.resolve("moment", js_in_dir), + "https://example.com/node_modules_3/moment/src/moment.js", ); - assert_eq!( - import_map.resolve("vue", js_in_dir).unwrap(), - Some( - "https://example.com/node_modules_3/vue/dist/vue.runtime.esm.js" - .to_string() - ) + assert_resolve( + import_map.resolve("vue", js_in_dir), + "https://example.com/node_modules_3/vue/dist/vue.runtime.esm.js", ); // Should fallback to "imports". - assert_eq!( - import_map.resolve("moment/foo", top_level).unwrap(), - Some("https://example.com/node_modules/moment/src/foo".to_string()) + assert_resolve( + import_map.resolve("moment/foo", top_level), + "https://example.com/node_modules/moment/src/foo", ); - assert_eq!( - import_map.resolve("moment/foo", js_in_dir).unwrap(), - Some("https://example.com/node_modules/moment/src/foo".to_string()) + assert_resolve( + import_map.resolve("moment/foo", js_in_dir), + "https://example.com/node_modules/moment/src/foo", ); - assert_eq!( - import_map.resolve("lodash-dot", top_level).unwrap(), - Some( - "https://example.com/app/node_modules/lodash-es/lodash.js".to_string() - ) + assert_resolve( + import_map.resolve("lodash-dot", top_level), + "https://example.com/app/node_modules/lodash-es/lodash.js", ); - assert_eq!( - import_map.resolve("lodash-dotdot", top_level).unwrap(), - Some("https://example.com/node_modules/lodash-es/lodash.js".to_string()) + assert_resolve( + import_map.resolve("lodash-dotdot", top_level), + "https://example.com/node_modules/lodash-es/lodash.js", ); - assert_eq!( - import_map.resolve("lodash-dot/foo", top_level).unwrap(), - Some("https://example.com/app/node_modules/lodash-es/foo".to_string()) + assert_resolve( + import_map.resolve("lodash-dot/foo", top_level), + "https://example.com/app/node_modules/lodash-es/foo", ); - assert_eq!( - import_map.resolve("lodash-dotdot/foo", top_level).unwrap(), - Some("https://example.com/node_modules/lodash-es/foo".to_string()) + assert_resolve( + import_map.resolve("lodash-dotdot/foo", top_level), + "https://example.com/node_modules/lodash-es/foo", ); // Should still fail for package-like specifiers that are not declared. @@ -1996,45 +1954,45 @@ mod tests { let scope_3_url = "https://example.com/scope2/scope3/foo.mjs"; // Should fall back to "imports" when none match. - assert_eq!( - import_map.resolve("a", scope_1_url).unwrap(), - Some("https://example.com/a-1.mjs".to_string()) + assert_resolve( + import_map.resolve("a", scope_1_url), + "https://example.com/a-1.mjs", ); - assert_eq!( - import_map.resolve("b", scope_1_url).unwrap(), - Some("https://example.com/b-1.mjs".to_string()) + assert_resolve( + import_map.resolve("b", scope_1_url), + "https://example.com/b-1.mjs", ); - assert_eq!( - import_map.resolve("c", scope_1_url).unwrap(), - Some("https://example.com/c-1.mjs".to_string()) + assert_resolve( + import_map.resolve("c", scope_1_url), + "https://example.com/c-1.mjs", ); // Should use a direct scope override. - assert_eq!( - import_map.resolve("a", scope_2_url).unwrap(), - Some("https://example.com/a-2.mjs".to_string()) + assert_resolve( + import_map.resolve("a", scope_2_url), + "https://example.com/a-2.mjs", ); - assert_eq!( - import_map.resolve("b", scope_2_url).unwrap(), - Some("https://example.com/b-1.mjs".to_string()) + assert_resolve( + import_map.resolve("b", scope_2_url), + "https://example.com/b-1.mjs", ); - assert_eq!( - import_map.resolve("c", scope_2_url).unwrap(), - Some("https://example.com/c-1.mjs".to_string()) + assert_resolve( + import_map.resolve("c", scope_2_url), + "https://example.com/c-1.mjs", ); // Should use an indirect scope override. - assert_eq!( - import_map.resolve("a", scope_3_url).unwrap(), - Some("https://example.com/a-2.mjs".to_string()) + assert_resolve( + import_map.resolve("a", scope_3_url), + "https://example.com/a-2.mjs", ); - assert_eq!( - import_map.resolve("b", scope_3_url).unwrap(), - Some("https://example.com/b-3.mjs".to_string()) + assert_resolve( + import_map.resolve("b", scope_3_url), + "https://example.com/b-3.mjs", ); - assert_eq!( - import_map.resolve("c", scope_3_url).unwrap(), - Some("https://example.com/c-1.mjs".to_string()) + assert_resolve( + import_map.resolve("c", scope_3_url), + "https://example.com/c-1.mjs", ); } @@ -2066,37 +2024,37 @@ mod tests { let in_dir_above_map = "https://example.com/foo.mjs"; // Should resolve an empty string scope using the import map URL. - assert_eq!( - import_map.resolve("a", base_url).unwrap(), - Some("https://example.com/a-empty-string.mjs".to_string()) + assert_resolve( + import_map.resolve("a", base_url), + "https://example.com/a-empty-string.mjs", ); - assert_eq!( - import_map.resolve("a", in_same_dir_as_map).unwrap(), - Some("https://example.com/a-1.mjs".to_string()) + assert_resolve( + import_map.resolve("a", in_same_dir_as_map), + "https://example.com/a-1.mjs", ); // Should resolve a ./ scope using the import map URL's directory. - assert_eq!( - import_map.resolve("b", base_url).unwrap(), - Some("https://example.com/b-dot-slash.mjs".to_string()) + assert_resolve( + import_map.resolve("b", base_url), + "https://example.com/b-dot-slash.mjs", ); - assert_eq!( - import_map.resolve("b", in_same_dir_as_map).unwrap(), - Some("https://example.com/b-dot-slash.mjs".to_string()) + assert_resolve( + import_map.resolve("b", in_same_dir_as_map), + "https://example.com/b-dot-slash.mjs", ); // Should resolve a ../ scope using the import map URL's directory. - assert_eq!( - import_map.resolve("c", base_url).unwrap(), - Some("https://example.com/c-dot-dot-slash.mjs".to_string()) + assert_resolve( + import_map.resolve("c", base_url), + "https://example.com/c-dot-dot-slash.mjs", ); - assert_eq!( - import_map.resolve("c", in_same_dir_as_map).unwrap(), - Some("https://example.com/c-dot-dot-slash.mjs".to_string()) + assert_resolve( + import_map.resolve("c", in_same_dir_as_map), + "https://example.com/c-dot-dot-slash.mjs", ); - assert_eq!( - import_map.resolve("c", in_dir_above_map).unwrap(), - Some("https://example.com/c-dot-dot-slash.mjs".to_string()) + assert_resolve( + import_map.resolve("c", in_dir_above_map), + "https://example.com/c-dot-dot-slash.mjs", ); } @@ -2121,13 +2079,13 @@ mod tests { }"#; let import_map = ImportMap::from_json(base_url, json_map).unwrap(); - assert_eq!( - import_map.resolve("std:blank", base_url).unwrap(), - Some("https://example.com/app/blank.mjs".to_string()) + assert_resolve( + import_map.resolve("std:blank", base_url), + "https://example.com/app/blank.mjs", ); - assert_eq!( - import_map.resolve("std:none", base_url).unwrap(), - Some("https://example.com/app/none.mjs".to_string()) + assert_resolve( + import_map.resolve("std:none", base_url), + "https://example.com/app/none.mjs", ); } } diff --git a/cli/main.rs b/cli/main.rs index 5976d42ba..11575ef63 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -27,6 +27,7 @@ mod http_body; mod http_util; mod import_map; pub mod js_errors; +mod module_specifier; pub mod msg; pub mod msg_util; pub mod ops; @@ -45,9 +46,9 @@ pub mod worker; use crate::compiler::bundle_async; use crate::errors::RustOrJsError; +use crate::module_specifier::ModuleSpecifier; use crate::progress::Progress; use crate::state::ThreadSafeState; -use crate::worker::root_specifier_to_url; use crate::worker::Worker; use deno::v8_set_flags; use flags::DenoFlags; @@ -98,51 +99,53 @@ where pub fn print_file_info( worker: Worker, - url: &str, + module_specifier: &ModuleSpecifier, ) -> impl Future<Item = Worker, Error = ()> { - state::fetch_module_meta_data_and_maybe_compile_async(&worker.state, url, ".") - .and_then(move |out| { - println!("{} {}", ansi::bold("local:".to_string()), &(out.filename)); - + state::fetch_module_meta_data_and_maybe_compile_async( + &worker.state, + module_specifier, + ).and_then(move |out| { + println!("{} {}", ansi::bold("local:".to_string()), &(out.filename)); + + println!( + "{} {}", + ansi::bold("type:".to_string()), + msg::enum_name_media_type(out.media_type) + ); + + if out.maybe_output_code_filename.is_some() { println!( "{} {}", - ansi::bold("type:".to_string()), - msg::enum_name_media_type(out.media_type) + ansi::bold("compiled:".to_string()), + out.maybe_output_code_filename.as_ref().unwrap(), ); + } - if out.maybe_output_code_filename.is_some() { - println!( - "{} {}", - ansi::bold("compiled:".to_string()), - out.maybe_output_code_filename.as_ref().unwrap(), - ); - } - - if out.maybe_source_map_filename.is_some() { - println!( - "{} {}", - ansi::bold("map:".to_string()), - out.maybe_source_map_filename.as_ref().unwrap() - ); - } + if out.maybe_source_map_filename.is_some() { + println!( + "{} {}", + ansi::bold("map:".to_string()), + out.maybe_source_map_filename.as_ref().unwrap() + ); + } - if let Some(deps) = - worker.state.modules.lock().unwrap().deps(&out.module_name) - { - println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name); - if let Some(ref depsdeps) = deps.deps { - for d in depsdeps { - println!("{}", d); - } + if let Some(deps) = + worker.state.modules.lock().unwrap().deps(&out.module_name) + { + println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name); + if let Some(ref depsdeps) = deps.deps { + for d in depsdeps { + println!("{}", d); } - } else { - println!( - "{} cannot retrieve full dependency graph", - ansi::bold("deps:".to_string()), - ); } - Ok(worker) - }).map_err(|err| println!("{}", err)) + } else { + println!( + "{} cannot retrieve full dependency graph", + ansi::bold("deps:".to_string()), + ); + } + Ok(worker) + }).map_err(|err| println!("{}", err)) } fn create_worker_and_state( @@ -193,10 +196,8 @@ fn fetch_or_info_command( js_check(worker.execute("denoMain()")); debug!("main_module {}", main_module); - let main_url = root_specifier_to_url(&main_module).unwrap(); - worker - .execute_mod_async(&main_url, true) + .execute_mod_async(&main_module, true) .map_err(print_err_and_exit) .and_then(move |()| { if print_info { @@ -269,11 +270,10 @@ fn bundle_command(flags: DenoFlags, argv: Vec<String>) { let (mut _worker, state) = create_worker_and_state(flags, argv); let main_module = state.main_module().unwrap(); - let main_url = root_specifier_to_url(&main_module).unwrap(); assert!(state.argv.len() >= 3); let out_file = state.argv[2].clone(); debug!(">>>>> bundle_async START"); - let bundle_future = bundle_async(state, main_url.to_string(), out_file) + let bundle_future = bundle_async(state, main_module.to_string(), out_file) .map_err(|e| { debug!("diagnostics returned, exiting!"); eprintln!("\n{}", e.to_string()); @@ -313,10 +313,8 @@ fn run_script(flags: DenoFlags, argv: Vec<String>) { js_check(worker.execute("denoMain()")); debug!("main_module {}", main_module); - let main_url = root_specifier_to_url(&main_module).unwrap(); - worker - .execute_mod_async(&main_url, false) + .execute_mod_async(&main_module, false) .and_then(move |()| { worker.then(|result| { js_check(result); diff --git a/cli/module_specifier.rs b/cli/module_specifier.rs new file mode 100644 index 000000000..475211289 --- /dev/null +++ b/cli/module_specifier.rs @@ -0,0 +1,81 @@ +use std::fmt; +use url::Url; + +#[derive(Debug, Clone, PartialEq)] +/// Resolved module specifier +pub struct ModuleSpecifier(Url); + +impl ModuleSpecifier { + pub fn to_url(&self) -> Url { + self.0.clone() + } + /// Resolves module using this algorithm: + /// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier + pub fn resolve( + specifier: &str, + base: &str, + ) -> Result<ModuleSpecifier, url::ParseError> { + // 1. Apply the URL parser to specifier. If the result is not failure, return + // the result. + // let specifier = parse_local_or_remote(specifier)?.to_string(); + if let Ok(url) = Url::parse(specifier) { + return Ok(ModuleSpecifier(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. + if !specifier.starts_with('/') + && !specifier.starts_with("./") + && !specifier.starts_with("../") + { + // TODO This is (probably) not the correct error to return here. + // TODO: This error is very not-user-friendly + return Err(url::ParseError::RelativeUrlWithCannotBeABaseBase); + } + + // 3. Return the result of applying the URL parser to specifier with base URL + // as the base URL. + let base_url = Url::parse(base)?; + let u = base_url.join(&specifier)?; + Ok(ModuleSpecifier(u)) + } + + /// Takes a string representing a path or URL to a module, but of the type + /// passed through the command-line interface for the main module. This is + /// slightly different than specifiers used in import statements: "foo.js" for + /// example is allowed here, whereas in import statements a leading "./" is + /// required ("./foo.js"). This function is aware of the current working + /// directory and returns an absolute URL. + pub fn resolve_root( + root_specifier: &str, + ) -> Result<ModuleSpecifier, url::ParseError> { + if let Ok(url) = Url::parse(root_specifier) { + Ok(ModuleSpecifier(url)) + } else { + let cwd = std::env::current_dir().unwrap(); + let base = Url::from_directory_path(cwd).unwrap(); + let url = base.join(root_specifier)?; + Ok(ModuleSpecifier(url)) + } + } +} + +impl From<Url> for ModuleSpecifier { + fn from(url: Url) -> Self { + ModuleSpecifier(url) + } +} + +impl fmt::Display for ModuleSpecifier { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq<String> for ModuleSpecifier { + fn eq(&self, other: &String) -> bool { + &self.to_string() == other + } +} diff --git a/cli/ops.rs b/cli/ops.rs index 61f29ac9f..e9fd48d0d 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -10,6 +10,7 @@ use crate::fs as deno_fs; use crate::http_util; use crate::js_errors::apply_source_map; use crate::js_errors::JSErrorColor; +use crate::module_specifier::ModuleSpecifier; use crate::msg; use crate::msg_util; use crate::rand; @@ -24,12 +25,10 @@ use crate::state::ThreadSafeState; use crate::tokio_util; use crate::tokio_write; use crate::version; -use crate::worker::root_specifier_to_url; use crate::worker::Worker; use deno::js_check; use deno::Buf; use deno::JSError; -//use deno::Loader; use deno::Op; use deno::PinnedBuf; use flatbuffers::FlatBufferBuilder; @@ -341,7 +340,9 @@ fn op_start( let deno_version = version::DENO; let deno_version_off = builder.create_string(deno_version); - let main_module = state.main_module().map(|m| builder.create_string(&m)); + let main_module = state + .main_module() + .map(|m| builder.create_string(&m.to_string())); let xeval_delim = state .flags @@ -507,7 +508,7 @@ fn op_fetch_module_meta_data( Some(import_map) => { match import_map.resolve(specifier, referrer) { Ok(result) => match result { - Some(url) => url.clone(), + Some(module_specifier) => module_specifier.to_string(), None => specifier.to_string(), }, Err(err) => panic!("error resolving using import map: {:?}", err), // TODO: this should be coerced to DenoError @@ -2082,11 +2083,11 @@ fn op_create_worker( js_check(worker.execute("denoMain()")); js_check(worker.execute("workerMain()")); - let op = root_specifier_to_url(specifier) - .and_then(|specifier_url| { + let op = ModuleSpecifier::resolve_root(specifier) + .and_then(|module_specifier| { Ok( worker - .execute_mod_async(&specifier_url, false) + .execute_mod_async(&module_specifier, false) .and_then(move |()| { let mut workers_tl = parent_state.workers.lock().unwrap(); workers_tl.insert(rid, worker.shared()); diff --git a/cli/state.rs b/cli/state.rs index bb284038c..df1bf1cc5 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -7,13 +7,13 @@ use crate::errors::DenoResult; use crate::flags; use crate::global_timer::GlobalTimer; use crate::import_map::ImportMap; +use crate::module_specifier::ModuleSpecifier; use crate::msg; use crate::ops; use crate::permissions::DenoPermissions; use crate::progress::Progress; use crate::resources; use crate::resources::ResourceId; -use crate::worker::resolve_module_spec; use crate::worker::Worker; use deno::Buf; use deno::Loader; @@ -60,7 +60,7 @@ pub struct ThreadSafeState(Arc<State>); #[cfg_attr(feature = "cargo-clippy", allow(stutter))] pub struct State { pub modules: Arc<Mutex<deno::Modules>>, - pub main_module: Option<String>, + pub main_module: Option<ModuleSpecifier>, pub dir: deno_dir::DenoDir, pub argv: Vec<String>, pub permissions: DenoPermissions, @@ -113,44 +113,40 @@ impl ThreadSafeState { pub fn fetch_module_meta_data_and_maybe_compile_async( state: &ThreadSafeState, - specifier: &str, - referrer: &str, + module_specifier: &ModuleSpecifier, ) -> impl Future<Item = ModuleMetaData, Error = DenoError> { let state_ = state.clone(); - let specifier = specifier.to_string(); - let referrer = referrer.to_string(); - let is_root = referrer == "."; - - let f = - futures::future::result(state.resolve(&specifier, &referrer, is_root)); - f.and_then(move |module_id| { - let use_cache = !state_.flags.reload || state_.has_compiled(&module_id); - let no_fetch = state_.flags.no_fetch; - - state_ - .dir - .fetch_module_meta_data_async(&specifier, &referrer, use_cache, no_fetch) - .and_then(move |out| { - if out.media_type == msg::MediaType::TypeScript - && !out.has_output_code_and_source_map() - { - debug!(">>>>> compile_sync START"); - Either::A( - compile_async(state_.clone(), &out) - .map_err(|e| { - debug!("compiler error exiting!"); - eprintln!("\n{}", e.to_string()); - std::process::exit(1); - }).and_then(move |out| { - debug!(">>>>> compile_sync END"); - Ok(out) - }), - ) - } else { - Either::B(futures::future::ok(out)) - } - }) - }) + let use_cache = + !state_.flags.reload || state_.has_compiled(&module_specifier.to_string()); + let no_fetch = state_.flags.no_fetch; + + state_ + .dir + .fetch_module_meta_data_async( + &module_specifier.to_string(), + ".", + use_cache, + no_fetch, + ).and_then(move |out| { + if out.media_type == msg::MediaType::TypeScript + && !out.has_output_code_and_source_map() + { + debug!(">>>>> compile_sync START"); + Either::A( + compile_async(state_.clone(), &out) + .map_err(|e| { + debug!("compiler error exiting!"); + eprintln!("\n{}", e.to_string()); + std::process::exit(1); + }).and_then(move |out| { + debug!(">>>>> compile_sync END"); + Ok(out) + }), + ) + } else { + Either::B(futures::future::ok(out)) + } + }) } impl Loader for ThreadSafeState { @@ -164,28 +160,25 @@ impl Loader for ThreadSafeState { ) -> Result<String, Self::Error> { if !is_root { if let Some(import_map) = &self.import_map { - match import_map.resolve(specifier, referrer) { - Ok(result) => { - if result.is_some() { - return Ok(result.unwrap()); - } - } - Err(err) => { - // TODO(bartlomieju): this should be coerced to DenoError - panic!("error resolving using import map: {:?}", err); - } + let result = import_map.resolve(specifier, referrer)?; + if result.is_some() { + return Ok(result.unwrap().to_string()); } } } - resolve_module_spec(specifier, referrer).map_err(DenoError::from) + let module_specifier = + ModuleSpecifier::resolve(specifier, referrer).map_err(DenoError::from)?; + Ok(module_specifier.to_string()) } /// Given an absolute url, load its source code. fn load(&self, url: &str) -> Box<deno::SourceCodeInfoFuture<Self::Error>> { self.metrics.resolve_count.fetch_add(1, Ordering::SeqCst); + let module_specifier = ModuleSpecifier::resolve_root(url) + .expect("should already been properly resolved"); Box::new( - fetch_module_meta_data_and_maybe_compile_async(self, url, ".") + fetch_module_meta_data_and_maybe_compile_async(self, &module_specifier) .map_err(|err| { eprintln!("{}", err); err @@ -256,18 +249,15 @@ impl ThreadSafeState { let dir = deno_dir::DenoDir::new(custom_root, &config, progress.clone()).unwrap(); - let main_module: Option<String> = if argv_rest.len() <= 1 { + let main_module: Option<ModuleSpecifier> = if argv_rest.len() <= 1 { None } else { - let specifier = argv_rest[1].clone(); - let referrer = "."; - // TODO: does this really have to be resolved by DenoDir? - // Maybe we can call `resolve_module_spec` - match dir.resolve_module_url(&specifier, referrer) { - Ok(url) => Some(url.to_string()), + let root_specifier = argv_rest[1].clone(); + match ModuleSpecifier::resolve_root(&root_specifier) { + Ok(specifier) => Some(specifier), Err(e) => { - debug!("Potentially swallowed error {}", e); - None + // TODO: handle unresolvable specifier + panic!("Unable to resolve root specifier: {:?}", e); } } }; @@ -275,11 +265,11 @@ impl ThreadSafeState { let mut import_map = None; if let Some(file_name) = &flags.import_map_path { let base_url = match &main_module { - Some(url) => url, + Some(module_specifier) => module_specifier.clone(), None => unreachable!(), }; - match ImportMap::load(base_url, file_name) { + match ImportMap::load(&base_url.to_string(), file_name) { Ok(map) => import_map = Some(map), Err(err) => { println!("{:?}", err); @@ -319,9 +309,9 @@ impl ThreadSafeState { } /// Read main module from argv - pub fn main_module(&self) -> Option<String> { + pub fn main_module(&self) -> Option<ModuleSpecifier> { match &self.main_module { - Some(url) => Some(url.to_string()), + Some(module_specifier) => Some(module_specifier.clone()), None => None, } } diff --git a/cli/worker.rs b/cli/worker.rs index 94d33e391..f11aa93e9 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -2,6 +2,7 @@ use crate::errors::DenoError; use crate::errors::RustOrJsError; use crate::js_errors; +use crate::module_specifier::ModuleSpecifier; use crate::state::ThreadSafeState; use crate::tokio_util; use deno; @@ -11,7 +12,6 @@ use futures::Async; use futures::Future; use std::sync::Arc; use std::sync::Mutex; -use url::Url; /// Wraps deno::Isolate to provide source maps, ops for the CLI, and /// high-level module loading @@ -57,7 +57,7 @@ impl Worker { /// Executes the provided JavaScript module. pub fn execute_mod_async( &mut self, - js_url: &Url, + module_specifier: &ModuleSpecifier, is_prefetch: bool, ) -> impl Future<Item = (), Error = RustOrJsError> { let worker = self.clone(); @@ -65,8 +65,12 @@ impl Worker { let loader = self.state.clone(); let isolate = self.isolate.clone(); let modules = self.state.modules.clone(); - let recursive_load = - deno::RecursiveLoad::new(js_url.as_str(), loader, isolate, modules); + let recursive_load = deno::RecursiveLoad::new( + &module_specifier.to_string(), + loader, + isolate, + modules, + ); recursive_load .and_then(move |id| -> Result<(), deno::JSErrorOr<DenoError>> { worker.state.progress.done(); @@ -96,10 +100,10 @@ impl Worker { /// Executes the provided JavaScript module. pub fn execute_mod( &mut self, - js_url: &Url, + module_specifier: &ModuleSpecifier, is_prefetch: bool, ) -> Result<(), RustOrJsError> { - tokio_util::block_on(self.execute_mod_async(js_url, is_prefetch)) + tokio_util::block_on(self.execute_mod_async(module_specifier, is_prefetch)) } /// Applies source map to the error. @@ -108,58 +112,6 @@ impl Worker { } } -// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier -// TODO(ry) Add tests. -// TODO(ry) Move this to core? -pub fn resolve_module_spec( - specifier: &str, - base: &str, -) -> Result<String, url::ParseError> { - // 1. Apply the URL parser to specifier. If the result is not failure, return - // the result. - // let specifier = parse_local_or_remote(specifier)?.to_string(); - if let Ok(specifier_url) = Url::parse(specifier) { - return Ok(specifier_url.to_string()); - } - - // 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. - if !specifier.starts_with('/') - && !specifier.starts_with("./") - && !specifier.starts_with("../") - { - // TODO(ry) This is (probably) not the correct error to return here. - return Err(url::ParseError::RelativeUrlWithCannotBeABaseBase); - } - - // 3. Return the result of applying the URL parser to specifier with base URL - // as the base URL. - let base_url = Url::parse(base)?; - let u = base_url.join(&specifier)?; - Ok(u.to_string()) -} - -/// Takes a string representing a path or URL to a module, but of the type -/// passed through the command-line interface for the main module. This is -/// slightly different than specifiers used in import statements: "foo.js" for -/// example is allowed here, whereas in import statements a leading "./" is -/// required ("./foo.js"). This function is aware of the current working -/// directory and returns an absolute URL. -pub fn root_specifier_to_url( - root_specifier: &str, -) -> Result<Url, url::ParseError> { - let maybe_url = Url::parse(root_specifier); - if let Ok(url) = maybe_url { - Ok(url) - } else { - let cwd = std::env::current_dir().unwrap(); - let base = Url::from_directory_path(cwd).unwrap(); - base.join(root_specifier) - } -} - impl Future for Worker { type Item = (); type Error = JSError; @@ -186,12 +138,9 @@ mod tests { #[test] fn execute_mod_esm_imports_a() { - let filename = std::env::current_dir() - .unwrap() - .join("tests/esm_imports_a.js"); - let js_url = Url::from_file_path(filename).unwrap(); - - let argv = vec![String::from("./deno"), js_url.to_string()]; + let module_specifier = + ModuleSpecifier::resolve_root("tests/esm_imports_a.js").unwrap(); + let argv = vec![String::from("./deno"), module_specifier.to_string()]; let state = ThreadSafeState::new( flags::DenoFlags::default(), argv, @@ -202,7 +151,7 @@ mod tests { tokio_util::run(lazy(move || { let mut worker = Worker::new("TEST".to_string(), StartupData::None, state); - let result = worker.execute_mod(&js_url, false); + let result = worker.execute_mod(&module_specifier, false); if let Err(err) = result { eprintln!("execute_mod err {:?}", err); } @@ -217,10 +166,9 @@ mod tests { #[test] fn execute_mod_circular() { - let filename = std::env::current_dir().unwrap().join("tests/circular1.js"); - let js_url = Url::from_file_path(filename).unwrap(); - - let argv = vec![String::from("./deno"), js_url.to_string()]; + let module_specifier = + ModuleSpecifier::resolve_root("tests/circular1.js").unwrap(); + let argv = vec![String::from("./deno"), module_specifier.to_string()]; let state = ThreadSafeState::new( flags::DenoFlags::default(), argv, @@ -231,7 +179,7 @@ mod tests { tokio_util::run(lazy(move || { let mut worker = Worker::new("TEST".to_string(), StartupData::None, state); - let result = worker.execute_mod(&js_url, false); + let result = worker.execute_mod(&module_specifier, false); if let Err(err) = result { eprintln!("execute_mod err {:?}", err); } @@ -246,11 +194,9 @@ mod tests { #[test] fn execute_006_url_imports() { - let filename = std::env::current_dir() - .unwrap() - .join("tests/006_url_imports.ts"); - let js_url = Url::from_file_path(filename).unwrap(); - let argv = vec![String::from("deno"), js_url.to_string()]; + let module_specifier = + ModuleSpecifier::resolve_root("tests/006_url_imports.ts").unwrap(); + let argv = vec![String::from("deno"), module_specifier.to_string()]; let mut flags = flags::DenoFlags::default(); flags.reload = true; let state = @@ -263,7 +209,7 @@ mod tests { state, ); js_check(worker.execute("denoMain()")); - let result = worker.execute_mod(&js_url, false); + let result = worker.execute_mod(&module_specifier, false); if let Err(err) = result { eprintln!("execute_mod err {:?}", err); } @@ -378,8 +324,9 @@ mod tests { tokio_util::init(|| { // "foo" is not a vailid module specifier so this should return an error. let mut worker = create_test_worker(); - let js_url = root_specifier_to_url("does-not-exist").unwrap(); - let result = worker.execute_mod_async(&js_url, false).wait(); + let module_specifier = + ModuleSpecifier::resolve_root("does-not-exist").unwrap(); + let result = worker.execute_mod_async(&module_specifier, false).wait(); assert!(result.is_err()); }) } @@ -390,8 +337,9 @@ mod tests { // This assumes cwd is project root (an assumption made throughout the // tests). let mut worker = create_test_worker(); - let js_url = root_specifier_to_url("./tests/002_hello.ts").unwrap(); - let result = worker.execute_mod_async(&js_url, false).wait(); + let module_specifier = + ModuleSpecifier::resolve_root("./tests/002_hello.ts").unwrap(); + let result = worker.execute_mod_async(&module_specifier, false).wait(); assert!(result.is_ok()); }) } |