diff options
Diffstat (limited to 'core/module_specifier.rs')
-rw-r--r-- | core/module_specifier.rs | 500 |
1 files changed, 0 insertions, 500 deletions
diff --git a/core/module_specifier.rs b/core/module_specifier.rs deleted file mode 100644 index 20358e79c..000000000 --- a/core/module_specifier.rs +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use crate::normalize_path; -use std::error::Error; -use std::fmt; -use std::path::Path; -use std::path::PathBuf; -use url::ParseError; -use url::Url; - -/// Error indicating the reason resolving a module specifier failed. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum ModuleResolutionError { - InvalidUrl(ParseError), - InvalidBaseUrl(ParseError), - InvalidPath(PathBuf), - ImportPrefixMissing(String, Option<String>), -} -use ModuleResolutionError::*; - -impl Error for ModuleResolutionError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - InvalidUrl(ref err) | InvalidBaseUrl(ref err) => Some(err), - _ => None, - } - } -} - -impl fmt::Display for ModuleResolutionError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - InvalidUrl(ref err) => write!(f, "invalid URL: {err}"), - InvalidBaseUrl(ref err) => { - write!(f, "invalid base URL for relative import: {err}") - } - InvalidPath(ref path) => write!(f, "invalid module path: {path:?}"), - ImportPrefixMissing(ref specifier, ref maybe_referrer) => write!( - f, - "Relative import path \"{}\" not prefixed with / or ./ or ../{}", - specifier, - match maybe_referrer { - Some(referrer) => format!(" from \"{referrer}\""), - None => String::new(), - } - ), - } - } -} - -/// Resolved module specifier -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)); - } - - // 3. Return the result of applying the URL parser to specifier with base - // URL as the base URL. - Err(ParseError::RelativeUrlWithoutBase) => { - let base = 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(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_err(ModuleResolutionError::InvalidUrl) -} - -/// 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 passed -/// `current_dir`. -pub fn resolve_url_or_path( - specifier: &str, - current_dir: &Path, -) -> Result<ModuleSpecifier, ModuleResolutionError> { - if specifier_has_uri_scheme(specifier) { - resolve_url(specifier) - } else { - resolve_path(specifier, current_dir) - } -} - -/// Converts a string representing a relative or absolute path into a -/// ModuleSpecifier. A relative path is considered relative to the passed -/// `current_dir`. -pub fn resolve_path( - path_str: &str, - current_dir: &Path, -) -> Result<ModuleSpecifier, ModuleResolutionError> { - let path = current_dir.join(path_str); - let path = normalize_path(path); - Url::from_file_path(&path) - .map_err(|()| ModuleResolutionError::InvalidPath(path)) -} - -/// 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, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::serde_json::from_value; - use crate::serde_json::json; - use std::env::current_dir; - use std::path::Path; - - #[test] - fn test_resolve_import() { - let tests = vec![ - ( - "./005_more_imports.ts", - "http://deno.land/core/tests/006_url_imports.ts", - "http://deno.land/core/tests/005_more_imports.ts", - ), - ( - "../005_more_imports.ts", - "http://deno.land/core/tests/006_url_imports.ts", - "http://deno.land/core/005_more_imports.ts", - ), - ( - "http://deno.land/core/tests/005_more_imports.ts", - "http://deno.land/core/tests/006_url_imports.ts", - "http://deno.land/core/tests/005_more_imports.ts", - ), - ( - "data:text/javascript,export default 'grapes';", - "http://deno.land/core/tests/006_url_imports.ts", - "data:text/javascript,export default 'grapes';", - ), - ( - "blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f", - "http://deno.land/core/tests/006_url_imports.ts", - "blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f", - ), - ( - "javascript:export default 'artichokes';", - "http://deno.land/core/tests/006_url_imports.ts", - "javascript:export default 'artichokes';", - ), - ( - "data:text/plain,export default 'kale';", - "http://deno.land/core/tests/006_url_imports.ts", - "data:text/plain,export default 'kale';", - ), - ( - "/dev/core/tests/005_more_imports.ts", - "file:///home/yeti", - "file:///dev/core/tests/005_more_imports.ts", - ), - ( - "//zombo.com/1999.ts", - "https://cherry.dev/its/a/thing", - "https://zombo.com/1999.ts", - ), - ( - "http://deno.land/this/url/is/valid", - "base is clearly not a valid url", - "http://deno.land/this/url/is/valid", - ), - ( - "//server/some/dir/file", - "file:///home/yeti/deno", - "file://server/some/dir/file", - ), - // This test is disabled because the url crate does not follow the spec, - // dropping the server part from the final result. - // ( - // "/another/path/at/the/same/server", - // "file://server/some/dir/file", - // "file://server/another/path/at/the/same/server", - // ), - ]; - - for (specifier, base, expected_url) in tests { - let url = resolve_import(specifier, base).unwrap().to_string(); - assert_eq!(url, expected_url); - } - } - - #[test] - fn test_resolve_import_error() { - use url::ParseError::*; - use ModuleResolutionError::*; - - let tests = vec![ - ( - "awesome.ts", - "<unknown>", - ImportPrefixMissing( - "awesome.ts".to_string(), - Some("<unknown>".to_string()), - ), - ), - ( - "005_more_imports.ts", - "http://deno.land/core/tests/006_url_imports.ts", - ImportPrefixMissing( - "005_more_imports.ts".to_string(), - Some("http://deno.land/core/tests/006_url_imports.ts".to_string()), - ), - ), - ( - ".tomato", - "http://deno.land/core/tests/006_url_imports.ts", - ImportPrefixMissing( - ".tomato".to_string(), - Some("http://deno.land/core/tests/006_url_imports.ts".to_string()), - ), - ), - ( - "..zucchini.mjs", - "http://deno.land/core/tests/006_url_imports.ts", - ImportPrefixMissing( - "..zucchini.mjs".to_string(), - Some("http://deno.land/core/tests/006_url_imports.ts".to_string()), - ), - ), - ( - r".\yam.es", - "http://deno.land/core/tests/006_url_imports.ts", - ImportPrefixMissing( - r".\yam.es".to_string(), - Some("http://deno.land/core/tests/006_url_imports.ts".to_string()), - ), - ), - ( - r"..\yam.es", - "http://deno.land/core/tests/006_url_imports.ts", - ImportPrefixMissing( - r"..\yam.es".to_string(), - Some("http://deno.land/core/tests/006_url_imports.ts".to_string()), - ), - ), - ( - "https://eggplant:b/c", - "http://deno.land/core/tests/006_url_imports.ts", - InvalidUrl(InvalidPort), - ), - ( - "https://eggplant@/c", - "http://deno.land/core/tests/006_url_imports.ts", - InvalidUrl(EmptyHost), - ), - ( - "./foo.ts", - "/relative/base/url", - InvalidBaseUrl(RelativeUrlWithoutBase), - ), - ]; - - for (specifier, base, expected_err) in tests { - let err = resolve_import(specifier, base).unwrap_err(); - assert_eq!(err, expected_err); - } - } - - #[test] - fn test_resolve_url_or_path() { - // Absolute URL. - let mut tests: Vec<(&str, String)> = vec![ - ( - "http://deno.land/core/tests/006_url_imports.ts", - "http://deno.land/core/tests/006_url_imports.ts".to_string(), - ), - ( - "https://deno.land/core/tests/006_url_imports.ts", - "https://deno.land/core/tests/006_url_imports.ts".to_string(), - ), - ]; - - // The local path tests assume that the cwd is the deno repo root. - let cwd = current_dir().unwrap(); - let cwd_str = cwd.to_str().unwrap(); - - if cfg!(target_os = "windows") { - // Absolute local path. - let expected_url = "file:///C:/deno/tests/006_url_imports.ts"; - tests.extend(vec![ - ( - r"C:/deno/tests/006_url_imports.ts", - expected_url.to_string(), - ), - ( - r"C:\deno\tests\006_url_imports.ts", - expected_url.to_string(), - ), - ( - r"\\?\C:\deno\tests\006_url_imports.ts", - expected_url.to_string(), - ), - // Not supported: `Url::from_file_path()` fails. - // (r"\\.\C:\deno\tests\006_url_imports.ts", expected_url.to_string()), - // Not supported: `Url::from_file_path()` performs the wrong conversion. - // (r"//./C:/deno/tests/006_url_imports.ts", expected_url.to_string()), - ]); - - // Rooted local path without drive letter. - let expected_url = format!( - "file:///{}:/deno/tests/006_url_imports.ts", - cwd_str.get(..1).unwrap(), - ); - tests.extend(vec![ - (r"/deno/tests/006_url_imports.ts", expected_url.to_string()), - (r"\deno\tests\006_url_imports.ts", expected_url.to_string()), - ( - r"\deno\..\deno\tests\006_url_imports.ts", - expected_url.to_string(), - ), - (r"\deno\.\tests\006_url_imports.ts", expected_url), - ]); - - // Relative local path. - let expected_url = format!( - "file:///{}/tests/006_url_imports.ts", - cwd_str.replace('\\', "/") - ); - tests.extend(vec![ - (r"tests/006_url_imports.ts", expected_url.to_string()), - (r"tests\006_url_imports.ts", expected_url.to_string()), - (r"./tests/006_url_imports.ts", (*expected_url).to_string()), - (r".\tests\006_url_imports.ts", (*expected_url).to_string()), - ]); - - // UNC network path. - let expected_url = "file://server/share/deno/cool"; - tests.extend(vec![ - (r"\\server\share\deno\cool", expected_url.to_string()), - (r"\\server/share/deno/cool", expected_url.to_string()), - // Not supported: `Url::from_file_path()` performs the wrong conversion. - // (r"//server/share/deno/cool", expected_url.to_string()), - ]); - } else { - // Absolute local path. - let expected_url = "file:///deno/tests/006_url_imports.ts"; - tests.extend(vec![ - ("/deno/tests/006_url_imports.ts", expected_url.to_string()), - ("//deno/tests/006_url_imports.ts", expected_url.to_string()), - ]); - - // Relative local path. - let expected_url = format!("file://{cwd_str}/tests/006_url_imports.ts"); - tests.extend(vec![ - ("tests/006_url_imports.ts", expected_url.to_string()), - ("./tests/006_url_imports.ts", expected_url.to_string()), - ( - "tests/../tests/006_url_imports.ts", - expected_url.to_string(), - ), - ("tests/./006_url_imports.ts", expected_url), - ]); - } - - for (specifier, expected_url) in tests { - let url = resolve_url_or_path(specifier, &cwd).unwrap().to_string(); - assert_eq!(url, expected_url); - } - } - - #[test] - fn test_resolve_url_or_path_deprecated_error() { - use url::ParseError::*; - use ModuleResolutionError::*; - - let mut tests = vec![ - ("https://eggplant:b/c", InvalidUrl(InvalidPort)), - ("https://:8080/a/b/c", InvalidUrl(EmptyHost)), - ]; - if cfg!(target_os = "windows") { - let p = r"\\.\c:/stuff/deno/script.ts"; - tests.push((p, InvalidPath(PathBuf::from(p)))); - } - - for (specifier, expected_err) in tests { - let err = - resolve_url_or_path(specifier, &PathBuf::from("/")).unwrap_err(); - assert_eq!(err, expected_err); - } - } - - #[test] - fn test_specifier_has_uri_scheme() { - let tests = vec![ - ("http://foo.bar/etc", true), - ("HTTP://foo.bar/etc", true), - ("http:ftp:", true), - ("http:", true), - ("hTtP:", true), - ("ftp:", true), - ("mailto:spam@please.me", true), - ("git+ssh://git@github.com/denoland/deno", true), - ("blob:https://whatwg.org/mumbojumbo", true), - ("abc.123+DEF-ghi:", true), - ("abc.123+def-ghi:@", true), - ("", false), - (":not", false), - ("http", false), - ("c:dir", false), - ("X:", false), - ("./http://not", false), - ("1abc://kinda/but/no", false), - ("schluẞ://no/more", false), - ]; - - for (specifier, expected) in tests { - let result = specifier_has_uri_scheme(specifier); - assert_eq!(result, expected); - } - } - - #[test] - fn test_normalize_path() { - assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b")); - assert_eq!(normalize_path(Path::new("a/./b/")), PathBuf::from("a/b/")); - assert_eq!( - normalize_path(Path::new("a/./b/../c")), - PathBuf::from("a/c") - ); - - if cfg!(windows) { - assert_eq!( - normalize_path(Path::new("C:\\a\\.\\b\\..\\c")), - PathBuf::from("C:\\a\\c") - ); - } - } - - #[test] - fn test_deserialize_module_specifier() { - let actual: ModuleSpecifier = - from_value(json!("http://deno.land/x/mod.ts")).unwrap(); - let expected = resolve_url("http://deno.land/x/mod.ts").unwrap(); - assert_eq!(actual, expected); - } -} |