feat(resolver): implement restrictions (path only) (#693)

This commit is contained in:
Boshen 2023-08-07 13:50:42 +08:00 committed by GitHub
parent 38fb4c296a
commit 59f5dc1906
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 3 deletions

View file

@ -29,7 +29,7 @@
| | resolveToContext | false | Resolve to a context instead of a file |
| ✅ | preferRelative | false | Prefer to resolve module requests as relative request and fallback to resolving as module |
| ✅ | preferAbsolute | false | Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots |
| | restrictions | [] | A list of resolve restrictions |
| | restrictions | [] | A list of resolve restrictions |
| ✅ | roots | [] | A list of root paths |
| ✅ | symlinks | true | Whether to resolve symlinks to their symlinked location |
| | unsafeCache | false | Use this cache object to unsafely cache the successful requests
@ -62,7 +62,7 @@ Crossed out test files are irrelevant.
- [ ] pnp.test.js
- [x] ~pr-53.test.js~
- [x] resolve.test.js (need to add resolveToContext)
- [ ] restrictions.test.js
- [x] restrictions.test.js (partially done, regex is not supported yet)
- [x] roots.test.js (need to add resolveToContext)
- [x] scoped-packages.test.js
- [x] simple.test.js

View file

@ -28,6 +28,9 @@ pub enum ResolveError {
/// JSON parse error
JSON(JSONError),
/// Restricted by `ResolveOptions::restrictions`
Restriction(PathBuf),
// TODO: TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "./dist/../../../a.js" specifier is not a valid subpath for the "exports" resolution of /xxx/package.json
InvalidModuleSpecifier(String),
@ -47,6 +50,8 @@ pub enum ResolveError {
InvalidPackageConfigDirectory(PathBuf),
PackageImportNotDefined(String),
Unimplemented(&'static str),
}
#[derive(Debug, Clone, Eq, PartialEq)]

View file

@ -46,7 +46,7 @@ use crate::{
pub use crate::{
error::{JSONError, ResolveError},
file_system::{FileMetadata, FileSystem},
options::{Alias, AliasValue, ResolveOptions},
options::{Alias, AliasValue, ResolveOptions, Restriction},
resolution::Resolution,
};
@ -138,6 +138,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
.and_then(|value| value.ok_or(err))
})?;
let path = self.load_realpath(&cached_path).unwrap_or_else(|| cached_path.to_path_buf());
// enhanced_resolve: restrictions
self.check_restrictions(&path)?;
let ctx = ctx.borrow();
Ok(Resolution {
path,
@ -361,6 +363,34 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
}
}
fn check_restrictions(&self, path: &Path) -> Result<(), ResolveError> {
// https://github.com/webpack/enhanced-resolve/blob/a998c7d218b7a9ec2461fc4fddd1ad5dd7687485/lib/RestrictionsPlugin.js#L19-L24
fn is_inside(path: &Path, parent: &Path) -> bool {
if !path.starts_with(parent) {
return false;
}
if path.as_os_str().len() == parent.as_os_str().len() {
return true;
}
path.strip_prefix(parent).is_ok_and(|p| p == Path::new("./"))
}
for restriction in &self.options.restrictions {
match restriction {
Restriction::Path(restricted_path) => {
if !is_inside(path, restricted_path) {
return Err(ResolveError::Restriction(path.to_path_buf()));
}
}
Restriction::RegExp(_) => {
return Err(ResolveError::Unimplemented(
"Restriction with regex is unimplemented.",
))
}
}
}
Ok(())
}
fn load_index(&self, cached_path: &CachedPath, ctx: &ResolveContext) -> ResolveState {
for main_file in &self.options.main_files {
let main_path = cached_path.path().join(main_file);

View file

@ -84,6 +84,11 @@ pub struct ResolveOptions {
/// Default `false`
pub prefer_absolute: bool,
/// A list of resolve restrictions to restrict the paths that a request can be resolved on.
///
/// Default `[]`
pub restrictions: Vec<Restriction>,
/// A list of directories where requests of server-relative URLs (starting with '/') are resolved.
/// On non-Windows systems these requests are resolved as an absolute path first.
///
@ -111,6 +116,12 @@ pub enum AliasValue {
Ignore,
}
#[derive(Debug, Clone)]
pub enum Restriction {
Path(PathBuf),
RegExp(String),
}
impl Default for ResolveOptions {
fn default() -> Self {
Self {
@ -127,6 +138,7 @@ impl Default for ResolveOptions {
modules: vec!["node_modules".into()],
prefer_relative: false,
prefer_absolute: false,
restrictions: vec![],
roots: vec![],
symlinks: true,
}

View file

@ -9,6 +9,7 @@ mod imports_field;
mod incorrect_description_file;
mod memory_fs;
mod resolve;
mod restrictions;
mod roots;
mod scoped_packages;
mod simple;

View file

@ -0,0 +1,25 @@
//! <https://github.com/webpack/enhanced-resolve/blob/main/test/restrictions.test.js>
use crate::{ResolveError, ResolveOptions, Resolver, Restriction};
// TODO: regex
// * should respect RegExp restriction
// * should try to find alternative #1
// * should try to find alternative #2
// * should try to find alternative #3
#[test]
// should respect string restriction
fn restriction1() {
let fixture = super::fixture();
let f = fixture.join("restrictions");
let resolver = Resolver::new(ResolveOptions {
extensions: vec![".js".into()],
restrictions: vec![Restriction::Path(f.clone())],
..ResolveOptions::default()
});
let resolution = resolver.resolve(&f, "pck2");
assert_eq!(resolution, Err(ResolveError::Restriction(fixture.join("c.js"))));
}