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