1use indexmap::IndexMap;
4use serde_json::Map;
5use serde_json::Value;
6use std::cmp::Ordering;
7use std::collections::HashSet;
8use std::fmt;
9use std::fmt::Debug;
10use thiserror::Error;
11use url::Url;
12
13#[cfg(feature = "ext")]
14pub mod ext;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum ImportMapDiagnostic {
18 EmptySpecifier,
19 InvalidScope(String, String),
20 InvalidTargetAddress(String, String),
21 InvalidAddress(String, String),
22 InvalidAddressNotString(String, String),
23 InvalidTopLevelKey(String),
24}
25
26impl fmt::Display for ImportMapDiagnostic {
27 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 match self {
29 ImportMapDiagnostic::EmptySpecifier => {
30 write!(f, "Invalid empty string specifier.")
31 }
32 ImportMapDiagnostic::InvalidScope(scope, base_url) => {
33 write!(
34 f,
35 "Invalid scope \"{}\" (parsed against base URL \"{}\").",
36 scope, base_url
37 )
38 }
39 ImportMapDiagnostic::InvalidTargetAddress(address, key) => {
40 write!(
41 f,
42 "Invalid target address {:?} for package specifier {:?}. \
43 Package address targets must end with \"/\".",
44 address, key
45 )
46 }
47 ImportMapDiagnostic::InvalidAddress(value, key) => {
48 write!(
49 f,
50 "Invalid address {:#?} for the specifier key \"{}\".",
51 value, key
52 )
53 }
54 ImportMapDiagnostic::InvalidAddressNotString(value, key) => {
55 write!(f, "Invalid address {:#?} for the specifier key \"{}\". Addresses must be strings.", value, key)
56 }
57 ImportMapDiagnostic::InvalidTopLevelKey(key) => {
58 write!(f, "Invalid top-level key \"{}\". Only \"imports\" and \"scopes\" can be present.", key)
59 }
60 }
61 }
62}
63
64#[derive(Debug, boxed_error::Boxed, deno_error::JsError)]
65pub struct ImportMapError(pub Box<ImportMapErrorKind>);
66
67#[derive(Error, Debug, deno_error::JsError)]
68#[class(type)]
69pub enum ImportMapErrorKind {
70 #[error(
71 "Import \"{}\" not a dependency and not in import map{}",
72 .0,
73 .1.as_ref().map(|referrer| format!(" from \"{}\"", referrer)).unwrap_or_default(),
74 )]
75 UnmappedBareSpecifier(String, Option<String>),
76 #[class(inherit)]
77 #[error("Unable to parse import map JSON: {0}")]
78 JsonParse(serde_json::Error),
79 #[error("Import map JSON must be an object")]
80 ImportMapNotObject,
81 #[error("Import map's 'imports' must be an object")]
82 ImportsFieldNotObject,
83 #[error("Import map's 'scopes' must be an object")]
84 ScopesFieldNotObject,
85 #[error("The value for the {0} scope prefix must be an object")]
86 ScopePrefixNotObject(String),
87 #[error("Blocked by null entry for \"{0:?}\"")]
88 BlockedByNullEntry(String),
89 #[error("Failed to resolve the specifier \"{specifier:?}\" as its after-prefix portion \"{after_prefix:?}\" could not be URL-parsed relative to the URL prefix \"{resolution_result}\" mapped to by the prefix \"{specifier_key}\"")]
90 SpecifierResolutionFailure {
91 specifier: String,
92 after_prefix: String,
93 resolution_result: Url,
94 specifier_key: String,
95 },
96 #[error("The specifier \"{specifier:?}\" backtracks above its prefix \"{specifier_key:?}\"")]
97 SpecifierBacktracksAbovePrefix {
98 specifier: String,
99 specifier_key: String,
100 },
101}
102
103const SPECIAL_PROTOCOLS: &[&str] =
105 &["ftp", "file", "http", "https", "ws", "wss"];
106fn is_special(url: &Url) -> bool {
107 SPECIAL_PROTOCOLS.contains(&url.scheme())
108}
109
110#[derive(Debug)]
112pub struct SpecifierMapEntry<'a> {
113 pub key: &'a str,
115 pub raw_key: &'a str,
117 pub value: Option<&'a Url>,
119 pub raw_value: Option<&'a str>,
121}
122
123#[derive(Debug, Clone)]
124struct SpecifierMapValue {
125 index: usize,
128 raw_key: Option<String>,
130 raw_value: Option<String>,
132 maybe_address: Option<Url>,
133}
134
135struct RawKeyValue {
136 key: String,
137 value: Option<String>,
138}
139
140impl SpecifierMapValue {
141 pub fn new(
142 index: usize,
143 raw: &RawKeyValue,
144 normalized_key: &str,
145 value: Option<Url>,
146 ) -> Self {
147 Self {
148 index,
149 raw_key: if raw.key == normalized_key {
151 None
152 } else {
153 Some(raw.key.to_string())
154 },
155 raw_value: if value.as_ref().map(|v| v.as_str()) == raw.value.as_deref() {
156 None
157 } else {
158 raw.value.as_ref().map(|v| v.to_string())
159 },
160 maybe_address: value,
161 }
162 }
163}
164
165impl serde::Serialize for SpecifierMapValue {
166 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
167 where
168 S: serde::Serializer,
169 {
170 if let Some(value) = &self.maybe_address {
171 value.serialize(serializer)
172 } else {
173 serializer.serialize_none()
174 }
175 }
176}
177
178#[derive(Debug, Clone)]
179struct ScopesMapValue {
180 index: usize,
183 raw_key: Option<String>,
185 imports: SpecifierMap,
186}
187
188impl serde::Serialize for ScopesMapValue {
189 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
190 where
191 S: serde::Serializer,
192 {
193 self.imports.serialize(serializer)
194 }
195}
196
197type SpecifierMapInner = IndexMap<String, SpecifierMapValue>;
198
199#[derive(Debug, Clone)]
200pub struct SpecifierMap {
201 base_url: Url,
202 inner: SpecifierMapInner,
203}
204
205impl SpecifierMap {
206 pub fn keys(&self) -> impl Iterator<Item = &str> {
207 self.inner.keys().map(|k| k.as_str())
208 }
209
210 pub fn entries(&self) -> impl Iterator<Item = SpecifierMapEntry<'_>> {
212 self.inner.iter().map(|(k, v)| SpecifierMapEntry {
213 key: k.as_str(),
214 raw_key: v.raw_key.as_deref().unwrap_or(k.as_str()),
215 value: v.maybe_address.as_ref(),
216 raw_value: v
217 .raw_value
218 .as_deref()
219 .or_else(|| v.maybe_address.as_ref().map(|a| a.as_str())),
220 })
221 }
222
223 pub fn contains(&self, key: &str) -> bool {
224 if let Ok(key) = normalize_specifier_key(key, &self.base_url) {
225 self.inner.contains_key(&key)
226 } else {
227 false
228 }
229 }
230
231 pub fn append(&mut self, key: String, value: String) -> Result<(), String> {
232 let start_index = self
233 .inner
234 .values()
235 .map(|v| v.index)
236 .max()
237 .map(|index| index + 1)
238 .unwrap_or(0);
239
240 let raw = RawKeyValue {
241 key,
242 value: Some(value),
243 };
244 let key = normalize_specifier_key(&raw.key, &self.base_url)
245 .map_err(|e| e.to_string())?;
246 if let Some(import_value) = self.inner.get(&key) {
247 let import_val = if let Some(value) = &import_value.maybe_address {
248 value.as_str()
249 } else {
250 "<invalid value>"
251 };
252 return Err(format!(
253 "\"{}\" already exists and is mapped to \"{}\"",
254 key, import_val
255 ));
256 }
257
258 let address_url =
259 match try_url_like_specifier(raw.value.as_ref().unwrap(), &self.base_url)
260 {
261 Some(url) => url,
262 None => {
263 return Err(format!(
264 "Invalid address \"{}\" for the specifier key {:?}.",
265 raw.value.unwrap(),
266 key
267 ));
268 }
269 };
270
271 self.inner.insert(
272 key.to_string(),
273 SpecifierMapValue::new(start_index, &raw, &key, Some(address_url)),
274 );
275 self.sort();
276
277 Ok(())
278 }
279
280 fn sort(&mut self) {
281 self.inner.sort_by(|k1, _v1, k2, _v2| match k1.cmp(k2) {
283 Ordering::Greater => Ordering::Less,
284 Ordering::Less => Ordering::Greater,
285 Ordering::Equal => unreachable!(),
287 });
288 }
289}
290
291impl serde::Serialize for SpecifierMap {
292 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
293 where
294 S: serde::Serializer,
295 {
296 self.inner.serialize(serializer)
297 }
298}
299
300type ScopesMap = IndexMap<String, ScopesMapValue>;
301type UnresolvedSpecifierMap = IndexMap<String, Option<String>>;
302type UnresolvedScopesMap = IndexMap<String, UnresolvedSpecifierMap>;
303
304pub struct ScopeEntry<'a> {
306 pub key: &'a str,
308 pub raw_key: &'a str,
310 pub imports: &'a SpecifierMap,
312}
313
314#[derive(Debug, Clone)]
315pub struct ImportMapWithDiagnostics {
316 pub import_map: ImportMap,
317 pub diagnostics: Vec<ImportMapDiagnostic>,
318}
319
320#[derive(Default)]
321pub struct ImportMapOptions {
322 #[allow(clippy::type_complexity)]
324 pub address_hook: Option<Box<dyn Fn(&str, &str, Option<&str>) -> String>>,
325 pub expand_imports: bool,
338}
339
340impl Debug for ImportMapOptions {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342 f.debug_struct("ImportMapOptions")
343 .field("address_hook", &self.address_hook.as_ref().map(|_| ()))
344 .finish()
345 }
346}
347
348#[derive(Debug, Clone, serde::Serialize)]
349pub struct ImportMap {
350 #[serde(skip)]
351 base_url: Url,
352
353 imports: SpecifierMap,
354 scopes: ScopesMap,
355}
356
357impl ImportMap {
358 pub fn new(base_url: Url) -> Self {
359 Self {
360 base_url: base_url.clone(),
361 imports: SpecifierMap {
362 base_url,
363 inner: Default::default(),
364 },
365 scopes: Default::default(),
366 }
367 }
368
369 pub fn base_url(&self) -> &Url {
370 &self.base_url
371 }
372
373 pub fn lookup(&self, specifier: &Url, referrer: &Url) -> Option<String> {
377 let specifier_str = specifier.as_str();
378 for entry in self.entries_for_referrer(referrer) {
379 if let Some(address) = entry.value {
380 let address_str = address.as_str();
381 if address_str == specifier_str {
382 return Some(entry.raw_key.to_string());
383 }
384 if address_str.ends_with('/') && specifier_str.starts_with(address_str)
385 {
386 return Some(specifier_str.replace(address_str, entry.raw_key));
387 }
388 }
389 }
390 None
391 }
392
393 pub fn entries_for_referrer(
396 &self,
397 referrer: &Url,
398 ) -> impl Iterator<Item = SpecifierMapEntry<'_>> {
399 let referrer = referrer.as_str();
400 let mut imports = Vec::with_capacity(1 + self.scopes.len());
401 if let Some(scopes_map) = self.scopes.get(referrer) {
402 imports.push(&scopes_map.imports);
403 }
404
405 for (normalized_scope_key, scopes_map) in self.scopes.iter() {
406 if normalized_scope_key.ends_with('/')
407 && referrer.starts_with(normalized_scope_key)
408 && normalized_scope_key != referrer
410 {
411 imports.push(&scopes_map.imports);
412 }
413 }
414
415 imports.push(&self.imports);
416 imports.into_iter().flat_map(|i| i.entries())
417 }
418
419 pub fn resolve(
420 &self,
421 specifier: &str,
422 referrer: &Url,
423 ) -> Result<Url, ImportMapError> {
424 let as_url: Option<Url> = try_url_like_specifier(specifier, referrer);
425 let normalized_specifier = if let Some(url) = as_url.as_ref() {
426 url.to_string()
427 } else {
428 specifier.to_string()
429 };
430
431 let scopes_match = resolve_scopes_match(
432 &self.scopes,
433 &normalized_specifier,
434 as_url.as_ref(),
435 referrer.as_ref(),
436 )?;
437
438 if let Some(scopes_match) = scopes_match {
440 return Ok(scopes_match);
441 }
442
443 let imports_match = resolve_imports_match(
444 &self.imports,
445 &normalized_specifier,
446 as_url.as_ref(),
447 )?;
448
449 if let Some(imports_match) = imports_match {
451 return Ok(imports_match);
452 }
453
454 if let Some(as_url) = as_url {
456 return Ok(as_url);
457 }
458
459 Err(
460 ImportMapErrorKind::UnmappedBareSpecifier(
461 specifier.to_string(),
462 Some(referrer.to_string()),
463 )
464 .into_box(),
465 )
466 }
467
468 pub fn imports(&self) -> &SpecifierMap {
469 &self.imports
470 }
471
472 pub fn imports_mut(&mut self) -> &mut SpecifierMap {
473 &mut self.imports
474 }
475
476 pub fn scopes(&self) -> impl Iterator<Item = ScopeEntry<'_>> {
477 self.scopes.iter().map(|scope| ScopeEntry {
478 key: scope.0.as_str(),
479 raw_key: scope.1.raw_key.as_deref().unwrap_or(scope.0.as_str()),
480 imports: &scope.1.imports,
481 })
482 }
483
484 pub fn get_or_append_scope_mut(
485 &mut self,
486 key: &str,
487 ) -> Result<&mut SpecifierMap, ImportMapDiagnostic> {
488 let scope_prefix_url = match self.base_url.join(key) {
489 Ok(url) => url.to_string(),
490 _ => {
491 return Err(ImportMapDiagnostic::InvalidScope(
492 key.to_string(),
493 self.base_url.to_string(),
494 ));
495 }
496 };
497
498 let base_url = self.base_url.clone();
501 let index = self.scopes.values().map(|s| s.index + 1).max().unwrap_or(0);
502 Ok(
503 &mut self
504 .scopes
505 .entry(scope_prefix_url.clone())
506 .or_insert_with(|| {
507 ScopesMapValue {
508 index,
509 raw_key: if scope_prefix_url == key {
510 None
511 } else {
512 Some(key.to_string())
514 },
515 imports: SpecifierMap {
516 base_url,
517 inner: Default::default(),
518 },
519 }
520 })
521 .imports,
522 )
523 }
524
525 pub fn to_json(&self) -> String {
527 let mut w = String::new();
528
529 w.push('{');
530
531 if !self.imports.inner.is_empty() {
532 w.push('\n');
533 w.push_str(r#" "imports": {"#);
534 write_imports(&mut w, &self.imports, 2);
535 w.push_str("\n }");
536 }
537
538 if !self.scopes.is_empty() {
539 if !self.imports.inner.is_empty() {
540 w.push(',');
541 }
542 w.push('\n');
543 w.push_str(r#" "scopes": {"#);
544 write_scopes(&mut w, &self.scopes);
545 w.push_str("\n }");
546 }
547
548 w.push_str("\n}\n");
549
550 return w;
551
552 fn write_imports(
553 w: &mut String,
554 imports: &SpecifierMap,
555 indent_level: usize,
556 ) {
557 let mut imports = imports.inner.iter().collect::<Vec<_>>();
559 imports.sort_by_key(|v| v.1.index);
560
561 for (i, (key, value)) in imports.into_iter().enumerate() {
562 w.push_str(if i > 0 { ",\n" } else { "\n" });
563 let raw_key = value.raw_key.as_ref().unwrap_or(key);
564 let raw_value = value
565 .raw_value
566 .as_deref()
567 .or_else(|| value.maybe_address.as_ref().map(|a| a.as_str()));
568
569 w.push_str(&" ".repeat(indent_level));
570 w.push_str(&format!(r#""{}": "#, escape_string(raw_key)));
571 if let Some(value) = raw_value {
572 w.push_str(&format!(r#""{}""#, escape_string(value)));
573 } else {
574 w.push_str("null");
575 }
576 }
577 }
578
579 fn write_scopes(w: &mut String, scopes: &ScopesMap) {
580 let mut scopes = scopes.iter().collect::<Vec<_>>();
582 scopes.sort_by_key(|v| v.1.index);
583
584 for (i, (key, value)) in scopes.into_iter().enumerate() {
585 w.push_str(if i > 0 { ",\n" } else { "\n" });
586 let raw_key = value.raw_key.as_ref().unwrap_or(key);
587
588 w.push_str(&format!(r#" "{}": {{"#, escape_string(raw_key)));
589 write_imports(w, &value.imports, 3);
590 w.push_str("\n }");
591 }
592 }
593
594 fn escape_string(text: &str) -> String {
595 text.replace('"', "\\\"")
596 }
597 }
598}
599
600pub fn parse_from_json(
601 base_url: Url,
602 json_string: &str,
603) -> Result<ImportMapWithDiagnostics, ImportMapError> {
604 parse_from_json_with_options(base_url, json_string, Default::default())
605}
606
607pub fn parse_from_json_with_options(
608 base_url: Url,
609 json_string: &str,
610 options: ImportMapOptions,
611) -> Result<ImportMapWithDiagnostics, ImportMapError> {
612 let mut diagnostics = vec![];
613 let (unresolved_imports, unresolved_scopes) =
614 parse_json(json_string, &options, &mut diagnostics)?;
615 let imports =
616 parse_specifier_map(unresolved_imports, &base_url, &mut diagnostics);
617 let scopes = parse_scope_map(unresolved_scopes, &base_url, &mut diagnostics)?;
618
619 Ok(ImportMapWithDiagnostics {
620 diagnostics,
621 import_map: ImportMap {
622 base_url,
623 imports,
624 scopes,
625 },
626 })
627}
628
629pub fn parse_from_value(
630 base_url: Url,
631 json_value: Value,
632) -> Result<ImportMapWithDiagnostics, ImportMapError> {
633 parse_from_value_with_options(base_url, json_value, Default::default())
634}
635
636pub fn parse_from_value_with_options(
637 base_url: Url,
638 json_value: Value,
639 options: ImportMapOptions,
640) -> Result<ImportMapWithDiagnostics, ImportMapError> {
641 let mut diagnostics = vec![];
642 let (unresolved_imports, unresolved_scopes) =
643 parse_value(json_value, &options, &mut diagnostics)?;
644 let imports =
645 parse_specifier_map(unresolved_imports, &base_url, &mut diagnostics);
646 let scopes = parse_scope_map(unresolved_scopes, &base_url, &mut diagnostics)?;
647
648 Ok(ImportMapWithDiagnostics {
649 diagnostics,
650 import_map: ImportMap {
651 base_url,
652 imports,
653 scopes,
654 },
655 })
656}
657
658fn parse_json(
659 json_string: &str,
660 options: &ImportMapOptions,
661 diagnostics: &mut Vec<ImportMapDiagnostic>,
662) -> Result<(UnresolvedSpecifierMap, UnresolvedScopesMap), ImportMapError> {
663 let v: Value =
664 serde_json::from_str(json_string).map_err(ImportMapErrorKind::JsonParse)?;
665 parse_value(v, options, diagnostics)
666}
667
668fn parse_value(
669 v: Value,
670 options: &ImportMapOptions,
671 diagnostics: &mut Vec<ImportMapDiagnostic>,
672) -> Result<(UnresolvedSpecifierMap, UnresolvedScopesMap), ImportMapError> {
673 fn maybe_expand_imports(value: Value, options: &ImportMapOptions) -> Value {
674 if options.expand_imports {
675 #[cfg(feature = "ext")]
676 {
677 ext::expand_import_map_value(value)
678 }
679 #[cfg(not(feature = "ext"))]
680 {
681 debug_assert!(
682 false,
683 "expand_imports was true, but the \"ext\" feature was not enabled"
684 );
685 value
686 }
687 } else {
688 value
689 }
690 }
691
692 let mut v = maybe_expand_imports(v, options);
693 match v {
694 Value::Object(_) => {}
695 _ => {
696 return Err(ImportMapErrorKind::ImportMapNotObject.into_box());
697 }
698 }
699
700 let imports = if v.get("imports").is_some() {
701 match v["imports"].take() {
702 Value::Object(imports_map) => {
703 parse_specifier_map_json(imports_map, None, options, diagnostics)
704 }
705 _ => {
706 return Err(ImportMapErrorKind::ImportsFieldNotObject.into_box());
707 }
708 }
709 } else {
710 IndexMap::new()
711 };
712
713 let scopes = if v.get("scopes").is_some() {
714 match v["scopes"].take() {
715 Value::Object(scopes_map) => {
716 parse_scopes_map_json(scopes_map, options, diagnostics)?
717 }
718 _ => {
719 return Err(ImportMapErrorKind::ScopesFieldNotObject.into_box());
720 }
721 }
722 } else {
723 IndexMap::new()
724 };
725
726 let mut keys: HashSet<String> = v
727 .as_object()
728 .unwrap()
729 .keys()
730 .map(|k| k.to_string())
731 .collect();
732 keys.remove("imports");
733 keys.remove("scopes");
734 for key in keys {
735 diagnostics.push(ImportMapDiagnostic::InvalidTopLevelKey(key));
736 }
737
738 Ok((imports, scopes))
739}
740
741fn parse_specifier_map_json(
743 json_map: Map<String, Value>,
744 scope: Option<&str>,
745 options: &ImportMapOptions,
746 diagnostics: &mut Vec<ImportMapDiagnostic>,
747) -> UnresolvedSpecifierMap {
748 let mut map: IndexMap<String, Option<String>> = IndexMap::new();
749
750 for (specifier_key, value) in json_map.into_iter() {
752 map.insert(
753 specifier_key.clone(),
754 match value {
755 Value::String(mut address) => {
756 if let Some(address_hook) = &options.address_hook {
757 address = address_hook(&address, &specifier_key, scope);
758 }
759 Some(address)
760 }
761 _ => {
762 diagnostics.push(ImportMapDiagnostic::InvalidAddressNotString(
763 value.to_string(),
764 specifier_key,
765 ));
766 None
767 }
768 },
769 );
770 }
771
772 map
773}
774
775fn parse_scopes_map_json(
777 scopes_map: Map<String, Value>,
778 options: &ImportMapOptions,
779 diagnostics: &mut Vec<ImportMapDiagnostic>,
780) -> Result<UnresolvedScopesMap, ImportMapError> {
781 let mut map = UnresolvedScopesMap::new();
782
783 for (scope_prefix, mut potential_specifier_map) in scopes_map.into_iter() {
785 let potential_specifier_map = match potential_specifier_map.take() {
786 Value::Object(obj) => obj,
787 _ => {
788 return Err(
789 ImportMapErrorKind::ScopePrefixNotObject(scope_prefix).into_box(),
790 );
791 }
792 };
793
794 let specifier_map = parse_specifier_map_json(
795 potential_specifier_map,
796 Some(&scope_prefix),
797 options,
798 diagnostics,
799 );
800
801 map.insert(scope_prefix.to_string(), specifier_map);
802 }
803
804 Ok(map)
805}
806
807fn parse_specifier_map(
813 imports: UnresolvedSpecifierMap,
814 base_url: &Url,
815 diagnostics: &mut Vec<ImportMapDiagnostic>,
816) -> SpecifierMap {
817 let mut normalized_map: SpecifierMapInner = SpecifierMapInner::new();
818
819 for (i, (key, value)) in imports.into_iter().enumerate() {
820 let raw = RawKeyValue { key, value };
821 let normalized_key = match normalize_specifier_key(&raw.key, base_url) {
822 Ok(s) => s,
823 Err(err) => {
824 diagnostics.push(err);
825 continue;
826 }
827 };
828 let potential_address = match &raw.value {
829 Some(address) => address,
830 None => {
831 let value = SpecifierMapValue::new(i, &raw, &normalized_key, None);
832 normalized_map.insert(normalized_key, value);
833 continue;
834 }
835 };
836
837 let address_url = match try_url_like_specifier(potential_address, base_url)
838 {
839 Some(url) => url,
840 None => {
841 diagnostics.push(ImportMapDiagnostic::InvalidAddress(
842 potential_address.to_string(),
843 raw.key.to_string(),
844 ));
845 let value = SpecifierMapValue::new(i, &raw, &normalized_key, None);
846 normalized_map.insert(normalized_key, value);
847 continue;
848 }
849 };
850
851 let address_url_string = address_url.to_string();
852 if raw.key.ends_with('/') && !address_url_string.ends_with('/') {
853 diagnostics.push(ImportMapDiagnostic::InvalidTargetAddress(
854 address_url_string,
855 raw.key.to_string(),
856 ));
857 let value = SpecifierMapValue::new(i, &raw, &normalized_key, None);
858 normalized_map.insert(normalized_key, value);
859 continue;
860 }
861
862 let value =
863 SpecifierMapValue::new(i, &raw, &normalized_key, Some(address_url));
864 normalized_map.insert(normalized_key, value);
865 }
866
867 normalized_map.sort_by(|k1, _v1, k2, _v2| match k1.cmp(k2) {
869 Ordering::Greater => Ordering::Less,
870 Ordering::Less => Ordering::Greater,
871 Ordering::Equal => unreachable!(),
873 });
874
875 SpecifierMap {
876 inner: normalized_map,
877 base_url: base_url.clone(),
878 }
879}
880
881fn parse_scope_map(
887 scope_map: UnresolvedScopesMap,
888 base_url: &Url,
889 diagnostics: &mut Vec<ImportMapDiagnostic>,
890) -> Result<ScopesMap, ImportMapError> {
891 let mut normalized_map: ScopesMap = ScopesMap::new();
892
893 for (i, (raw_scope_prefix, potential_specifier_map)) in
895 scope_map.into_iter().enumerate()
896 {
897 let scope_prefix_url = match base_url.join(&raw_scope_prefix) {
898 Ok(url) => url.to_string(),
899 _ => {
900 diagnostics.push(ImportMapDiagnostic::InvalidScope(
901 raw_scope_prefix,
902 base_url.to_string(),
903 ));
904 continue;
905 }
906 };
907
908 let norm_map =
909 parse_specifier_map(potential_specifier_map, base_url, diagnostics);
910
911 let value = ScopesMapValue {
912 index: i,
913 raw_key: if scope_prefix_url == raw_scope_prefix {
914 None
915 } else {
916 Some(raw_scope_prefix)
918 },
919 imports: norm_map,
920 };
921 normalized_map.insert(scope_prefix_url, value);
922 }
923
924 normalized_map.sort_by(|k1, _v1, k2, _v2| match k1.cmp(k2) {
926 Ordering::Greater => Ordering::Less,
927 Ordering::Less => Ordering::Greater,
928 Ordering::Equal => unreachable!(),
930 });
931
932 Ok(normalized_map)
933}
934
935fn try_url_like_specifier(specifier: &str, base: &Url) -> Option<Url> {
936 if specifier.starts_with('/')
937 || specifier.starts_with("./")
938 || specifier.starts_with("../")
939 {
940 if let Ok(url) = base.join(specifier) {
941 return Some(url);
942 }
943 }
944
945 if let Ok(url) = Url::parse(specifier) {
946 return Some(url);
947 }
948
949 None
950}
951
952fn normalize_specifier_key(
957 specifier_key: &str,
958 base_url: &Url,
959) -> Result<String, ImportMapDiagnostic> {
960 if specifier_key.is_empty() {
962 Err(ImportMapDiagnostic::EmptySpecifier)
963 } else if let Some(url) = try_url_like_specifier(specifier_key, base_url) {
964 Ok(url.to_string())
965 } else {
966 Ok(specifier_key.to_string())
968 }
969}
970
971fn append_specifier_to_base(
972 base: &Url,
973 specifier: &str,
974) -> Result<Url, url::ParseError> {
975 let specifier =
978 percent_encoding::percent_decode_str(specifier).decode_utf8_lossy();
979 let mut base = base.clone();
980 let is_relative_or_absolute_specifier = specifier.starts_with("../")
981 || specifier.starts_with("./")
982 || specifier.starts_with('/');
983
984 let mut maybe_query_string_and_fragment = None;
990 if !is_relative_or_absolute_specifier && base.path_segments_mut().is_ok() {
991 {
992 let mut segments = base.path_segments_mut().unwrap();
993 segments.pop_if_empty();
994
995 let prefix = match specifier.find(&['?', '#'][..]) {
998 Some(idx) => {
999 maybe_query_string_and_fragment = Some(&specifier[idx..]);
1000 &specifier[..idx]
1001 }
1002 None => &specifier,
1003 };
1004 segments.extend(prefix.split('/'));
1005 }
1006
1007 if let Some(query_string_and_fragment) = maybe_query_string_and_fragment {
1008 Ok(base.join(query_string_and_fragment)?)
1009 } else {
1010 Ok(base)
1011 }
1012 } else {
1013 Ok(base.join(&specifier)?)
1014 }
1015}
1016
1017fn resolve_scopes_match(
1018 scopes: &ScopesMap,
1019 normalized_specifier: &str,
1020 as_url: Option<&Url>,
1021 referrer: &str,
1022) -> Result<Option<Url>, ImportMapError> {
1023 if let Some(scope_imports) = scopes.get(referrer) {
1025 let scope_match = resolve_imports_match(
1026 &scope_imports.imports,
1027 normalized_specifier,
1028 as_url,
1029 )?;
1030 if scope_match.is_some() {
1032 return Ok(scope_match);
1033 }
1034 }
1035
1036 for (normalized_scope_key, scope_imports) in scopes.iter() {
1037 if normalized_scope_key.ends_with('/')
1038 && referrer.starts_with(normalized_scope_key)
1039 {
1040 let scope_match = resolve_imports_match(
1041 &scope_imports.imports,
1042 normalized_specifier,
1043 as_url,
1044 )?;
1045 if scope_match.is_some() {
1047 return Ok(scope_match);
1048 }
1049 }
1050 }
1051
1052 Ok(None)
1053}
1054
1055fn resolve_imports_match(
1056 specifier_map: &SpecifierMap,
1057 normalized_specifier: &str,
1058 as_url: Option<&Url>,
1059) -> Result<Option<Url>, ImportMapError> {
1060 if let Some(value) = specifier_map.inner.get(normalized_specifier) {
1062 if let Some(address) = &value.maybe_address {
1063 return Ok(Some(address.clone()));
1064 } else {
1065 return Err(
1066 ImportMapErrorKind::BlockedByNullEntry(
1067 normalized_specifier.to_string(),
1068 )
1069 .into_box(),
1070 );
1071 }
1072 }
1073
1074 for (specifier_key, value) in specifier_map.inner.iter() {
1078 if !specifier_key.ends_with('/') {
1079 continue;
1080 }
1081
1082 if !normalized_specifier.starts_with(specifier_key) {
1083 continue;
1084 }
1085
1086 if let Some(url) = as_url {
1087 if !is_special(url) {
1088 continue;
1089 }
1090 }
1091
1092 let resolution_result = value.maybe_address.as_ref().ok_or_else(|| {
1093 ImportMapErrorKind::BlockedByNullEntry(specifier_key.clone())
1094 })?;
1095
1096 assert!(resolution_result.to_string().ends_with('/'));
1098
1099 let after_prefix = &normalized_specifier[specifier_key.len()..];
1100
1101 let url = match append_specifier_to_base(resolution_result, after_prefix) {
1102 Ok(url) => url,
1103 Err(_) => {
1104 return Err(
1105 ImportMapErrorKind::SpecifierResolutionFailure {
1106 specifier: normalized_specifier.to_string(),
1107 after_prefix: after_prefix.to_string(),
1108 resolution_result: resolution_result.clone(),
1109 specifier_key: specifier_key.clone(),
1110 }
1111 .into_box(),
1112 );
1113 }
1114 };
1115
1116 if !url.as_str().starts_with(resolution_result.as_str()) {
1117 return Err(
1118 ImportMapErrorKind::SpecifierBacktracksAbovePrefix {
1119 specifier: normalized_specifier.to_string(),
1120 specifier_key: specifier_key.clone(),
1121 }
1122 .into_box(),
1123 );
1124 }
1125
1126 return Ok(Some(url));
1127 }
1128
1129 #[cfg(feature = "logging")]
1130 log::debug!(
1131 "Specifier {:?} was not mapped in import map.",
1132 normalized_specifier
1133 );
1134
1135 Ok(None)
1136}
1137
1138#[cfg(test)]
1139mod test {
1140 use super::*;
1141 use pretty_assertions::assert_eq;
1142
1143 #[test]
1144 fn npm_specifiers() {
1145 let mut specifiers = SpecifierMapInner::new();
1146 specifiers.insert(
1147 "aws-sdk/".to_string(),
1148 SpecifierMapValue {
1149 index: 0,
1150 raw_key: None,
1151 raw_value: None,
1152 maybe_address: Some(Url::parse("npm:aws-sdk/").unwrap()),
1153 },
1154 );
1155 let specifiers = SpecifierMap {
1156 base_url: Url::parse("file:///").unwrap(),
1157 inner: specifiers,
1158 };
1159
1160 assert!(
1161 resolve_imports_match(&specifiers, "aws-sdk/clients/S3", None).is_err()
1162 );
1163
1164 let mut specifiers = SpecifierMapInner::new();
1165 specifiers.insert(
1166 "aws-sdk/".to_string(),
1167 SpecifierMapValue {
1168 index: 0,
1169 raw_key: None,
1170 raw_value: None,
1171 maybe_address: Some(Url::parse("npm:/aws-sdk/").unwrap()),
1172 },
1173 );
1174 let specifiers = SpecifierMap {
1175 base_url: Url::parse("file:///").unwrap(),
1176 inner: specifiers,
1177 };
1178
1179 let resolved_specifier =
1180 resolve_imports_match(&specifiers, "aws-sdk/clients/S3", None)
1181 .unwrap()
1182 .unwrap();
1183
1184 assert_eq!(resolved_specifier.as_str(), "npm:/aws-sdk/clients/S3");
1185 }
1186
1187 #[test]
1188 fn mapped_windows_file_specifier() {
1189 let mut specifiers = SpecifierMapInner::new();
1191 specifiers.insert(
1192 "file:///".to_string(),
1193 SpecifierMapValue {
1194 index: 0,
1195 raw_key: None,
1196 raw_value: None,
1197 maybe_address: Some(Url::parse("http://localhost/").unwrap()),
1198 },
1199 );
1200 let specifiers = SpecifierMap {
1201 base_url: Url::parse("file:///").unwrap(),
1202 inner: specifiers,
1203 };
1204
1205 let resolved_specifier =
1206 resolve_imports_match(&specifiers, "file:///C:/folder/file.ts", None)
1207 .unwrap()
1208 .unwrap();
1209
1210 assert_eq!(
1211 resolved_specifier.as_str(),
1212 "http://localhost/C:/folder/file.ts"
1213 );
1214 }
1215
1216 #[test]
1217 #[cfg(feature = "ext")]
1218 fn ext_expand_imports() {
1219 let url = Url::parse("file:///deno.json").unwrap();
1220 let json_string = r#"{
1221 "imports": {
1222 "@std": "jsr:/@std",
1223 "@foo": "jsr:@foo",
1224 "express": "npm:express@4",
1225 "foo": "https://example.com/foo/bar"
1226 },
1227 "scopes": {
1228 "./folder/": {
1229 "@std": "jsr:/@std",
1230 "@foo": "jsr:@foo",
1231 "express": "npm:express@4",
1232 "foo": "https://example.com/foo/bar"
1233 }
1234 }
1235}"#;
1236 let im = parse_from_json_with_options(
1237 url,
1238 json_string,
1239 ImportMapOptions {
1240 address_hook: None,
1241 expand_imports: true,
1242 },
1243 )
1244 .unwrap();
1245 assert_eq!(
1246 im.import_map.to_json(),
1247 r#"{
1248 "imports": {
1249 "@std": "jsr:/@std",
1250 "@std/": "jsr:/@std/",
1251 "@foo": "jsr:@foo",
1252 "@foo/": "jsr:/@foo/",
1253 "express": "npm:express@4",
1254 "express/": "npm:/express@4/",
1255 "foo": "https://example.com/foo/bar"
1256 },
1257 "scopes": {
1258 "./folder/": {
1259 "@std": "jsr:/@std",
1260 "@std/": "jsr:/@std/",
1261 "@foo": "jsr:@foo",
1262 "@foo/": "jsr:/@foo/",
1263 "express": "npm:express@4",
1264 "express/": "npm:/express@4/",
1265 "foo": "https://example.com/foo/bar"
1266 }
1267 }
1268}
1269"#,
1270 );
1271 }
1272
1273 #[test]
1274 fn iterate_applicable_entries() {
1275 let url = Url::parse("file:///deno.json").unwrap();
1276 let json_string = r#"{
1277 "imports": {
1278 "foo": "./main.ts"
1279 },
1280 "scopes": {
1281 "./folder/": {
1282 "bar": "./main.ts"
1283 },
1284 "./folder/file.ts": {
1285 "baz": "./other.ts"
1286 }
1287 }
1288}"#;
1289 let im = parse_from_json(url, json_string).unwrap();
1290 let im = im.import_map;
1291 let keys = im
1292 .entries_for_referrer(&Url::parse("file:///folder/main.ts").unwrap())
1293 .map(|e| e.raw_key)
1294 .collect::<Vec<_>>();
1295 assert_eq!(keys, ["bar", "foo"]);
1296 let keys = im
1297 .entries_for_referrer(&Url::parse("file:///folder/file.ts").unwrap())
1298 .map(|e| e.raw_key)
1299 .collect::<Vec<_>>();
1300 assert_eq!(keys, ["baz", "bar", "foo"]);
1301 let keys = im
1302 .entries_for_referrer(&Url::parse("file:///other/file.ts").unwrap())
1303 .map(|e| e.raw_key)
1304 .collect::<Vec<_>>();
1305 assert_eq!(keys, ["foo"]);
1306 }
1307}