From c32dd32bad67716c8bff3a513859deb5b31dd8fc Mon Sep 17 00:00:00 2001 From: Boshen Date: Tue, 18 Jul 2023 15:05:02 +0800 Subject: [PATCH] feat(resolver): implement enforceExtension (#566) --- crates/oxc_resolver/README.md | 2 +- crates/oxc_resolver/src/lib.rs | 2 +- crates/oxc_resolver/src/options.rs | 19 +++++++++-- .../tests/enhanced_resolve/test/extensions.rs | 33 +++++++++---------- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/crates/oxc_resolver/README.md b/crates/oxc_resolver/README.md index 1d705eee3..15563d910 100644 --- a/crates/oxc_resolver/README.md +++ b/crates/oxc_resolver/README.md @@ -16,7 +16,7 @@ | | cacheWithContext | true | If unsafe cache is enabled, includes `request.context` in the cache key | | | conditionNames | [] | A list of exports field condition names | | ✅ | descriptionFiles | ["package.json"] | A list of description files to read from | -| | enforceExtension | false | Enforce that a extension from extensions must be used | +| ✅ | enforceExtension | false | Enforce that a extension from extensions must be used | | | exportsFields | ["exports"] | A list of exports fields in description files | | ✅ | extensions | [".js", ".json", ".node"] | A list of extensions which should be tried for files | | | fallback | [] | Same as `alias`, but only used if default resolving fails | diff --git a/crates/oxc_resolver/src/lib.rs b/crates/oxc_resolver/src/lib.rs index 912c0cb65..92b111392 100644 --- a/crates/oxc_resolver/src/lib.rs +++ b/crates/oxc_resolver/src/lib.rs @@ -170,7 +170,7 @@ impl ResolverGeneric { fn load_index(&self, path: &Path) -> ResolveState { for main_field in &self.options.main_files { let main_path = path.join(main_field); - if !self.options.enforce_extension && self.cache.is_file(&main_path) { + if self.options.enforce_extension == Some(false) && self.cache.is_file(&main_path) { return Ok(Some(main_path)); } // 1. If X/index.js is a file, load X/index.js as JavaScript text. STOP diff --git a/crates/oxc_resolver/src/options.rs b/crates/oxc_resolver/src/options.rs index 874999b06..6ed1f6efd 100644 --- a/crates/oxc_resolver/src/options.rs +++ b/crates/oxc_resolver/src/options.rs @@ -25,8 +25,12 @@ pub struct ResolveOptions { /// Enforce that a extension from extensions must be used. /// - /// Default `false` - pub enforce_extension: bool, + /// Default to `true` when [ResolveOptions::extensions] contains an empty string. + /// Use `Some(false)` to disable the behavior. + /// See + /// + /// Default None, which is the same as `Some(false)` when the above empty rule is not applied. + pub enforce_extension: Option, /// An object which maps extension to extension aliases. /// @@ -55,7 +59,7 @@ impl Default for ResolveOptions { alias: vec![], alias_fields: vec![], description_files: vec!["package.json".into()], - enforce_extension: false, + enforce_extension: None, extension_alias: vec![], extensions: vec![".js".into(), ".json".into(), ".node".into()], main_files: vec!["index".into()], @@ -66,6 +70,15 @@ impl Default for ResolveOptions { impl ResolveOptions { pub(crate) fn sanitize(mut self) -> Self { + if self.enforce_extension.is_none() { + self.enforce_extension = Some(false); + // Set `enforceExtension` to `true` when [ResolveOptions::extensions] contains an empty string. + // See + if self.extensions.iter().any(String::is_empty) { + self.enforce_extension = Some(true); + self.extensions.retain(String::is_empty); + } + } self.extensions = Self::remove_leading_dots(self.extensions); self.extension_alias = self .extension_alias diff --git a/crates/oxc_resolver/tests/enhanced_resolve/test/extensions.rs b/crates/oxc_resolver/tests/enhanced_resolve/test/extensions.rs index cf9857520..d1bfd7388 100644 --- a/crates/oxc_resolver/tests/enhanced_resolve/test/extensions.rs +++ b/crates/oxc_resolver/tests/enhanced_resolve/test/extensions.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; -use oxc_resolver::{ResolveError, ResolveOptions, Resolver}; +use oxc_resolver::{Resolution, ResolveError, ResolveOptions, Resolver}; fn fixture() -> PathBuf { super::fixture().join("extensions") @@ -48,32 +48,31 @@ fn extensions() -> Result<(), ResolveError> { } #[test] -#[ignore = "need to match missingDependencies returned from the resolve function"] +// should default enforceExtension to true when extensions includes an empty string fn default_enforce_extension() { - // should default enforceExtension to true when extensions includes an empty string - let fixture = fixture(); + let f = fixture(); - let options = ResolveOptions { + let resolved = Resolver::new(ResolveOptions { extensions: vec![".ts".into(), String::new(), ".js".into()], ..ResolveOptions::default() - }; + }) + .resolve(&f, "./foo"); - let resolver = Resolver::new(options); - let _resolved = resolver.resolve(fixture, "./foo"); + assert_eq!(resolved, Err(ResolveError::NotFound(f.join("foo").into_boxed_path()))); + // TODO: need to match missingDependencies returned from the resolve function } #[test] -#[ignore = "need to match missingDependencies returned from the resolve function"] +// should respect enforceExtension when extensions includes an empty string fn respect_enforce_extension() { - // should respect enforceExtension when extensions includes an empty string - let fixture = fixture(); + let f = fixture(); - let options = ResolveOptions { - enforce_extension: false, + let resolved = Resolver::new(ResolveOptions { + enforce_extension: Some(false), extensions: vec![".ts".into(), String::new(), ".js".into()], ..ResolveOptions::default() - }; - - let resolver = Resolver::new(options); - let _resolved = resolver.resolve(fixture, "./foo"); + }) + .resolve(&f, "./foo"); + assert_eq!(resolved.map(Resolution::into_path_buf), Ok(f.join("foo.ts"))); + // TODO: need to match missingDependencies returned from the resolve function }