-
Notifications
You must be signed in to change notification settings - Fork 14
Import map merging #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,15 +3,18 @@ | |
| use indexmap::IndexMap; | ||
| use serde_json::Map; | ||
| use serde_json::Value; | ||
| use std::cmp::Ordering; | ||
| use std::collections::HashSet; | ||
| use std::fmt; | ||
| use std::fmt::Debug; | ||
| use thiserror::Error; | ||
| use url::Url; | ||
|
|
||
| use self::merge::code_unit_compare; | ||
| use self::merge::MergeDiagnostic; | ||
|
|
||
| #[cfg(feature = "ext")] | ||
| pub mod ext; | ||
| pub mod merge; | ||
| pub mod specifier; | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
|
|
@@ -22,6 +25,7 @@ pub enum ImportMapDiagnostic { | |
| InvalidAddress(String, String), | ||
| InvalidAddressNotString(String, String), | ||
| InvalidTopLevelKey(String), | ||
| Merge(Box<MergeDiagnostic>), | ||
| } | ||
|
|
||
| impl fmt::Display for ImportMapDiagnostic { | ||
|
|
@@ -58,6 +62,9 @@ impl fmt::Display for ImportMapDiagnostic { | |
| ImportMapDiagnostic::InvalidTopLevelKey(key) => { | ||
| write!(f, "Invalid top-level key \"{}\". Only \"imports\" and \"scopes\" can be present.", key) | ||
| } | ||
| ImportMapDiagnostic::Merge(merge) => { | ||
| write!(f, "Due to merge: {merge}") | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -130,6 +137,7 @@ struct SpecifierMapValue { | |
| raw_key: Option<String>, | ||
| /// The raw value if it differs from the actual value. | ||
| raw_value: Option<String>, | ||
| /// None in case of block by null entry | ||
| maybe_address: Option<Url>, | ||
| } | ||
|
|
||
|
|
@@ -199,6 +207,7 @@ type SpecifierMapInner = IndexMap<String, SpecifierMapValue>; | |
|
|
||
| #[derive(Debug, Clone)] | ||
| pub struct SpecifierMap { | ||
| #[deprecated = "import map base_url doesn't work with merged import maps"] | ||
| base_url: Url, | ||
| inner: SpecifierMapInner, | ||
| } | ||
|
|
@@ -221,6 +230,8 @@ impl SpecifierMap { | |
| }) | ||
| } | ||
|
|
||
| #[deprecated = "specifier map base_url doesn't work with merged import maps"] | ||
| #[allow(deprecated)] | ||
| pub fn contains(&self, key: &str) -> bool { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yet I have no idea what's the good place for this nethod, but it doesn't seem to be used anywhere |
||
| if let Ok(key) = normalize_specifier_key(key, &self.base_url) { | ||
| self.inner.contains_key(&key) | ||
|
|
@@ -229,6 +240,8 @@ impl SpecifierMap { | |
| } | ||
| } | ||
|
|
||
| #[deprecated = "specifier map base_url doesn't work with merged import maps"] | ||
| #[allow(deprecated)] | ||
| pub fn append(&mut self, key: String, value: String) -> Result<(), String> { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...In that case, methods like this be better implemented on SpecifierMapBuilder struct |
||
| let start_index = self | ||
| .inner | ||
|
|
@@ -280,12 +293,9 @@ impl SpecifierMap { | |
|
|
||
| fn sort(&mut self) { | ||
| // Sort in longest and alphabetical order. | ||
| self.inner.sort_by(|k1, _v1, k2, _v2| match k1.cmp(k2) { | ||
| Ordering::Greater => Ordering::Less, | ||
| Ordering::Less => Ordering::Greater, | ||
| // index map guarantees that there can't be duplicate keys | ||
| Ordering::Equal => unreachable!(), | ||
| }); | ||
| self | ||
| .inner | ||
| .sort_by(|k1, _v1, k2, _v2| code_unit_compare(k1, k2).reverse()); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -318,11 +328,27 @@ pub struct ImportMapWithDiagnostics { | |
| pub diagnostics: Vec<ImportMapDiagnostic>, | ||
| } | ||
|
|
||
| impl ImportMapWithDiagnostics { | ||
| pub fn merge(&mut self, new: ImportMapWithDiagnostics) { | ||
| let mut merge_diagnostics = vec![]; | ||
| merge::merge(&mut self.import_map, new.import_map, &mut merge_diagnostics); | ||
|
|
||
| self.diagnostics.extend( | ||
| merge_diagnostics | ||
| .into_iter() | ||
| .map(Box::new) | ||
| .map(ImportMapDiagnostic::Merge), | ||
| ); | ||
| // Should diagnostics ignored due to merge be dropped? | ||
| self.diagnostics.extend(new.diagnostics); | ||
| } | ||
| } | ||
|
|
||
| #[derive(Default)] | ||
| pub struct ImportMapOptions { | ||
| /// `(parsed_address, key, maybe_scope) -> new_address` | ||
| #[allow(clippy::type_complexity)] | ||
| pub address_hook: Option<Box<dyn (Fn(&str, &str, Option<&str>) -> String)>>, | ||
| pub address_hook: Option<Box<dyn Fn(&str, &str, Option<&str>) -> String>>, | ||
| /// Whether to expand imports in the import map. | ||
| /// | ||
| /// This functionality can be used to modify the import map | ||
|
|
@@ -346,8 +372,23 @@ impl Debug for ImportMapOptions { | |
| } | ||
| } | ||
|
|
||
| #[derive(Default)] | ||
| #[non_exhaustive] | ||
| pub struct ResolveOptions { | ||
| only_remapped: bool, | ||
| } | ||
| impl ResolveOptions { | ||
| /// If set - `ImportMap::resolve_with` should not return any result | ||
| /// if the specifier was not remapped. | ||
| pub fn only_remapped(mut self) -> Self { | ||
| self.only_remapped = true; | ||
| self | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, serde::Serialize)] | ||
| pub struct ImportMap { | ||
| #[deprecated = "specifier map base_url doesn't work with merged import maps"] | ||
| #[serde(skip)] | ||
| base_url: Url, | ||
|
|
||
|
|
@@ -358,15 +399,19 @@ pub struct ImportMap { | |
| impl ImportMap { | ||
| pub fn new(base_url: Url) -> Self { | ||
| Self { | ||
| #[allow(deprecated)] | ||
| base_url: base_url.clone(), | ||
| imports: SpecifierMap { | ||
| #[allow(deprecated)] | ||
| base_url, | ||
| inner: Default::default(), | ||
| }, | ||
| scopes: Default::default(), | ||
| } | ||
| } | ||
|
|
||
| #[deprecated = "import map base_url doesn't work with merged import maps"] | ||
| #[allow(deprecated)] | ||
| pub fn base_url(&self) -> &Url { | ||
| &self.base_url | ||
| } | ||
|
|
@@ -421,6 +466,15 @@ impl ImportMap { | |
| &self, | ||
| specifier: &str, | ||
| referrer: &Url, | ||
| ) -> Result<Url, ImportMapError> { | ||
| self.resolve_with(specifier, referrer, &ResolveOptions::default()) | ||
| } | ||
|
|
||
| pub fn resolve_with( | ||
| &self, | ||
| specifier: &str, | ||
| referrer: &Url, | ||
| opts: &ResolveOptions, | ||
| ) -> Result<Url, ImportMapError> { | ||
| let as_url: Option<Url> = try_url_like_specifier(specifier, referrer); | ||
| let normalized_specifier = if let Some(url) = as_url.as_ref() { | ||
|
|
@@ -453,8 +507,10 @@ impl ImportMap { | |
| } | ||
|
|
||
| // The specifier was able to be turned into a URL, but wasn't remapped into anything. | ||
| if let Some(as_url) = as_url { | ||
| return Ok(as_url); | ||
| if !opts.only_remapped { | ||
| if let Some(as_url) = as_url { | ||
| return Ok(as_url); | ||
| } | ||
| } | ||
|
|
||
| Err( | ||
|
|
@@ -482,6 +538,8 @@ impl ImportMap { | |
| }) | ||
| } | ||
|
|
||
| #[deprecated = "import map base_url doesn't work with merged import maps"] | ||
| #[allow(deprecated)] | ||
| pub fn get_or_append_scope_mut( | ||
| &mut self, | ||
| key: &str, | ||
|
|
@@ -514,6 +572,7 @@ impl ImportMap { | |
| Some(key.to_string()) | ||
| }, | ||
| imports: SpecifierMap { | ||
| #[allow(deprecated)] | ||
| base_url, | ||
| inner: Default::default(), | ||
| }, | ||
|
|
@@ -620,6 +679,7 @@ pub fn parse_from_json_with_options( | |
| Ok(ImportMapWithDiagnostics { | ||
| diagnostics, | ||
| import_map: ImportMap { | ||
| #[allow(deprecated)] | ||
| base_url, | ||
| imports, | ||
| scopes, | ||
|
|
@@ -649,6 +709,7 @@ pub fn parse_from_value_with_options( | |
| Ok(ImportMapWithDiagnostics { | ||
| diagnostics, | ||
| import_map: ImportMap { | ||
| #[allow(deprecated)] | ||
| base_url, | ||
| imports, | ||
| scopes, | ||
|
|
@@ -866,15 +927,12 @@ fn parse_specifier_map( | |
| } | ||
|
|
||
| // Sort in longest and alphabetical order. | ||
| normalized_map.sort_by(|k1, _v1, k2, _v2| match k1.cmp(k2) { | ||
| Ordering::Greater => Ordering::Less, | ||
| Ordering::Less => Ordering::Greater, | ||
| // JSON guarantees that there can't be duplicate keys | ||
| Ordering::Equal => unreachable!(), | ||
| }); | ||
| normalized_map | ||
| .sort_by(|k1, _v1, k2, _v2| code_unit_compare(k1, k2).reverse()); | ||
|
|
||
| SpecifierMap { | ||
| inner: normalized_map, | ||
| #[allow(deprecated)] | ||
| base_url: base_url.clone(), | ||
| } | ||
| } | ||
|
|
@@ -923,12 +981,8 @@ fn parse_scope_map( | |
| } | ||
|
|
||
| // Sort in longest and alphabetical order. | ||
| normalized_map.sort_by(|k1, _v1, k2, _v2| match k1.cmp(k2) { | ||
| Ordering::Greater => Ordering::Less, | ||
| Ordering::Less => Ordering::Greater, | ||
| // JSON guarantees that there can't be duplicate keys | ||
| Ordering::Equal => unreachable!(), | ||
| }); | ||
| normalized_map | ||
| .sort_by(|k1, _v1, k2, _v2| code_unit_compare(k1, k2).reverse()); | ||
|
|
||
| Ok(normalized_map) | ||
| } | ||
|
|
@@ -1154,6 +1208,7 @@ mod test { | |
| }, | ||
| ); | ||
| let specifiers = SpecifierMap { | ||
| #[allow(deprecated)] | ||
| base_url: Url::parse("file:///").unwrap(), | ||
| inner: specifiers, | ||
| }; | ||
|
|
@@ -1173,6 +1228,7 @@ mod test { | |
| }, | ||
| ); | ||
| let specifiers = SpecifierMap { | ||
| #[allow(deprecated)] | ||
| base_url: Url::parse("file:///").unwrap(), | ||
| inner: specifiers, | ||
| }; | ||
|
|
@@ -1199,6 +1255,7 @@ mod test { | |
| }, | ||
| ); | ||
| let specifiers = SpecifierMap { | ||
| #[allow(deprecated)] | ||
| base_url: Url::parse("file:///").unwrap(), | ||
| inner: specifiers, | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that base_url be better stored by the side of ImportMaps that are not merged on the application side.