diff options
Diffstat (limited to 'cli/config_file.rs')
-rw-r--r-- | cli/config_file.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/cli/config_file.rs b/cli/config_file.rs index 3ca00b0f4..53fe2f35e 100644 --- a/cli/config_file.rs +++ b/cli/config_file.rs @@ -227,6 +227,56 @@ pub fn json_merge(a: &mut Value, b: &Value) { } } +/// Based on an optional command line import map path and an optional +/// configuration file, return a resolved module specifier to an import map. +pub fn resolve_import_map_specifier( + maybe_import_map_path: Option<&str>, + maybe_config_file: Option<&ConfigFile>, +) -> Result<Option<ModuleSpecifier>, AnyError> { + if let Some(import_map_path) = maybe_import_map_path { + if let Some(config_file) = &maybe_config_file { + if config_file.to_import_map_path().is_some() { + log::warn!("{} the configuration file \"{}\" contains an entry for \"importMap\" that is being ignored.", crate::colors::yellow("Warning"), config_file.specifier); + } + } + let specifier = deno_core::resolve_url_or_path(import_map_path) + .context(format!("Bad URL (\"{}\") for import map.", import_map_path))?; + return Ok(Some(specifier)); + } else if let Some(config_file) = &maybe_config_file { + // when the import map is specifier in a config file, it needs to be + // resolved relative to the config file, versus the CWD like with the flag + // and with config files, we support both local and remote config files, + // so we have treat them differently. + if let Some(import_map_path) = config_file.to_import_map_path() { + let specifier = + // with local config files, it might be common to specify an import + // map like `"importMap": "import-map.json"`, which is resolvable if + // the file is resolved like a file path, so we will coerce the config + // file into a file path if possible and join the import map path to + // the file path. + if let Ok(config_file_path) = config_file.specifier.to_file_path() { + let import_map_file_path = config_file_path + .parent() + .ok_or_else(|| { + anyhow!("Bad config file specifier: {}", config_file.specifier) + })? + .join(&import_map_path); + ModuleSpecifier::from_file_path(import_map_file_path).unwrap() + // otherwise if the config file is remote, we have no choice but to + // use "import resolution" with the config file as the base. + } else { + deno_core::resolve_import(&import_map_path, config_file.specifier.as_str()) + .context(format!( + "Bad URL (\"{}\") for import map.", + import_map_path + ))? + }; + return Ok(Some(specifier)); + } + } + Ok(None) +} + fn parse_compiler_options( compiler_options: &HashMap<String, Value>, maybe_specifier: Option<ModuleSpecifier>, @@ -476,6 +526,7 @@ pub struct FmtConfig { #[serde(rename_all = "camelCase")] pub struct ConfigFileJson { pub compiler_options: Option<Value>, + pub import_map: Option<String>, pub lint: Option<Value>, pub fmt: Option<Value>, } @@ -583,6 +634,10 @@ impl ConfigFile { } } + pub fn to_import_map_path(&self) -> Option<String> { + self.json.import_map.clone() + } + pub fn to_lint_config(&self) -> Result<Option<LintConfig>, AnyError> { if let Some(config) = self.json.lint.clone() { let lint_config: SerializedLintConfig = serde_json::from_value(config) @@ -914,4 +969,80 @@ mod tests { let err = discover_from(&d, &mut checked).unwrap_err(); assert!(err.to_string().contains("Unable to parse config file")); } + + #[cfg(not(windows))] + #[test] + fn resolve_import_map_config_file() { + let config_text = r#"{ + "importMap": "import_map.json" + }"#; + let config_specifier = + ModuleSpecifier::parse("file:///deno/deno.jsonc").unwrap(); + let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); + let actual = resolve_import_map_specifier(None, Some(&config_file)); + assert!(actual.is_ok()); + let actual = actual.unwrap(); + assert_eq!( + actual, + Some(ModuleSpecifier::parse("file:///deno/import_map.json").unwrap()) + ); + } + + #[test] + fn resolve_import_map_config_file_remote() { + let config_text = r#"{ + "importMap": "./import_map.json" + }"#; + let config_specifier = + ModuleSpecifier::parse("https://example.com/deno.jsonc").unwrap(); + let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); + let actual = resolve_import_map_specifier(None, Some(&config_file)); + assert!(actual.is_ok()); + let actual = actual.unwrap(); + assert_eq!( + actual, + Some( + ModuleSpecifier::parse("https://example.com/import_map.json").unwrap() + ) + ); + } + + #[test] + fn resolve_import_map_flags_take_precedence() { + let config_text = r#"{ + "importMap": "import_map.json" + }"#; + let config_specifier = + ModuleSpecifier::parse("file:///deno/deno.jsonc").unwrap(); + let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); + let actual = + resolve_import_map_specifier(Some("import-map.json"), Some(&config_file)); + let import_map_path = + std::env::current_dir().unwrap().join("import-map.json"); + let expected_specifier = + ModuleSpecifier::from_file_path(&import_map_path).unwrap(); + assert!(actual.is_ok()); + let actual = actual.unwrap(); + assert_eq!(actual, Some(expected_specifier)); + } + + #[test] + fn resolve_import_map_none() { + let config_text = r#"{}"#; + let config_specifier = + ModuleSpecifier::parse("file:///deno/deno.jsonc").unwrap(); + let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); + let actual = resolve_import_map_specifier(None, Some(&config_file)); + assert!(actual.is_ok()); + let actual = actual.unwrap(); + assert_eq!(actual, None); + } + + #[test] + fn resolve_import_map_no_config() { + let actual = resolve_import_map_specifier(None, None); + assert!(actual.is_ok()); + let actual = actual.unwrap(); + assert_eq!(actual, None); + } } |