1use std::{
2 borrow::Cow,
3 cmp::Ordering,
4 ops::Range,
5 path::{Component, Path, PathBuf},
6};
7
8use bitflags::bitflags;
9use glob_match::{glob_match, glob_match_with_captures};
10use indexmap::IndexMap;
11use serde::Deserialize;
12
13use crate::{
14 cache::{Cache, CachedPath},
15 error::JsonError,
16 specifier::{decode_path, Specifier, SpecifierType},
17 ResolverError,
18};
19
20bitflags! {
21 #[derive(serde::Serialize)]
23 pub struct Fields: u8 {
24 const MAIN = 1 << 0;
26 const MODULE = 1 << 1;
28 const SOURCE = 1 << 2;
30 const BROWSER = 1 << 3;
32 const ALIAS = 1 << 4;
34 const TSCONFIG = 1 << 5;
36 const TYPES = 1 << 6;
38 }
39}
40
41#[derive(serde::Deserialize, Debug, Default)]
42#[serde(rename_all = "camelCase")]
43struct SerializedPackageJson {
44 #[serde(default)]
45 pub name: String,
46 #[serde(rename = "type", default)]
47 pub module_type: ModuleType,
48 main: Option<PathBuf>,
49 module: Option<PathBuf>,
50 tsconfig: Option<PathBuf>,
51 types: Option<PathBuf>,
52 #[serde(default)]
53 pub source: SourceField,
54 #[serde(default)]
55 browser: BrowserField,
56 #[serde(default)]
57 alias: IndexMap<Specifier<'static>, AliasValue<'static>>,
58 #[serde(default)]
59 exports: ExportsField,
60 #[serde(default)]
61 imports: IndexMap<ExportsKey<'static>, ExportsField>,
62 #[serde(default)]
63 side_effects: SideEffects,
64}
65
66#[derive(Debug)]
67pub struct PackageJson {
68 pub path: CachedPath,
69 pub name: String,
70 pub module_type: ModuleType,
71 main: Option<CachedPath>,
72 module: Option<CachedPath>,
73 tsconfig: Option<CachedPath>,
74 types: Option<CachedPath>,
75 pub source: SourceField,
76 browser: BrowserField,
77 alias: IndexMap<Specifier<'static>, AliasValue<'static>>,
78 exports: ExportsField,
79 imports: IndexMap<ExportsKey<'static>, ExportsField>,
80 side_effects: SideEffects,
81}
82
83#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Default, PartialEq)]
85#[serde(rename_all = "lowercase")]
86pub enum ModuleType {
87 Module,
88 Json,
89 #[default]
90 #[serde(other)]
91 CommonJs,
92}
93
94#[derive(serde::Deserialize, Debug, Default)]
95#[serde(untagged)]
96pub enum BrowserField {
97 #[default]
98 None,
99 String(String),
100 Map(IndexMap<Specifier<'static>, AliasValue<'static>>),
101}
102
103#[derive(serde::Deserialize, Debug, Default)]
104#[serde(untagged)]
105pub enum SourceField {
106 #[default]
107 None,
108 String(String),
109 Map(IndexMap<Specifier<'static>, AliasValue<'static>>),
110 Array(Vec<String>),
111 Bool(bool),
112}
113
114#[derive(serde::Deserialize, Debug, Default, PartialEq)]
115#[serde(untagged)]
116pub enum ExportsField {
117 #[default]
118 None,
119 String(String),
120 #[serde(skip)]
121 Path(CachedPath),
122 Array(Vec<ExportsField>),
123 Map(IndexMap<ExportsKey<'static>, ExportsField>),
124}
125
126impl ExportsField {
127 fn convert_paths(&mut self, base: &CachedPath, cache: &Cache) {
128 match self {
129 ExportsField::String(target) => {
130 if target.starts_with("./") && !target.contains('*') {
131 let target_path = decode_path(target.as_ref(), SpecifierType::Esm).0;
135 if target_path
136 .components()
137 .enumerate()
138 .any(|(index, c)| match c {
139 Component::ParentDir => true,
140 Component::CurDir => index > 0,
141 Component::Normal(c) => c.eq_ignore_ascii_case("node_modules"),
142 _ => false,
143 })
144 {
145 return;
146 }
147
148 *self = ExportsField::Path(base.resolve(&target_path, cache));
149 }
150 }
151 ExportsField::Array(arr) => {
152 for item in arr {
153 item.convert_paths(base, cache);
154 }
155 }
156 ExportsField::Map(map) => {
157 for val in map.values_mut() {
158 val.convert_paths(base, cache);
159 }
160 }
161 _ => {}
162 }
163 }
164}
165
166bitflags! {
167 pub struct ExportsCondition: u16 {
169 const IMPORT = 1 << 0;
171 const REQUIRE = 1 << 1;
173 const MODULE = 1 << 2;
175 const NODE = 1 << 3;
177 const BROWSER = 1 << 4;
179 const WORKER = 1 << 5;
181 const WORKLET = 1 << 6;
183 const ELECTRON = 1 << 7;
185 const DEVELOPMENT = 1 << 8;
187 const PRODUCTION = 1 << 9;
189 const TYPES = 1 << 10;
191 const DEFAULT = 1 << 11;
193 const STYLE = 1 << 12;
195 const SASS = 1 << 13;
197 const LESS = 1 << 14;
199 const STYLUS = 1 << 15;
201 }
202}
203
204impl Default for ExportsCondition {
205 fn default() -> Self {
206 ExportsCondition::empty()
207 }
208}
209
210impl TryFrom<&str> for ExportsCondition {
211 type Error = ();
212 fn try_from(value: &str) -> Result<Self, Self::Error> {
213 Ok(match value {
214 "import" => ExportsCondition::IMPORT,
215 "require" => ExportsCondition::REQUIRE,
216 "module" => ExportsCondition::MODULE,
217 "node" => ExportsCondition::NODE,
218 "browser" => ExportsCondition::BROWSER,
219 "worker" => ExportsCondition::WORKER,
220 "worklet" => ExportsCondition::WORKLET,
221 "electron" => ExportsCondition::ELECTRON,
222 "development" => ExportsCondition::DEVELOPMENT,
223 "production" => ExportsCondition::PRODUCTION,
224 "types" => ExportsCondition::TYPES,
225 "default" => ExportsCondition::DEFAULT,
226 "style" => ExportsCondition::STYLE,
227 "sass" => ExportsCondition::SASS,
228 "less" => ExportsCondition::LESS,
229 "stylus" => ExportsCondition::STYLUS,
230 _ => return Err(()),
231 })
232 }
233}
234
235#[derive(Debug, PartialEq, Eq, Hash)]
236pub enum ExportsKey<'a> {
237 Main,
238 Pattern(Cow<'a, str>),
239 Condition(ExportsCondition),
240 CustomCondition(String),
241}
242
243impl<'a> From<&str> for ExportsKey<'a> {
244 fn from(key: &str) -> Self {
245 if key == "." {
246 ExportsKey::Main
247 } else if let Some(key) = key.strip_prefix("./") {
248 ExportsKey::Pattern(Cow::Owned(key.to_owned()))
249 } else if let Some(key) = key.strip_prefix('#') {
250 ExportsKey::Pattern(Cow::Owned(key.to_owned()))
251 } else if let Ok(c) = ExportsCondition::try_from(key) {
252 ExportsKey::Condition(c)
253 } else {
254 ExportsKey::CustomCondition(key.to_owned())
255 }
256 }
257}
258
259impl<'de> Deserialize<'de> for ExportsKey<'static> {
260 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
261 where
262 D: serde::Deserializer<'de>,
263 {
264 let s: &'de str = Deserialize::deserialize(deserializer)?;
265 Ok(ExportsKey::from(s))
266 }
267}
268
269#[derive(serde::Deserialize, Clone, PartialEq, Debug)]
270#[serde(untagged)]
271pub enum AliasValue<'a> {
272 #[serde(bound(deserialize = "'a: 'static"))]
273 Specifier(Specifier<'a>),
274 Bool(bool),
275 Global {
276 global: String,
277 },
278}
279
280#[derive(serde::Deserialize, Clone, Default, PartialEq, Debug)]
281#[serde(untagged)]
282pub enum SideEffects {
283 #[default]
284 None,
285 Boolean(bool),
286 String(String),
287 Array(Vec<String>),
288}
289
290#[derive(Debug, Clone, PartialEq, serde::Serialize)]
292pub enum PackageJsonError {
293 InvalidPackageTarget,
295 PackagePathNotExported,
297 InvalidSpecifier,
299 ImportNotDefined,
301}
302
303#[derive(Debug, PartialEq)]
304pub enum ExportsResolution<'a> {
305 None,
306 Path(CachedPath),
307 Package(Cow<'a, str>),
308}
309
310impl PackageJson {
311 pub fn read(path: &CachedPath, cache: &Cache) -> Result<PackageJson, ResolverError> {
312 let contents = cache.fs.read_to_string(path.as_path())?;
313 let mut pkg = PackageJson::parse(path.clone(), contents, cache)
314 .map_err(|e| JsonError::new(path.as_path().into(), e))?;
315
316 if !matches!(pkg.source, SourceField::None) {
322 let realpath = pkg.path.canonicalize(&cache)?;
323 if realpath == pkg.path || realpath.in_node_modules() {
324 pkg.source = SourceField::None;
325 }
326 }
327
328 Ok(pkg)
329 }
330
331 pub fn parse(path: CachedPath, data: String, cache: &Cache) -> serde_json::Result<PackageJson> {
332 let parsed: SerializedPackageJson = serde_json::from_str(&data)?;
333 Ok(PackageJson::from_serialized(path, parsed, cache))
334 }
335
336 fn from_serialized(
337 path: CachedPath,
338 mut parsed: SerializedPackageJson,
339 cache: &Cache,
340 ) -> PackageJson {
341 parsed.exports.convert_paths(&path, cache);
342 PackageJson {
343 name: parsed.name,
344 module_type: parsed.module_type,
345 main: parsed.main.map(|main| path.resolve(&main, cache)),
346 module: parsed.module.map(|module| path.resolve(&module, cache)),
347 tsconfig: parsed
348 .tsconfig
349 .map(|tsconfig| path.resolve(&tsconfig, cache)),
350 types: parsed.types.map(|types| path.resolve(&types, cache)),
351 source: parsed.source,
352 browser: parsed.browser,
353 alias: parsed.alias,
354 exports: parsed.exports,
355 imports: parsed.imports,
356 side_effects: parsed.side_effects,
357 path,
358 }
359 }
360
361 pub fn entries<'a>(&'a self, fields: Fields, cache: &'a Cache) -> EntryIter {
362 EntryIter {
363 package: self,
364 fields,
365 cache,
366 }
367 }
368
369 pub fn source(&self, cache: &Cache) -> Option<CachedPath> {
370 match &self.source {
371 SourceField::None | SourceField::Array(_) | SourceField::Bool(_) => None,
372 SourceField::String(source) => Some(self.path.resolve(Path::new(source), cache)),
373 SourceField::Map(map) => match map.get(&Specifier::Package(
374 Cow::Borrowed(self.name.as_str()),
375 Cow::Borrowed(""),
376 )) {
377 Some(AliasValue::Specifier(Specifier::Relative(s))) => Some(self.path.resolve(s, cache)),
378 _ => None,
379 },
380 }
381 }
382
383 pub fn has_exports(&self) -> bool {
384 self.exports != ExportsField::None
385 }
386
387 pub fn resolve_package_exports(
388 &self,
389 subpath: &str,
390 conditions: ExportsCondition,
391 custom_conditions: &[String],
392 paths: &Cache,
393 ) -> Result<CachedPath, PackageJsonError> {
394 if let ExportsField::Map(map) = &self.exports {
396 let mut has_conditions = false;
397 let mut has_patterns = false;
398 for key in map.keys() {
399 has_conditions = has_conditions
400 || matches!(
401 key,
402 ExportsKey::Condition(..) | ExportsKey::CustomCondition(..)
403 );
404 has_patterns = has_patterns || matches!(key, ExportsKey::Pattern(..) | ExportsKey::Main);
405 if has_conditions && has_patterns {
406 return Err(PackageJsonError::InvalidPackageTarget);
407 }
408 }
409 }
410
411 if subpath.is_empty() {
412 let mut main_export = &ExportsField::None;
413 match &self.exports {
414 ExportsField::None
415 | ExportsField::String(_)
416 | ExportsField::Path(_)
417 | ExportsField::Array(_) => {
418 main_export = &self.exports;
419 }
420 ExportsField::Map(map) => {
421 if let Some(v) = map.get(&ExportsKey::Main) {
422 main_export = v;
423 } else if !map.keys().any(|k| matches!(k, ExportsKey::Pattern(_))) {
424 main_export = &self.exports;
425 }
426 }
427 }
428
429 if main_export != &ExportsField::None {
430 match self.resolve_package_target(
431 main_export,
432 "",
433 false,
434 conditions,
435 custom_conditions,
436 paths,
437 )? {
438 ExportsResolution::Path(path) => return Ok(path),
439 ExportsResolution::None | ExportsResolution::Package(..) => {}
440 }
441 }
442 } else if let ExportsField::Map(exports) = &self.exports {
443 match self.resolve_package_imports_exports(
445 subpath,
446 exports,
447 false,
448 conditions,
449 custom_conditions,
450 paths,
451 )? {
452 ExportsResolution::Path(path) => return Ok(path),
453 ExportsResolution::None | ExportsResolution::Package(..) => {}
454 }
455 }
456
457 Err(PackageJsonError::PackagePathNotExported)
458 }
459
460 pub fn resolve_package_imports<'a>(
461 &'a self,
462 specifier: &'a str,
463 conditions: ExportsCondition,
464 custom_conditions: &[String],
465 paths: &Cache,
466 ) -> Result<ExportsResolution<'a>, PackageJsonError> {
467 if specifier == "#" || specifier.starts_with("#/") {
468 return Err(PackageJsonError::InvalidSpecifier);
469 }
470
471 match self.resolve_package_imports_exports(
472 specifier,
473 &self.imports,
474 true,
475 conditions,
476 custom_conditions,
477 paths,
478 )? {
479 ExportsResolution::None => {}
480 res => return Ok(res),
481 }
482
483 Err(PackageJsonError::ImportNotDefined)
484 }
485
486 fn resolve_package_target<'a>(
487 &'a self,
488 target: &'a ExportsField,
489 pattern_match: &str,
490 is_imports: bool,
491 conditions: ExportsCondition,
492 custom_conditions: &[String],
493 paths: &Cache,
494 ) -> Result<ExportsResolution<'_>, PackageJsonError> {
495 match target {
496 ExportsField::String(target) => {
497 if !target.starts_with("./") {
498 if !is_imports || target.starts_with("../") || target.starts_with('/') {
499 return Err(PackageJsonError::InvalidPackageTarget);
500 }
501
502 if !pattern_match.is_empty() {
503 let target = target.replace('*', pattern_match);
504 return Ok(ExportsResolution::Package(Cow::Owned(target)));
505 }
506
507 return Ok(ExportsResolution::Package(Cow::Borrowed(target)));
508 }
509
510 let target = if pattern_match.is_empty() {
511 Cow::Borrowed(target.as_str())
512 } else {
513 Cow::Owned(target.replace('*', pattern_match))
514 };
515
516 let target_path = decode_path(target.as_ref(), SpecifierType::Esm).0;
520 if target_path
521 .components()
522 .enumerate()
523 .any(|(index, c)| match c {
524 Component::ParentDir => true,
525 Component::CurDir => index > 0,
526 Component::Normal(c) => c.eq_ignore_ascii_case("node_modules"),
527 _ => false,
528 })
529 {
530 return Err(PackageJsonError::InvalidPackageTarget);
531 }
532
533 let resolved_target = self.path.resolve(&target_path, paths);
534 return Ok(ExportsResolution::Path(resolved_target));
535 }
536 ExportsField::Path(target) => return Ok(ExportsResolution::Path(target.clone())),
537 ExportsField::Map(target) => {
538 for (key, value) in target {
540 let matches = match key {
541 ExportsKey::Condition(key) => {
542 *key == ExportsCondition::DEFAULT || conditions.contains(*key)
543 }
544 ExportsKey::CustomCondition(key) => custom_conditions.iter().any(|k| k == key),
545 _ => false,
546 };
547 if matches {
548 match self.resolve_package_target(
549 value,
550 pattern_match,
551 is_imports,
552 conditions,
553 custom_conditions,
554 paths,
555 )? {
556 ExportsResolution::None => continue,
557 res => return Ok(res),
558 }
559 }
560 }
561 }
562 ExportsField::Array(target) => {
563 if target.is_empty() {
564 return Err(PackageJsonError::PackagePathNotExported);
565 }
566
567 for item in target {
568 match self.resolve_package_target(
569 item,
570 pattern_match,
571 is_imports,
572 conditions,
573 custom_conditions,
574 paths,
575 ) {
576 Err(_) | Ok(ExportsResolution::None) => continue,
577 Ok(res) => return Ok(res),
578 }
579 }
580 }
581 ExportsField::None => return Ok(ExportsResolution::None),
582 }
583
584 Ok(ExportsResolution::None)
585 }
586
587 fn resolve_package_imports_exports<'a>(
588 &'a self,
589 match_key: &'a str,
590 match_obj: &'a IndexMap<ExportsKey, ExportsField>,
591 is_imports: bool,
592 conditions: ExportsCondition,
593 custom_conditions: &[String],
594 paths: &Cache,
595 ) -> Result<ExportsResolution<'_>, PackageJsonError> {
596 let pattern = ExportsKey::Pattern(Cow::Borrowed(match_key));
597 if let Some(target) = match_obj.get(&pattern) {
598 if !match_key.contains('*') {
599 return self.resolve_package_target(
600 target,
601 "",
602 is_imports,
603 conditions,
604 custom_conditions,
605 paths,
606 );
607 }
608 }
609
610 let mut best_key = "";
611 let mut best_match = "";
612 for key in match_obj.keys() {
613 if let ExportsKey::Pattern(key) = key {
614 if let Some((pattern_base, pattern_trailer)) = key.split_once('*') {
615 if match_key.starts_with(pattern_base)
616 && !pattern_trailer.contains('*')
617 && (pattern_trailer.is_empty()
618 || (match_key.len() >= key.len() && match_key.ends_with(pattern_trailer)))
619 && pattern_key_compare(best_key, key) == Ordering::Greater
620 {
621 best_key = key;
622 best_match = &match_key[pattern_base.len()..match_key.len() - pattern_trailer.len()];
623 }
624 }
625 }
626 }
627
628 if !best_key.is_empty() {
629 return self.resolve_package_target(
630 &match_obj[&ExportsKey::Pattern(Cow::Borrowed(best_key))],
631 best_match,
632 is_imports,
633 conditions,
634 custom_conditions,
635 paths,
636 );
637 }
638
639 Ok(ExportsResolution::None)
640 }
641
642 pub fn resolve_aliases<'a>(
643 &'a self,
644 specifier: &Specifier<'a>,
645 fields: Fields,
646 ) -> Option<Cow<'a, AliasValue<'a>>> {
647 if fields.contains(Fields::SOURCE) {
648 if let SourceField::Map(source) = &self.source {
649 match self.resolve_alias(source, specifier) {
650 None => {}
651 res => return res,
652 }
653 }
654 }
655
656 if fields.contains(Fields::ALIAS) {
657 match self.resolve_alias(&self.alias, specifier) {
658 None => {}
659 res => return res,
660 }
661 }
662
663 if fields.contains(Fields::BROWSER) {
664 if let BrowserField::Map(browser) = &self.browser {
665 match self.resolve_alias(browser, specifier) {
666 None => {}
667 res => return res,
668 }
669 }
670 }
671
672 None
673 }
674
675 fn resolve_alias<'a>(
676 &'a self,
677 map: &'a IndexMap<Specifier<'a>, AliasValue<'a>>,
678 specifier: &Specifier<'a>,
679 ) -> Option<Cow<'a, AliasValue>> {
680 if let Some(alias) = self.lookup_alias(map, specifier) {
681 return Some(alias);
682 }
683
684 if let Specifier::Package(package, subpath) = specifier {
685 if let Some(alias) =
686 self.lookup_alias(map, &Specifier::Package(package.clone(), Cow::Borrowed("")))
687 {
688 match alias.as_ref() {
689 AliasValue::Specifier(base) => {
690 match base {
692 Specifier::Package(base_pkg, base_subpath) => {
693 let subpath = if !base_subpath.is_empty() && !subpath.is_empty() {
694 let mut full_subpath =
695 String::with_capacity(base_subpath.len() + subpath.len() + 1);
696 full_subpath.push_str(base_subpath);
697 full_subpath.push('/');
698 full_subpath.push_str(subpath);
699 Cow::Owned(full_subpath)
700 } else if !subpath.is_empty() {
701 subpath.clone()
702 } else {
703 return Some(alias);
704 };
705 return Some(Cow::Owned(AliasValue::Specifier(Specifier::Package(
706 base_pkg.clone(),
707 subpath,
708 ))));
709 }
710 Specifier::Relative(path) => {
711 if subpath.is_empty() {
712 return Some(alias);
713 } else {
714 return Some(Cow::Owned(AliasValue::Specifier(Specifier::Relative(
715 Cow::Owned(path.join(subpath.as_ref())),
716 ))));
717 }
718 }
719 Specifier::Absolute(path) => {
720 if subpath.is_empty() {
721 return Some(alias);
722 } else {
723 return Some(Cow::Owned(AliasValue::Specifier(Specifier::Absolute(
724 Cow::Owned(path.join(subpath.as_ref())),
725 ))));
726 }
727 }
728 Specifier::Tilde(path) => {
729 if subpath.is_empty() {
730 return Some(alias);
731 } else {
732 return Some(Cow::Owned(AliasValue::Specifier(Specifier::Tilde(
733 Cow::Owned(path.join(subpath.as_ref())),
734 ))));
735 }
736 }
737 _ => return Some(alias),
738 }
739 }
740 _ => return Some(alias),
741 };
742 }
743 }
744
745 None
746 }
747
748 fn lookup_alias<'a>(
749 &'a self,
750 map: &'a IndexMap<Specifier<'a>, AliasValue<'a>>,
751 specifier: &Specifier<'a>,
752 ) -> Option<Cow<'a, AliasValue>> {
753 if let Some(value) = map.get(specifier) {
754 return Some(Cow::Borrowed(value));
755 }
756
757 for (key, value) in map {
759 let (glob, path) = match (key, specifier) {
760 (Specifier::Relative(glob), Specifier::Relative(path))
761 | (Specifier::Absolute(glob), Specifier::Absolute(path))
762 | (Specifier::Tilde(glob), Specifier::Tilde(path)) => (
763 glob.as_os_str().to_string_lossy(),
764 path.as_os_str().to_string_lossy(),
765 ),
766 (Specifier::Package(module_a, glob), Specifier::Package(module_b, path))
767 if module_a == module_b =>
768 {
769 (Cow::Borrowed(glob.as_ref()), Cow::Borrowed(path.as_ref()))
770 }
771 (pkg_a @ Specifier::Package(..), pkg_b @ Specifier::Package(..)) => {
772 (pkg_a.to_string(), pkg_b.to_string())
774 }
775 _ => continue,
776 };
777
778 if let Some(captures) = glob_match_with_captures(&glob, &path) {
779 let res = match value {
780 AliasValue::Specifier(specifier) => AliasValue::Specifier(match specifier {
781 Specifier::Relative(r) => {
782 Specifier::Relative(replace_path_captures(r, &path, &captures)?)
783 }
784 Specifier::Absolute(r) => {
785 Specifier::Absolute(replace_path_captures(r, &path, &captures)?)
786 }
787 Specifier::Tilde(r) => Specifier::Tilde(replace_path_captures(r, &path, &captures)?),
788 Specifier::Package(module, subpath) => {
789 Specifier::Package(module.clone(), replace_captures(subpath, &path, &captures))
790 }
791 _ => return Some(Cow::Borrowed(value)),
792 }),
793 _ => return Some(Cow::Borrowed(value)),
794 };
795
796 return Some(Cow::Owned(res));
797 }
798 }
799
800 None
801 }
802
803 pub fn has_side_effects(&self, path: &Path) -> bool {
804 let path = path
805 .strip_prefix(self.path.as_path().parent().unwrap())
806 .ok()
807 .and_then(|path| path.as_os_str().to_str());
808
809 let path = match path {
810 Some(p) => p,
811 None => return true,
812 };
813
814 fn side_effects_glob_matches(glob: &str, path: &str) -> bool {
815 let glob = glob.strip_prefix("./").unwrap_or(glob);
817
818 let glob = if !glob.contains('/') {
820 Cow::Owned(format!("**/{}", glob))
821 } else {
822 Cow::Borrowed(glob)
823 };
824
825 glob_match(glob.as_ref(), path)
826 }
827
828 match &self.side_effects {
829 SideEffects::None => true,
830 SideEffects::Boolean(b) => *b,
831 SideEffects::String(glob) => side_effects_glob_matches(glob, path),
832 SideEffects::Array(globs) => globs
833 .iter()
834 .any(|glob| side_effects_glob_matches(glob, path)),
835 }
836 }
837}
838
839fn replace_path_captures<'a>(
840 s: &'a Path,
841 path: &str,
842 captures: &Vec<Range<usize>>,
843) -> Option<Cow<'a, Path>> {
844 Some(
845 match replace_captures(s.as_os_str().to_str()?, path, captures) {
846 Cow::Borrowed(b) => Cow::Borrowed(Path::new(b)),
847 Cow::Owned(b) => Cow::Owned(PathBuf::from(b)),
848 },
849 )
850}
851
852fn replace_captures<'a>(s: &'a str, path: &str, captures: &Vec<Range<usize>>) -> Cow<'a, str> {
855 let mut res = Cow::Borrowed(s);
856 let bytes = s.as_bytes();
857 for (idx, _) in s.match_indices('$').rev() {
858 let mut end = idx;
859 while end + 1 < bytes.len() && bytes[end + 1].is_ascii_digit() {
860 end += 1;
861 }
862
863 if end != idx {
864 if let Ok(capture_index) = s[idx + 1..end + 1].parse::<usize>() {
865 if capture_index > 0 && capture_index - 1 < captures.len() {
866 res
867 .to_mut()
868 .replace_range(idx..end + 1, &path[captures[capture_index - 1].clone()]);
869 }
870 }
871 }
872 }
873
874 res
875}
876
877fn pattern_key_compare(a: &str, b: &str) -> Ordering {
878 let a_pos = a.chars().position(|c| c == '*');
879 let b_pos = b.chars().position(|c| c == '*');
880 let base_length_a = a_pos.map_or(a.len(), |p| p + 1);
881 let base_length_b = b_pos.map_or(b.len(), |p| p + 1);
882 let cmp = base_length_b.cmp(&base_length_a);
883 if cmp != Ordering::Equal {
884 return cmp;
885 }
886
887 if a_pos.is_none() {
888 return Ordering::Greater;
889 }
890
891 if b_pos.is_none() {
892 return Ordering::Less;
893 }
894
895 b.len().cmp(&a.len())
896}
897
898pub struct EntryIter<'a> {
899 package: &'a PackageJson,
900 fields: Fields,
901 cache: &'a Cache,
902}
903
904impl<'a> Iterator for EntryIter<'a> {
905 type Item = (CachedPath, &'static str);
906
907 fn next(&mut self) -> Option<Self::Item> {
908 if self.fields.contains(Fields::SOURCE) {
909 self.fields.remove(Fields::SOURCE);
910 if let Some(source) = self.package.source(&self.cache) {
911 return Some((source, "source"));
912 }
913 }
914
915 if self.fields.contains(Fields::TYPES) {
916 self.fields.remove(Fields::TYPES);
917 if let Some(types) = &self.package.types {
918 return Some((types.clone(), "types"));
919 }
920 }
921
922 if self.fields.contains(Fields::BROWSER) {
923 self.fields.remove(Fields::BROWSER);
924 match &self.package.browser {
925 BrowserField::None => {}
926 BrowserField::String(browser) => {
927 return Some((
928 self.package.path.resolve(Path::new(browser), self.cache),
929 "browser",
930 ))
931 }
932 BrowserField::Map(map) => {
933 if let Some(AliasValue::Specifier(Specifier::Relative(s))) = map.get(&Specifier::Package(
934 Cow::Borrowed(&self.package.name),
935 Cow::Borrowed(""),
936 )) {
937 return Some((self.package.path.resolve(s, self.cache), "browser"));
938 }
939 }
940 }
941 }
942
943 if self.fields.contains(Fields::MODULE) {
944 self.fields.remove(Fields::MODULE);
945 if let Some(module) = &self.package.module {
946 return Some((module.clone(), "module"));
947 }
948 }
949
950 if self.fields.contains(Fields::MAIN) {
951 self.fields.remove(Fields::MAIN);
952 if let Some(main) = &self.package.main {
953 return Some((main.clone(), "main"));
954 }
955 }
956
957 if self.fields.contains(Fields::TSCONFIG) {
958 self.fields.remove(Fields::TSCONFIG);
959 if let Some(tsconfig) = &self.package.tsconfig {
960 return Some((tsconfig.clone(), "tsconfig"));
961 }
962 }
963
964 None
965 }
966}
967
968#[cfg(test)]
969mod tests {
970 use super::*;
971 use indexmap::indexmap;
972
973 #[test]
978 fn exports_string() {
979 let cache = Cache::default();
980 let pkg = PackageJson::from_serialized(
981 cache.get_normalized("/foo/package.json"),
982 SerializedPackageJson {
983 name: "foobar".into(),
984 exports: ExportsField::String("./exports.js".into()),
985 ..Default::default()
986 },
987 &cache,
988 );
989
990 assert_eq!(
991 pkg
992 .resolve_package_exports("", ExportsCondition::empty(), &[], &cache)
993 .unwrap(),
994 cache.get_normalized("/foo/exports.js")
995 );
996 }
999
1000 #[test]
1001 fn exports_dot() {
1002 let cache = Cache::default();
1003 let pkg = PackageJson::from_serialized(
1004 cache.get_normalized("/foo/package.json"),
1005 SerializedPackageJson {
1006 name: "foobar".into(),
1007 exports: ExportsField::Map(indexmap! {
1008 ".".into() => ExportsField::String("./exports.js".into())
1009 }),
1010 ..Default::default()
1011 },
1012 &cache,
1013 );
1014
1015 assert_eq!(
1016 pkg
1017 .resolve_package_exports("", ExportsCondition::empty(), &[], &cache)
1018 .unwrap(),
1019 cache.get_normalized("/foo/exports.js")
1020 );
1021 assert!(matches!(
1022 pkg.resolve_package_exports(".", ExportsCondition::empty(), &[], &cache),
1023 Err(PackageJsonError::PackagePathNotExported)
1024 ));
1025 }
1027
1028 #[test]
1029 fn exports_dot_conditions() {
1030 let cache = Cache::default();
1031 let pkg = PackageJson::from_serialized(
1032 cache.get_normalized("/foo/package.json"),
1033 SerializedPackageJson {
1034 name: "foobar".into(),
1035 exports: ExportsField::Map(indexmap! {
1036 ".".into() => ExportsField::Map(indexmap! {
1037 "import".into() => ExportsField::String("./import.js".into()),
1038 "require".into() => ExportsField::String("./require.js".into())
1039 })
1040 }),
1041 ..Default::default()
1042 },
1043 &cache,
1044 );
1045
1046 assert_eq!(
1047 pkg
1048 .resolve_package_exports(
1049 "",
1050 ExportsCondition::IMPORT | ExportsCondition::REQUIRE,
1051 &[],
1052 &cache
1053 )
1054 .unwrap(),
1055 cache.get_normalized("/foo/import.js")
1056 );
1057 assert_eq!(
1058 pkg
1059 .resolve_package_exports("", ExportsCondition::REQUIRE, &[], &cache)
1060 .unwrap(),
1061 cache.get_normalized("/foo/require.js")
1062 );
1063 assert!(matches!(
1064 pkg.resolve_package_exports("", ExportsCondition::empty(), &[], &cache),
1065 Err(PackageJsonError::PackagePathNotExported)
1066 ));
1067 assert!(matches!(
1068 pkg.resolve_package_exports("", ExportsCondition::NODE, &[], &cache),
1069 Err(PackageJsonError::PackagePathNotExported)
1070 ));
1071 }
1072
1073 #[test]
1074 fn exports_map_string() {
1075 let cache = Cache::default();
1076 let pkg = PackageJson::from_serialized(
1077 cache.get_normalized("/foo/package.json"),
1078 SerializedPackageJson {
1079 name: "foobar".into(),
1080 exports: ExportsField::Map(indexmap! {
1081 "./foo".into() => ExportsField::String("./exports.js".into()),
1082 "./.invisible".into() => ExportsField::String("./.invisible.js".into()),
1083 "./".into() => ExportsField::String("./".into()),
1084 "./*".into() => ExportsField::String("./*.js".into())
1085 }),
1086 ..Default::default()
1087 },
1088 &cache,
1089 );
1090
1091 assert_eq!(
1092 pkg
1093 .resolve_package_exports("foo", ExportsCondition::empty(), &[], &cache)
1094 .unwrap(),
1095 cache.get_normalized("/foo/exports.js")
1096 );
1097 assert_eq!(
1098 pkg
1099 .resolve_package_exports(".invisible", ExportsCondition::empty(), &[], &cache)
1100 .unwrap(),
1101 cache.get_normalized("/foo/.invisible.js")
1102 );
1103 assert_eq!(
1104 pkg
1105 .resolve_package_exports("file", ExportsCondition::empty(), &[], &cache)
1106 .unwrap(),
1107 cache.get_normalized("/foo/file.js")
1108 );
1109 }
1110
1111 #[test]
1112 fn exports_map_conditions() {
1113 let cache = Cache::default();
1114 let pkg = PackageJson::from_serialized(
1115 cache.get_normalized("/foo/package.json"),
1116 SerializedPackageJson {
1117 name: "foobar".into(),
1118 exports: ExportsField::Map(indexmap! {
1119 "./foo".into() => ExportsField::Map(indexmap! {
1120 "import".into() => ExportsField::String("./import.js".into()),
1121 "require".into() => ExportsField::String("./require.js".into())
1122 })
1123 }),
1124 ..Default::default()
1125 },
1126 &cache,
1127 );
1128
1129 assert_eq!(
1130 pkg
1131 .resolve_package_exports(
1132 "foo",
1133 ExportsCondition::IMPORT | ExportsCondition::REQUIRE,
1134 &[],
1135 &cache
1136 )
1137 .unwrap(),
1138 cache.get_normalized("/foo/import.js")
1139 );
1140 assert_eq!(
1141 pkg
1142 .resolve_package_exports("foo", ExportsCondition::REQUIRE, &[], &cache)
1143 .unwrap(),
1144 cache.get_normalized("/foo/require.js")
1145 );
1146 assert!(matches!(
1147 pkg.resolve_package_exports("foo", ExportsCondition::empty(), &[], &cache),
1148 Err(PackageJsonError::PackagePathNotExported)
1149 ));
1150 assert!(matches!(
1151 pkg.resolve_package_exports("foo", ExportsCondition::NODE, &[], &cache),
1152 Err(PackageJsonError::PackagePathNotExported)
1153 ));
1154 }
1155
1156 #[test]
1157 fn nested_conditions() {
1158 let cache = Cache::default();
1159 let pkg = PackageJson::from_serialized(
1160 cache.get_normalized("/foo/package.json"),
1161 SerializedPackageJson {
1162 name: "foobar".into(),
1163 exports: ExportsField::Map(indexmap! {
1164 "node".into() => ExportsField::Map(indexmap! {
1165 "import".into() => ExportsField::String("./import.js".into()),
1166 "require".into() => ExportsField::String("./require.js".into())
1167 }),
1168 "default".into() => ExportsField::String("./default.js".into())
1169 }),
1170 ..Default::default()
1171 },
1172 &cache,
1173 );
1174
1175 assert_eq!(
1176 pkg
1177 .resolve_package_exports(
1178 "",
1179 ExportsCondition::NODE | ExportsCondition::IMPORT,
1180 &[],
1181 &cache
1182 )
1183 .unwrap(),
1184 cache.get_normalized("/foo/import.js")
1185 );
1186 assert_eq!(
1187 pkg
1188 .resolve_package_exports(
1189 "",
1190 ExportsCondition::NODE | ExportsCondition::REQUIRE,
1191 &[],
1192 &cache
1193 )
1194 .unwrap(),
1195 cache.get_normalized("/foo/require.js")
1196 );
1197 assert_eq!(
1198 pkg
1199 .resolve_package_exports("", ExportsCondition::IMPORT, &[], &cache)
1200 .unwrap(),
1201 cache.get_normalized("/foo/default.js")
1202 );
1203 assert_eq!(
1204 pkg
1205 .resolve_package_exports("", ExportsCondition::empty(), &[], &cache)
1206 .unwrap(),
1207 cache.get_normalized("/foo/default.js")
1208 );
1209 assert_eq!(
1210 pkg
1211 .resolve_package_exports("", ExportsCondition::NODE, &[], &cache)
1212 .unwrap(),
1213 cache.get_normalized("/foo/default.js")
1214 );
1215 }
1216
1217 #[test]
1218 fn custom_conditions() {
1219 let cache = Cache::default();
1220 let pkg = PackageJson::from_serialized(
1221 cache.get_normalized("/foo/package.json"),
1222 SerializedPackageJson {
1223 name: "foobar".into(),
1224 exports: ExportsField::Map(indexmap! {
1225 "custom".into() => ExportsField::String("./custom.js".into()),
1226 "default".into() => ExportsField::String("./default.js".into())
1227 }),
1228 ..Default::default()
1229 },
1230 &cache,
1231 );
1232 assert_eq!(
1233 pkg
1234 .resolve_package_exports("", ExportsCondition::NODE, &["custom".into()], &cache)
1235 .unwrap(),
1236 cache.get_normalized("/foo/custom.js")
1237 );
1238 assert_eq!(
1239 pkg
1240 .resolve_package_exports("", ExportsCondition::NODE, &[], &cache)
1241 .unwrap(),
1242 cache.get_normalized("/foo/default.js")
1243 );
1244 }
1245
1246 #[test]
1247 fn subpath_nested_conditions() {
1248 let cache = Cache::default();
1249 let pkg = PackageJson::from_serialized(
1250 cache.get_normalized("/foo/package.json"),
1251 SerializedPackageJson {
1252 name: "foobar".into(),
1253 exports: ExportsField::Map(indexmap! {
1254 "./lite".into() => ExportsField::Map(indexmap! {
1255 "node".into() => ExportsField::Map(indexmap! {
1256 "import".into() => ExportsField::String("./node_import.js".into()),
1257 "require".into() => ExportsField::String("./node_require.js".into())
1258 }),
1259 "browser".into() => ExportsField::Map(indexmap! {
1260 "import".into() => ExportsField::String("./browser_import.js".into()),
1261 "require".into() => ExportsField::String("./browser_require.js".into())
1262 }),
1263 })
1264 }),
1265 ..Default::default()
1266 },
1267 &cache,
1268 );
1269
1270 assert_eq!(
1271 pkg
1272 .resolve_package_exports(
1273 "lite",
1274 ExportsCondition::NODE | ExportsCondition::IMPORT,
1275 &[],
1276 &cache
1277 )
1278 .unwrap(),
1279 cache.get_normalized("/foo/node_import.js")
1280 );
1281 assert_eq!(
1282 pkg
1283 .resolve_package_exports(
1284 "lite",
1285 ExportsCondition::NODE | ExportsCondition::REQUIRE,
1286 &[],
1287 &cache
1288 )
1289 .unwrap(),
1290 cache.get_normalized("/foo/node_require.js")
1291 );
1292 assert_eq!(
1293 pkg
1294 .resolve_package_exports(
1295 "lite",
1296 ExportsCondition::BROWSER | ExportsCondition::IMPORT,
1297 &[],
1298 &cache
1299 )
1300 .unwrap(),
1301 cache.get_normalized("/foo/browser_import.js")
1302 );
1303 assert_eq!(
1304 pkg
1305 .resolve_package_exports(
1306 "lite",
1307 ExportsCondition::BROWSER | ExportsCondition::REQUIRE,
1308 &[],
1309 &cache
1310 )
1311 .unwrap(),
1312 cache.get_normalized("/foo/browser_require.js")
1313 );
1314 assert!(matches!(
1315 pkg.resolve_package_exports("lite", ExportsCondition::empty(), &[], &cache),
1316 Err(PackageJsonError::PackagePathNotExported)
1317 ));
1318 }
1319
1320 #[test]
1321 fn subpath_star() {
1322 let cache = Cache::default();
1323 let pkg = PackageJson::from_serialized(
1324 cache.get_normalized("/foo/package.json"),
1325 SerializedPackageJson {
1326 name: "foobar".into(),
1327 exports: ExportsField::Map(indexmap! {
1328 "./*".into() => ExportsField::String("./cheese/*.mjs".into()),
1329 "./pizza/*".into() => ExportsField::String("./pizza/*.mjs".into()),
1330 "./burritos/*".into() => ExportsField::String("./burritos/*/*.mjs".into()),
1331 "./literal".into() => ExportsField::String("./literal/*.js".into()),
1332 }),
1333 ..Default::default()
1334 },
1335 &cache,
1336 );
1337
1338 assert_eq!(
1339 pkg
1340 .resolve_package_exports("hello", ExportsCondition::empty(), &[], &cache)
1341 .unwrap(),
1342 cache.get_normalized("/foo/cheese/hello.mjs")
1343 );
1344 assert_eq!(
1345 pkg
1346 .resolve_package_exports("hello/world", ExportsCondition::empty(), &[], &cache)
1347 .unwrap(),
1348 cache.get_normalized("/foo/cheese/hello/world.mjs")
1349 );
1350 assert_eq!(
1351 pkg
1352 .resolve_package_exports("hello.js", ExportsCondition::empty(), &[], &cache)
1353 .unwrap(),
1354 cache.get_normalized("/foo/cheese/hello.js.mjs")
1355 );
1356 assert_eq!(
1357 pkg
1358 .resolve_package_exports("pizza/test", ExportsCondition::empty(), &[], &cache)
1359 .unwrap(),
1360 cache.get_normalized("/foo/pizza/test.mjs")
1361 );
1362 assert_eq!(
1363 pkg
1364 .resolve_package_exports("burritos/test", ExportsCondition::empty(), &[], &cache)
1365 .unwrap(),
1366 cache.get_normalized("/foo/burritos/test/test.mjs")
1367 );
1368 assert_eq!(
1369 pkg
1370 .resolve_package_exports("literal", ExportsCondition::empty(), &[], &cache)
1371 .unwrap(),
1372 cache.get_normalized("/foo/literal/*.js")
1373 );
1374
1375 let pkg = PackageJson::from_serialized(
1376 cache.get_normalized("/foo/package.json"),
1377 SerializedPackageJson {
1378 name: "foobar".into(),
1379 exports: ExportsField::Map(indexmap! {
1380 "./*".into() => ExportsField::String("./*.js".into()),
1381 "./*.js".into() => ExportsField::None,
1382 "./internal/*".into() => ExportsField::None,
1383 }),
1384 ..Default::default()
1385 },
1386 &cache,
1387 );
1388 assert_eq!(
1389 pkg
1390 .resolve_package_exports("file", ExportsCondition::empty(), &[], &cache)
1391 .unwrap(),
1392 cache.get_normalized("/foo/file.js")
1393 );
1394 assert!(matches!(
1395 pkg.resolve_package_exports("file.js", ExportsCondition::empty(), &[], &cache),
1396 Err(PackageJsonError::PackagePathNotExported)
1397 ));
1398 assert!(matches!(
1399 pkg.resolve_package_exports("internal/file", ExportsCondition::empty(), &[], &cache),
1400 Err(PackageJsonError::PackagePathNotExported)
1401 ));
1402 }
1403
1404 #[test]
1405 fn exports_null() {
1406 let cache = Cache::default();
1407 let pkg = PackageJson::from_serialized(
1408 cache.get_normalized("/foo/package.json"),
1409 SerializedPackageJson {
1410 name: "foobar".into(),
1411 exports: ExportsField::Map(indexmap! {
1412 "./features/*.js".into() => ExportsField::String("./src/features/*.js".into()),
1413 "./features/private-internal/*".into() => ExportsField::None,
1414 }),
1415 ..Default::default()
1416 },
1417 &cache,
1418 );
1419
1420 assert_eq!(
1421 pkg
1422 .resolve_package_exports("features/foo.js", ExportsCondition::empty(), &[], &cache)
1423 .unwrap(),
1424 cache.get_normalized("/foo/src/features/foo.js")
1425 );
1426 assert_eq!(
1427 pkg
1428 .resolve_package_exports(
1429 "features/foo/bar.js",
1430 ExportsCondition::empty(),
1431 &[],
1432 &cache
1433 )
1434 .unwrap(),
1435 cache.get_normalized("/foo/src/features/foo/bar.js")
1436 );
1437 assert!(matches!(
1438 pkg.resolve_package_exports(
1439 "features/private-internal/foo.js",
1440 ExportsCondition::empty(),
1441 &[],
1442 &cache
1443 ),
1444 Err(PackageJsonError::PackagePathNotExported)
1445 ),);
1446 }
1447
1448 #[test]
1449 fn exports_array() {
1450 let cache = Cache::default();
1451 let pkg = PackageJson::from_serialized(
1452 cache.get_normalized("/foo/package.json"),
1453 SerializedPackageJson {
1454 name: "foobar".into(),
1455 exports: ExportsField::Map(indexmap! {
1456 "./utils/*".into() => ExportsField::Map(indexmap! {
1457 "browser".into() => ExportsField::Map(indexmap! {
1458 "worklet".into() => ExportsField::Array(vec![ExportsField::String("./*".into()), ExportsField::String("./node/*".into())]),
1459 "default".into() => ExportsField::Map(indexmap! {
1460 "node".into() => ExportsField::String("./node/*".into())
1461 })
1462 })
1463 }),
1464 "./test/*".into() => ExportsField::Array(vec![ExportsField::String("lodash/*".into()), ExportsField::String("./bar/*".into())]),
1465 "./file".into() => ExportsField::Array(vec![ExportsField::String("http://a.com".into()), ExportsField::String("./file.js".into())])
1466 }),
1467 ..Default::default()
1468 },
1469 &cache,
1470 );
1471
1472 assert_eq!(
1473 pkg
1474 .resolve_package_exports(
1475 "utils/index.js",
1476 ExportsCondition::BROWSER | ExportsCondition::WORKLET,
1477 &[],
1478 &cache
1479 )
1480 .unwrap(),
1481 cache.get_normalized("/foo/index.js")
1482 );
1483 assert_eq!(
1484 pkg
1485 .resolve_package_exports(
1486 "utils/index.js",
1487 ExportsCondition::BROWSER | ExportsCondition::NODE,
1488 &[],
1489 &cache
1490 )
1491 .unwrap(),
1492 cache.get_normalized("/foo/node/index.js")
1493 );
1494 assert_eq!(
1495 pkg
1496 .resolve_package_exports("test/index.js", ExportsCondition::empty(), &[], &cache)
1497 .unwrap(),
1498 cache.get_normalized("/foo/bar/index.js")
1499 );
1500 assert_eq!(
1501 pkg
1502 .resolve_package_exports("file", ExportsCondition::empty(), &[], &cache)
1503 .unwrap(),
1504 cache.get_normalized("/foo/file.js")
1505 );
1506 assert!(matches!(
1507 pkg.resolve_package_exports("utils/index.js", ExportsCondition::BROWSER, &[], &cache),
1508 Err(PackageJsonError::PackagePathNotExported)
1509 ));
1510 assert!(matches!(
1511 pkg.resolve_package_exports("dir/file.js", ExportsCondition::BROWSER, &[], &cache),
1512 Err(PackageJsonError::PackagePathNotExported)
1513 ));
1514
1515 let pkg = PackageJson::from_serialized(
1516 cache.get_normalized("/foo/package.json"),
1517 SerializedPackageJson {
1518 name: "foobar".into(),
1519 exports: ExportsField::Array(vec![
1520 ExportsField::Map(indexmap! {
1521 "node".into() => ExportsField::String("./a.js".into())
1522 }),
1523 ExportsField::String("./b.js".into()),
1524 ]),
1525 ..Default::default()
1526 },
1527 &cache,
1528 );
1529
1530 assert_eq!(
1531 pkg
1532 .resolve_package_exports("", ExportsCondition::empty(), &[], &cache)
1533 .unwrap(),
1534 cache.get_normalized("/foo/b.js")
1535 );
1536 assert_eq!(
1537 pkg
1538 .resolve_package_exports("", ExportsCondition::NODE, &[], &cache)
1539 .unwrap(),
1540 cache.get_normalized("/foo/a.js")
1541 );
1542 }
1543
1544 #[test]
1545 fn exports_invalid() {
1546 let cache = Cache::default();
1547 let pkg = PackageJson::from_serialized(
1548 cache.get_normalized("/foo/package.json"),
1549 SerializedPackageJson {
1550 name: "foobar".into(),
1551 exports: ExportsField::Map(indexmap! {
1552 "./invalid".into() => ExportsField::String("../invalid".into()),
1553 "./absolute".into() => ExportsField::String("/absolute".into()),
1554 "./package".into() => ExportsField::String("package".into()),
1555 "./utils/index".into() => ExportsField::String("./src/../index.js".into()),
1556 "./dist/*".into() => ExportsField::String("./src/../../*".into()),
1557 "./modules/*".into() => ExportsField::String("./node_modules/*".into()),
1558 "./modules2/*".into() => ExportsField::String("./NODE_MODULES/*".into()),
1559 "./*/*".into() => ExportsField::String("./file.js".into())
1560 }),
1561 ..Default::default()
1562 },
1563 &cache,
1564 );
1565
1566 assert!(matches!(
1567 pkg.resolve_package_exports("invalid", ExportsCondition::empty(), &[], &cache),
1568 Err(PackageJsonError::InvalidPackageTarget)
1569 ));
1570 assert!(matches!(
1571 pkg.resolve_package_exports("absolute", ExportsCondition::empty(), &[], &cache),
1572 Err(PackageJsonError::InvalidPackageTarget)
1573 ));
1574 assert!(matches!(
1575 pkg.resolve_package_exports("package", ExportsCondition::empty(), &[], &cache),
1576 Err(PackageJsonError::InvalidPackageTarget)
1577 ));
1578 assert!(matches!(
1579 pkg.resolve_package_exports("utils/index", ExportsCondition::empty(), &[], &cache),
1580 Err(PackageJsonError::InvalidPackageTarget)
1581 ));
1582 assert!(matches!(
1583 pkg.resolve_package_exports("dist/foo", ExportsCondition::empty(), &[], &cache),
1584 Err(PackageJsonError::InvalidPackageTarget)
1585 ));
1586 assert!(matches!(
1587 pkg.resolve_package_exports("modules/foo", ExportsCondition::empty(), &[], &cache),
1588 Err(PackageJsonError::InvalidPackageTarget)
1589 ));
1590 assert!(matches!(
1591 pkg.resolve_package_exports("a/b", ExportsCondition::empty(), &[], &cache),
1592 Err(PackageJsonError::PackagePathNotExported)
1593 ));
1594 assert!(matches!(
1595 pkg.resolve_package_exports("a/*", ExportsCondition::empty(), &[], &cache),
1596 Err(PackageJsonError::PackagePathNotExported)
1597 ));
1598
1599 let pkg = PackageJson::from_serialized(
1600 cache.get_normalized("/foo/package.json"),
1601 SerializedPackageJson {
1602 name: "foobar".into(),
1603 exports: ExportsField::Map(indexmap! {
1604 ".".into() => ExportsField::String("./foo.js".into()),
1605 "node".into() => ExportsField::String("./bar.js".into()),
1606 }),
1607 ..Default::default()
1608 },
1609 &cache,
1610 );
1611
1612 assert!(matches!(
1613 pkg.resolve_package_exports("", ExportsCondition::NODE, &[], &cache),
1614 Err(PackageJsonError::InvalidPackageTarget)
1615 ));
1616 assert!(matches!(
1617 pkg.resolve_package_exports("", ExportsCondition::NODE, &[], &cache),
1618 Err(PackageJsonError::InvalidPackageTarget)
1619 ));
1620 }
1621
1622 #[test]
1623 fn imports() {
1624 let cache = Cache::default();
1625 let pkg = PackageJson::from_serialized(
1626 cache.get_normalized("/foo/package.json"),
1627 SerializedPackageJson {
1628 name: "foobar".into(),
1629 imports: indexmap! {
1630 "#foo".into() => ExportsField::String("./foo.mjs".into()),
1631 "#internal/*".into() => ExportsField::String("./src/internal/*.mjs".into()),
1632 "#bar".into() => ExportsField::String("bar".into()),
1633 },
1634 ..Default::default()
1635 },
1636 &cache,
1637 );
1638
1639 assert_eq!(
1640 pkg
1641 .resolve_package_imports("foo", ExportsCondition::empty(), &[], &cache)
1642 .unwrap(),
1643 ExportsResolution::Path(cache.get_normalized("/foo/foo.mjs"))
1644 );
1645 assert_eq!(
1646 pkg
1647 .resolve_package_imports("internal/foo", ExportsCondition::empty(), &[], &cache)
1648 .unwrap(),
1649 ExportsResolution::Path(cache.get_normalized("/foo/src/internal/foo.mjs"))
1650 );
1651 assert_eq!(
1652 pkg
1653 .resolve_package_imports("bar", ExportsCondition::empty(), &[], &cache)
1654 .unwrap(),
1655 ExportsResolution::Package("bar".into())
1656 );
1657 }
1658
1659 #[test]
1660 fn import_conditions() {
1661 let cache = Cache::default();
1662 let pkg = PackageJson::from_serialized(
1663 cache.get_normalized("/foo/package.json"),
1664 SerializedPackageJson {
1665 name: "foobar".into(),
1666 imports: indexmap! {
1667 "#entry/*".into() => ExportsField::Map(indexmap! {
1668 "node".into() => ExportsField::String("./node/*.js".into()),
1669 "browser".into() => ExportsField::String("./browser/*.js".into())
1670 })
1671 },
1672 ..Default::default()
1673 },
1674 &cache,
1675 );
1676 assert_eq!(
1677 pkg
1678 .resolve_package_imports("entry/foo", ExportsCondition::NODE, &[], &cache)
1679 .unwrap(),
1680 ExportsResolution::Path(cache.get_normalized("/foo/node/foo.js"))
1681 );
1682 assert_eq!(
1683 pkg
1684 .resolve_package_imports("entry/foo", ExportsCondition::BROWSER, &[], &cache)
1685 .unwrap(),
1686 ExportsResolution::Path(cache.get_normalized("/foo/browser/foo.js"))
1687 );
1688 assert_eq!(
1689 pkg
1690 .resolve_package_imports(
1691 "entry/foo",
1692 ExportsCondition::NODE | ExportsCondition::BROWSER,
1693 &[],
1694 &cache
1695 )
1696 .unwrap(),
1697 ExportsResolution::Path(cache.get_normalized("/foo/node/foo.js"))
1698 );
1699 }
1700
1701 #[test]
1702 fn aliases() {
1703 let cache = Cache::default();
1704 let pkg = PackageJson::from_serialized(
1705 cache.get_normalized("/foo/package.json"),
1706 SerializedPackageJson {
1707 name: "foobar".into(),
1708 alias: indexmap! {
1709 "./foo.js".into() => AliasValue::Specifier("./foo-alias.js".into()),
1710 "bar".into() => AliasValue::Specifier("./bar-alias.js".into()),
1711 "lodash".into() => AliasValue::Specifier("my-lodash".into()),
1712 "lodash/clone".into() => AliasValue::Specifier("./clone.js".into()),
1713 "test".into() => AliasValue::Specifier("./test".into()),
1714 "foo/*".into() => AliasValue::Specifier("bar/$1".into()),
1715 "./foo/src/**".into() => AliasValue::Specifier("./foo/lib/$1".into()),
1716 "/foo/src/**".into() => AliasValue::Specifier("/foo/lib/$1".into()),
1717 "~/foo/src/**".into() => AliasValue::Specifier("~/foo/lib/$1".into()),
1718 "url".into() => AliasValue::Bool(false),
1719 "@internal/**".into() => AliasValue::Specifier("./internal/$1".into()),
1720 "@foo/*/bar/*".into() => AliasValue::Specifier("./test/$1/$2".into()),
1721 },
1722 ..Default::default()
1723 },
1724 &cache,
1725 );
1726
1727 assert_eq!(
1728 pkg.resolve_aliases(&"./foo.js".into(), Fields::ALIAS),
1729 Some(Cow::Owned(AliasValue::Specifier("./foo-alias.js".into())))
1730 );
1731 assert_eq!(
1732 pkg.resolve_aliases(&"bar".into(), Fields::ALIAS),
1733 Some(Cow::Owned(AliasValue::Specifier("./bar-alias.js".into())))
1734 );
1735 assert_eq!(
1736 pkg.resolve_aliases(&"lodash".into(), Fields::ALIAS),
1737 Some(Cow::Owned(AliasValue::Specifier("my-lodash".into())))
1738 );
1739 assert_eq!(
1740 pkg.resolve_aliases(&"lodash/foo".into(), Fields::ALIAS),
1741 Some(Cow::Owned(AliasValue::Specifier("my-lodash/foo".into())))
1742 );
1743 assert_eq!(
1744 pkg.resolve_aliases(&"lodash/clone".into(), Fields::ALIAS),
1745 Some(Cow::Owned(AliasValue::Specifier("./clone.js".into())))
1746 );
1747 assert_eq!(
1748 pkg.resolve_aliases(&"test".into(), Fields::ALIAS),
1749 Some(Cow::Owned(AliasValue::Specifier("./test".into())))
1750 );
1751 assert_eq!(
1752 pkg.resolve_aliases(&"test/foo".into(), Fields::ALIAS),
1753 Some(Cow::Owned(AliasValue::Specifier("./test/foo".into())))
1754 );
1755 assert_eq!(
1756 pkg.resolve_aliases(&"foo/hi".into(), Fields::ALIAS),
1757 Some(Cow::Owned(AliasValue::Specifier("bar/hi".into())))
1758 );
1759 assert_eq!(
1760 pkg.resolve_aliases(&"./foo/src/a/b".into(), Fields::ALIAS),
1761 Some(Cow::Owned(AliasValue::Specifier("./foo/lib/a/b".into())))
1762 );
1763 assert_eq!(
1764 pkg.resolve_aliases(&"/foo/src/a/b".into(), Fields::ALIAS),
1765 Some(Cow::Owned(AliasValue::Specifier("/foo/lib/a/b".into())))
1766 );
1767 assert_eq!(
1768 pkg.resolve_aliases(&"~/foo/src/a/b".into(), Fields::ALIAS),
1769 Some(Cow::Owned(AliasValue::Specifier("~/foo/lib/a/b".into())))
1770 );
1771 assert_eq!(
1772 pkg.resolve_aliases(&"url".into(), Fields::ALIAS),
1773 Some(Cow::Owned(AliasValue::Bool(false)))
1774 );
1775 assert_eq!(
1776 pkg.resolve_aliases(&"@internal/foo".into(), Fields::ALIAS),
1777 Some(Cow::Owned(AliasValue::Specifier("./internal/foo".into())))
1778 );
1779 assert_eq!(
1780 pkg.resolve_aliases(&"@internal/foo/bar".into(), Fields::ALIAS),
1781 Some(Cow::Owned(AliasValue::Specifier(
1782 "./internal/foo/bar".into()
1783 )))
1784 );
1785 assert_eq!(
1786 pkg.resolve_aliases(&"@foo/a/bar/b".into(), Fields::ALIAS),
1787 Some(Cow::Owned(AliasValue::Specifier("./test/a/b".into())))
1788 );
1789 }
1790
1791 #[allow(clippy::single_range_in_vec_init)]
1792 #[test]
1793 fn test_replace_captures() {
1794 assert_eq!(
1795 replace_captures("test/$1/$2", "foo/bar/baz", &vec![4..7, 8..11]),
1796 Cow::Borrowed("test/bar/baz")
1797 );
1798 assert_eq!(
1799 replace_captures("test/$1/$2", "foo/bar/baz", &vec![4..7]),
1800 Cow::Borrowed("test/bar/$2")
1801 );
1802 assert_eq!(
1803 replace_captures("test/$1/$2/$3", "foo/bar/baz", &vec![4..7, 8..11]),
1804 Cow::Borrowed("test/bar/baz/$3")
1805 );
1806 assert_eq!(
1807 replace_captures("test/$1/$2/$", "foo/bar/baz", &vec![4..7, 8..11]),
1808 Cow::Borrowed("test/bar/baz/$")
1809 );
1810 assert_eq!(
1811 replace_captures("te$st/$1/$2", "foo/bar/baz", &vec![4..7, 8..11]),
1812 Cow::Borrowed("te$st/bar/baz")
1813 );
1814 }
1815
1816 #[test]
1817 fn side_effects_none() {
1818 let cache = Cache::default();
1819 let pkg = PackageJson::from_serialized(
1820 cache.get_normalized("/foo/package.json"),
1821 SerializedPackageJson {
1822 name: "foobar".into(),
1823 ..Default::default()
1824 },
1825 &cache,
1826 );
1827
1828 assert!(pkg.has_side_effects(Path::new("/foo/index.js")));
1829 assert!(pkg.has_side_effects(Path::new("/foo/bar/index.js")));
1830 assert!(pkg.has_side_effects(Path::new("/index.js")));
1831 }
1832
1833 #[test]
1834 fn side_effects_bool() {
1835 let cache = Cache::default();
1836 let pkg = PackageJson::from_serialized(
1837 cache.get_normalized("/foo/package.json"),
1838 SerializedPackageJson {
1839 name: "foobar".into(),
1840 side_effects: SideEffects::Boolean(false),
1841 ..Default::default()
1842 },
1843 &cache,
1844 );
1845
1846 assert!(!pkg.has_side_effects(Path::new("/foo/index.js")));
1847 assert!(!pkg.has_side_effects(Path::new("/foo/bar/index.js")));
1848 assert!(pkg.has_side_effects(Path::new("/index.js")));
1849
1850 let pkg = PackageJson {
1851 side_effects: SideEffects::Boolean(true),
1852 ..pkg
1853 };
1854
1855 assert!(pkg.has_side_effects(Path::new("/foo/index.js")));
1856 assert!(pkg.has_side_effects(Path::new("/foo/bar/index.js")));
1857 assert!(pkg.has_side_effects(Path::new("/index.js")));
1858 }
1859
1860 #[test]
1861 fn side_effects_glob() {
1862 let cache = Cache::default();
1863 let pkg = PackageJson::from_serialized(
1864 cache.get_normalized("/foo/package.json"),
1865 SerializedPackageJson {
1866 name: "foobar".into(),
1867 side_effects: SideEffects::String("*.css".into()),
1868 ..Default::default()
1869 },
1870 &cache,
1871 );
1872
1873 assert!(pkg.has_side_effects(Path::new("/foo/a.css")));
1874 assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css")));
1875 assert!(pkg.has_side_effects(Path::new("/foo/bar/x/baz.css")));
1876 assert!(!pkg.has_side_effects(Path::new("/foo/a.js")));
1877 assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js")));
1878 assert!(pkg.has_side_effects(Path::new("/index.js")));
1879
1880 let pkg = PackageJson {
1881 side_effects: SideEffects::String("bar/*.css".into()),
1882 ..pkg
1883 };
1884
1885 assert!(!pkg.has_side_effects(Path::new("/foo/a.css")));
1886 assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css")));
1887 assert!(!pkg.has_side_effects(Path::new("/foo/bar/x/baz.css")));
1888 assert!(!pkg.has_side_effects(Path::new("/foo/a.js")));
1889 assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js")));
1890 assert!(pkg.has_side_effects(Path::new("/index.js")));
1891
1892 let pkg = PackageJson {
1893 side_effects: SideEffects::String("./bar/*.css".into()),
1894 ..pkg
1895 };
1896
1897 assert!(!pkg.has_side_effects(Path::new("/foo/a.css")));
1898 assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css")));
1899 assert!(!pkg.has_side_effects(Path::new("/foo/bar/x/baz.css")));
1900 assert!(!pkg.has_side_effects(Path::new("/foo/a.js")));
1901 assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js")));
1902 assert!(pkg.has_side_effects(Path::new("/index.js")));
1903 }
1904
1905 #[test]
1906 fn side_effects_array() {
1907 let cache = Cache::default();
1908 let pkg = PackageJson::from_serialized(
1909 cache.get_normalized("/foo/package.json"),
1910 SerializedPackageJson {
1911 name: "foobar".into(),
1912 side_effects: SideEffects::Array(vec!["*.css".into(), "*.html".into()]),
1913 ..Default::default()
1914 },
1915 &cache,
1916 );
1917
1918 assert!(pkg.has_side_effects(Path::new("/foo/a.css")));
1919 assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css")));
1920 assert!(pkg.has_side_effects(Path::new("/foo/bar/x/baz.css")));
1921 assert!(pkg.has_side_effects(Path::new("/foo/a.html")));
1922 assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.html")));
1923 assert!(pkg.has_side_effects(Path::new("/foo/bar/x/baz.html")));
1924 assert!(!pkg.has_side_effects(Path::new("/foo/a.js")));
1925 assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js")));
1926 assert!(pkg.has_side_effects(Path::new("/index.js")));
1927 }
1928
1929 #[test]
1930 fn parsing() {
1931 let pkg: SerializedPackageJson = serde_json::from_str(r#"{"type":"script"}"#).unwrap();
1932 assert_eq!(pkg.module_type, ModuleType::CommonJs);
1933 let pkg: SerializedPackageJson = serde_json::from_str(r#"{"name":"foo"}"#).unwrap();
1934 assert_eq!(pkg.module_type, ModuleType::CommonJs);
1935 }
1936}