diff --git a/crates/next-api/src/server_actions.rs b/crates/next-api/src/server_actions.rs index 34552cf3564c4..32aadcbffc822 100644 --- a/crates/next-api/src/server_actions.rs +++ b/crates/next-api/src/server_actions.rs @@ -213,7 +213,7 @@ pub async fn to_rsc_context( // module. let source = FileSource::new_with_query( client_module.ident().path().root().join(entry_path.into()), - Vc::cell(entry_query.into()), + entry_query.into(), ); let module = asset_context .process( diff --git a/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_transition.rs b/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_transition.rs index f01802804f734..a782250d8e3d3 100644 --- a/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_transition.rs +++ b/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_transition.rs @@ -71,7 +71,11 @@ impl Transition for NextEcmascriptClientReferenceTransition { .replace("next/dist/esm/", "next/dist/") .into(), ); - Vc::upcast(FileSource::new_with_query(path, *ident_ref.query)) + Vc::upcast(FileSource::new_with_query_and_fragment( + path, + (*ident_ref.query.await?).clone(), + (*ident_ref.fragment.await?).clone(), + )) } else { source }; diff --git a/turbopack/crates/turbopack-core/src/file_source.rs b/turbopack/crates/turbopack-core/src/file_source.rs index c196d8221c22e..1b43e7b99316e 100644 --- a/turbopack/crates/turbopack-core/src/file_source.rs +++ b/turbopack/crates/turbopack-core/src/file_source.rs @@ -13,38 +13,48 @@ use crate::{ /// references to other [Source]s. #[turbo_tasks::value] pub struct FileSource { - pub path: ResolvedVc, - pub query: ResolvedVc, + path: ResolvedVc, + query: RcStr, + fragment: RcStr, +} + +impl FileSource { + pub fn new(path: Vc) -> Vc { + FileSource::new_with_query_and_fragment(path, RcStr::default(), RcStr::default()) + } + pub fn new_with_query(path: Vc, query: RcStr) -> Vc { + FileSource::new_with_query_and_fragment(path, query, RcStr::default()) + } } #[turbo_tasks::value_impl] impl FileSource { #[turbo_tasks::function] - pub fn new(path: ResolvedVc) -> Vc { + pub fn new_with_query_and_fragment( + path: ResolvedVc, + query: RcStr, + fragment: RcStr, + ) -> Vc { Self::cell(FileSource { path, - query: ResolvedVc::cell(RcStr::default()), + query, + fragment, }) } - - #[turbo_tasks::function] - pub async fn new_with_query( - path: ResolvedVc, - query: ResolvedVc, - ) -> Result> { - if query.await?.is_empty() { - Ok(Self::new(*path)) - } else { - Ok(Self::cell(FileSource { path, query })) - } - } } #[turbo_tasks::value_impl] impl Source for FileSource { #[turbo_tasks::function] fn ident(&self) -> Vc { - AssetIdent::from_path(*self.path).with_query(*self.query) + let mut ident = AssetIdent::from_path(*self.path); + if !self.query.is_empty() { + ident = ident.with_query(Vc::cell(self.query.clone())); + } + if !self.fragment.is_empty() { + ident = ident.with_fragment(Vc::cell(self.fragment.clone())); + } + ident } } diff --git a/turbopack/crates/turbopack-core/src/ident.rs b/turbopack/crates/turbopack-core/src/ident.rs index 8267e7e4c0fd6..09dd709fe4b66 100644 --- a/turbopack/crates/turbopack-core/src/ident.rs +++ b/turbopack/crates/turbopack-core/src/ident.rs @@ -15,10 +15,12 @@ use crate::resolve::ModulePart; pub struct AssetIdent { /// The primary path of the asset pub path: ResolvedVc, - /// The query string of the asset (e.g. `?foo=bar`) + /// The query string of the asset this is either the empty string or a query string that starts + /// with a `?` (e.g. `?foo=bar`) pub query: ResolvedVc, - /// The fragment of the asset (e.g. `#foo`) - pub fragment: Option>, + /// The fragment of the asset, this is either the empty string or a fragment string that starts + /// with a `#` (e.g. `#foo`) + pub fragment: ResolvedVc, /// The assets that are nested in this asset pub assets: Vec<(ResolvedVc, ResolvedVc)>, /// The modifiers of this asset (e.g. `client chunks`) @@ -57,14 +59,10 @@ impl ValueToString for AssetIdent { async fn to_string(&self) -> Result> { let mut s = self.path.to_string().owned().await?.into_owned(); - let query = self.query.await?; - if !query.is_empty() { - write!(s, "?{}", &*query)?; - } - - if let Some(fragment) = &self.fragment { - write!(s, "#{}", fragment.await?)?; - } + // The query string is either empty or non-empty starting with `?` so we can just concat + s.push_str(&self.query.await?); + // ditto for fragment + s.push_str(&self.fragment.await?); if !self.assets.is_empty() { s.push_str(" {"); @@ -121,8 +119,19 @@ impl ValueToString for AssetIdent { #[turbo_tasks::value_impl] impl AssetIdent { #[turbo_tasks::function] - pub fn new(ident: Value) -> Vc { - ident.into_value().cell() + pub async fn new(ident: Value) -> Result> { + let query = &*ident.query.await?; + // TODO(lukesandberg); downgrade to debug_assert + assert!( + query.is_empty() || query.starts_with("?"), + "query should be empty or start with a `?`" + ); + let fragment = &*ident.fragment.await?; + assert!( + fragment.is_empty() || fragment.starts_with("#"), + "query should be empty or start with a `?`" + ); + Ok(ident.into_value().cell()) } /// Creates an [AssetIdent] from a [Vc] @@ -131,7 +140,7 @@ impl AssetIdent { Self::new(Value::new(AssetIdent { path, query: ResolvedVc::cell(RcStr::default()), - fragment: None, + fragment: ResolvedVc::cell(RcStr::default()), assets: Vec::new(), modifiers: Vec::new(), parts: Vec::new(), @@ -147,6 +156,13 @@ impl AssetIdent { Self::new(Value::new(this)) } + #[turbo_tasks::function] + pub fn with_fragment(&self, fragment: ResolvedVc) -> Vc { + let mut this = self.clone(); + this.fragment = fragment; + Self::new(Value::new(this)) + } + #[turbo_tasks::function] pub fn with_modifier(&self, modifier: ResolvedVc) -> Vc { let mut this = self.clone(); @@ -252,9 +268,10 @@ impl AssetIdent { query.deterministic_hash(&mut hasher); has_hash = true; } - if let Some(fragment) = fragment { + let fragment = fragment.await?; + if !fragment.is_empty() { 1_u8.deterministic_hash(&mut hasher); - fragment.await?.deterministic_hash(&mut hasher); + fragment.deterministic_hash(&mut hasher); has_hash = true; } for (key, ident) in assets.iter() { diff --git a/turbopack/crates/turbopack-core/src/resolve/mod.rs b/turbopack/crates/turbopack-core/src/resolve/mod.rs index cb3c01d9b947c..0ba4790563ade 100644 --- a/turbopack/crates/turbopack-core/src/resolve/mod.rs +++ b/turbopack/crates/turbopack-core/src/resolve/mod.rs @@ -2203,18 +2203,17 @@ async fn resolve_relative_request( let fragment_val = fragment.await?; if !fragment_val.is_empty() { - new_path.push(Pattern::Alternatives( - once(Pattern::Constant("".into())) - .chain(once(Pattern::Constant(format!("#{fragment_val}").into()))) - .collect(), - )); + new_path.push(Pattern::Alternatives(vec![ + Pattern::Constant(RcStr::default()), + Pattern::Constant((*fragment_val).clone()), + ])); } if !options_value.fully_specified { // Add the extensions as alternatives to the path // read_matches keeps the order of alternatives intact new_path.push(Pattern::Alternatives( - once(Pattern::Constant("".into())) + once(Pattern::Constant(RcStr::default())) .chain( options_value .extensions @@ -2328,10 +2327,7 @@ async fn resolve_relative_request( } if !fragment_val.is_empty() { // If the fragment is not empty, we need to strip it from the matched pattern - if let Some(matched_pattern) = matched_pattern - .strip_suffix(&**fragment_val) - .and_then(|s| s.strip_suffix('#')) - { + if let Some(matched_pattern) = matched_pattern.strip_suffix(&**fragment_val) { results.push( resolved( RequestKey::new(matched_pattern.into()), @@ -2864,9 +2860,13 @@ async fn resolved( Ok(*ResolveResult::source_with_affecting_sources( request_key, ResolvedVc::upcast( - FileSource::new_with_query(**path, query) - .to_resolved() - .await?, + FileSource::new_with_query_and_fragment( + **path, + (*query.await?).clone(), + (*fragment.await?).clone(), + ) + .to_resolved() + .await?, ), symlinks .iter() diff --git a/turbopack/crates/turbopack-core/src/resolve/parse.rs b/turbopack/crates/turbopack-core/src/resolve/parse.rs index ced30156b315e..deb00a6347ff9 100644 --- a/turbopack/crates/turbopack-core/src/resolve/parse.rs +++ b/turbopack/crates/turbopack-core/src/resolve/parse.rs @@ -61,30 +61,32 @@ pub enum Request { }, } -fn split_off_query_fragment(raw: RcStr) -> (Pattern, Vc, Vc) { - let Some((raw, query)) = raw.split_once('?') else { - if let Some((raw, fragment)) = raw.split_once('#') { - return ( - Pattern::Constant(raw.into()), - Vc::::default(), - Vc::cell(fragment.into()), - ); +/// Splits a string like `foo?bar#baz` into `(Pattern::Constant('foo'), '?bar', '#baz')` +/// +/// If the hash or query portion are missing they will be empty strings otherwise they will be +/// non-empty along with their prepender characters +fn split_off_query_fragment(mut raw: &str) -> (Pattern, RcStr, RcStr) { + // Per the URI spec fragments can contain `?` characters, so we should trim it off first + // https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + + let hash = match raw.as_bytes().iter().position(|&b| b == b'#') { + Some(pos) => { + let (prefix, hash) = raw.split_at(pos); + raw = prefix; + RcStr::from(hash) } - - return ( - Pattern::Constant(raw), - Vc::::default(), - Vc::::default(), - ); + None => RcStr::default(), }; - let (query, fragment) = query.split_once('#').unwrap_or((query, "")); - - ( - Pattern::Constant(raw.into()), - Vc::cell(format!("?{query}").into()), - Vc::cell(format!("#{fragment}").into()), - ) + let query = match raw.as_bytes().iter().position(|&b| b == b'?') { + Some(pos) => { + let (prefix, query) = raw.split_at(pos); + raw = prefix; + RcStr::from(query) + } + None => RcStr::default(), + }; + (Pattern::Constant(RcStr::from(raw)), query, hash) } lazy_static! { @@ -164,12 +166,12 @@ impl Request { } if r.starts_with('/') { - let (path, query, fragment) = split_off_query_fragment(r); + let (path, query, fragment) = split_off_query_fragment(&r); return Ok(Request::ServerRelative { path, - query: query.to_resolved().await?, - fragment: fragment.to_resolved().await?, + query: ResolvedVc::cell(query), + fragment: ResolvedVc::cell(fragment), }); } @@ -180,23 +182,23 @@ impl Request { } if r.starts_with("./") || r.starts_with("../") || r == "." || r == ".." { - let (path, query, fragment) = split_off_query_fragment(r); + let (path, query, fragment) = split_off_query_fragment(&r); return Ok(Request::Relative { path, force_in_lookup_dir: false, - query: query.to_resolved().await?, - fragment: fragment.to_resolved().await?, + query: ResolvedVc::cell(query), + fragment: ResolvedVc::cell(fragment), }); } if WINDOWS_PATH.is_match(&r) { - let (path, query, fragment) = split_off_query_fragment(r); + let (path, query, fragment) = split_off_query_fragment(&r); return Ok(Request::Windows { path, - query: query.to_resolved().await?, - fragment: fragment.to_resolved().await?, + query: ResolvedVc::cell(query), + fragment: ResolvedVc::cell(fragment), }); } @@ -227,13 +229,13 @@ impl Request { .captures(&r) .and_then(|caps| caps.get(1).zip(caps.get(2))) { - let (path, query, fragment) = split_off_query_fragment(path.as_str().into()); + let (path, query, fragment) = split_off_query_fragment(path.as_str()); return Ok(Request::Module { module: module.as_str().into(), path, - query: query.to_resolved().await?, - fragment: fragment.to_resolved().await?, + query: ResolvedVc::cell(query), + fragment: ResolvedVc::cell(fragment), }); } @@ -817,3 +819,63 @@ pub async fn stringify_data_uri( data.await? )) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_split_query_fragment() { + assert_eq!( + ( + Pattern::Constant("foo".into()), + RcStr::default(), + RcStr::default() + ), + split_off_query_fragment("foo") + ); + // These two cases are a bit odd, but it is important to treat `import './foo?'` differently + // from `import './foo'`, ditto for fragments. + assert_eq!( + ( + Pattern::Constant("foo".into()), + RcStr::from("?"), + RcStr::default() + ), + split_off_query_fragment("foo?") + ); + assert_eq!( + ( + Pattern::Constant("foo".into()), + RcStr::default(), + RcStr::from("#") + ), + split_off_query_fragment("foo#") + ); + assert_eq!( + ( + Pattern::Constant("foo".into()), + RcStr::from("?bar=baz"), + RcStr::default() + ), + split_off_query_fragment("foo?bar=baz") + ); + assert_eq!( + ( + Pattern::Constant("foo".into()), + RcStr::default(), + RcStr::from("#stuff?bar=baz") + ), + split_off_query_fragment("foo#stuff?bar=baz") + ); + + assert_eq!( + ( + Pattern::Constant("foo".into()), + RcStr::from("?bar=baz"), + RcStr::from("#stuff") + ), + split_off_query_fragment("foo?bar=baz#stuff") + ); + } +} diff --git a/turbopack/crates/turbopack-css/src/chunk/mod.rs b/turbopack/crates/turbopack-css/src/chunk/mod.rs index 669dc77d44799..64f8b8c01e1b6 100644 --- a/turbopack/crates/turbopack-css/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-css/src/chunk/mod.rs @@ -194,7 +194,7 @@ impl CssChunk { ServerFileSystem::new().root().to_resolved().await? }, query: ResolvedVc::cell(RcStr::default()), - fragment: None, + fragment: ResolvedVc::cell(RcStr::default()), assets, modifiers: Vec::new(), parts: Vec::new(), diff --git a/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs b/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs index 8da0601da65a6..38a4d98d57d7d 100644 --- a/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs @@ -123,7 +123,7 @@ impl Chunk for EcmascriptChunk { ServerFileSystem::new().root().to_resolved().await? }, query: ResolvedVc::cell(RcStr::default()), - fragment: None, + fragment: ResolvedVc::cell(RcStr::default()), assets, modifiers: Vec::new(), parts: Vec::new(), diff --git a/turbopack/crates/turbopack/src/module_options/rule_condition.rs b/turbopack/crates/turbopack/src/module_options/rule_condition.rs index b2d1fb573f30c..3885dd6f33390 100644 --- a/turbopack/crates/turbopack/src/module_options/rule_condition.rs +++ b/turbopack/crates/turbopack/src/module_options/rule_condition.rs @@ -222,7 +222,7 @@ impl RuleCondition { } #[cfg(test)] -mod tests { +pub mod tests { use turbo_tasks::Vc; use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage}; use turbo_tasks_fs::{FileContent, FileSystem, VirtualFileSystem}; @@ -237,144 +237,146 @@ mod tests { BackendOptions::default(), noop_backing_storage(), )); - tt.run_once(async { - let fs = VirtualFileSystem::new(); - let virtual_path = fs.root().join("foo.js".into()); - let virtual_source = Vc::upcast::>(VirtualSource::new( - virtual_path, - AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(), - )) + tt.run_once(async { run_leaves_test().await }) + .await + .unwrap(); + } + + #[turbo_tasks::function] + pub async fn run_leaves_test() -> Result<()> { + let fs = VirtualFileSystem::new(); + let virtual_path = fs.root().join("foo.js".into()); + let virtual_source = Vc::upcast::>(VirtualSource::new( + virtual_path, + AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(), + )) + .to_resolved() + .await?; + + let non_virtual_path = fs.root().join("bar.js".into()); + let non_virtual_source = Vc::upcast::>(FileSource::new(non_virtual_path)) .to_resolved() .await?; - let non_virtual_path = fs.root().join("bar.js".into()); - let non_virtual_source = - Vc::upcast::>(FileSource::new(non_virtual_path)) - .to_resolved() - .await?; - - { - let condition = RuleCondition::ReferenceType(ReferenceType::Runtime); - assert!( - condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Runtime - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Css( - turbopack_core::reference_type::CssReferenceSubType::Compose - ) + { + let condition = RuleCondition::ReferenceType(ReferenceType::Runtime); + assert!( + condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Runtime + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Css( + turbopack_core::reference_type::CssReferenceSubType::Compose ) - .await - .unwrap() - ); - } + ) + .await + .unwrap() + ); + } - { - let condition = RuleCondition::ResourceIsVirtualSource; - assert!( - condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - { - let condition = RuleCondition::ResourcePathEquals(virtual_path.await?); - assert!( - condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - { - let condition = RuleCondition::ResourcePathHasNoExtension; - assert!( - condition - .matches( - virtual_source, - &*fs.root().join("foo".into()).await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - { - let condition = RuleCondition::ResourcePathEndsWith("foo.js".to_string()); - assert!( - condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - anyhow::Ok(()) - }) - .await - .unwrap(); + { + let condition = RuleCondition::ResourceIsVirtualSource; + assert!( + condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + { + let condition = RuleCondition::ResourcePathEquals(virtual_path.await?); + assert!( + condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + { + let condition = RuleCondition::ResourcePathHasNoExtension; + assert!( + condition + .matches( + virtual_source, + &*fs.root().join("foo".into()).await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + { + let condition = RuleCondition::ResourcePathEndsWith("foo.js".to_string()); + assert!( + condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + anyhow::Ok(()) } #[tokio::test] @@ -384,141 +386,143 @@ mod tests { BackendOptions::default(), noop_backing_storage(), )); - tt.run_once(async { - let fs = VirtualFileSystem::new(); - let virtual_path = fs.root().join("foo.js".into()); - let virtual_source = Vc::upcast::>(VirtualSource::new( - virtual_path, - AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(), - )) + tt.run_once(async { run_rule_condition_tree_test().await }) + .await + .unwrap(); + } + + #[turbo_tasks::function] + pub async fn run_rule_condition_tree_test() -> Result<()> { + let fs = VirtualFileSystem::new(); + let virtual_path = fs.root().join("foo.js".into()); + let virtual_source = Vc::upcast::>(VirtualSource::new( + virtual_path, + AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(), + )) + .to_resolved() + .await?; + + let non_virtual_path = fs.root().join("bar.js".into()); + let non_virtual_source = Vc::upcast::>(FileSource::new(non_virtual_path)) .to_resolved() .await?; - let non_virtual_path = fs.root().join("bar.js".into()); - let non_virtual_source = - Vc::upcast::>(FileSource::new(non_virtual_path)) - .to_resolved() - .await?; + { + // not + let condition = RuleCondition::not(RuleCondition::ResourceIsVirtualSource); + assert!( + !condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + { + // any + // Only one of the conditions matches our virtual source + let condition = RuleCondition::any(vec![ + RuleCondition::ResourcePathInDirectory("doesnt/exist".to_string()), + RuleCondition::ResourceIsVirtualSource, + RuleCondition::ResourcePathHasNoExtension, + ]); + assert!( + condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + { + // all + // Only one of the conditions matches our virtual source + let condition = RuleCondition::all(vec![ + RuleCondition::ResourcePathEndsWith("foo.js".to_string()), + RuleCondition::ResourceIsVirtualSource, + RuleCondition::ResourcePathEquals(virtual_path.await?), + ]); + assert!( + condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + { + // bigger tree - { - // not - let condition = RuleCondition::not(RuleCondition::ResourceIsVirtualSource); - assert!( - !condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - { - // any - // Only one of the conditions matches our virtual source - let condition = RuleCondition::any(vec![ - RuleCondition::ResourcePathInDirectory("doesnt/exist".to_string()), - RuleCondition::ResourceIsVirtualSource, - RuleCondition::ResourcePathHasNoExtension, - ]); - assert!( - condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - { - // all - // Only one of the conditions matches our virtual source - let condition = RuleCondition::all(vec![ + // Build a simple tree to cover our various composite conditions + let condition = RuleCondition::all(vec![ + RuleCondition::ResourceIsVirtualSource, + RuleCondition::ResourcePathEquals(virtual_path.await?), + RuleCondition::Not(Box::new(RuleCondition::ResourcePathHasNoExtension)), + RuleCondition::Any(vec![ RuleCondition::ResourcePathEndsWith("foo.js".to_string()), - RuleCondition::ResourceIsVirtualSource, - RuleCondition::ResourcePathEquals(virtual_path.await?), - ]); - assert!( - condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - { - // bigger tree - - // Build a simple tree to cover our various composite conditions - let condition = RuleCondition::all(vec![ - RuleCondition::ResourceIsVirtualSource, - RuleCondition::ResourcePathEquals(virtual_path.await?), - RuleCondition::Not(Box::new(RuleCondition::ResourcePathHasNoExtension)), - RuleCondition::Any(vec![ - RuleCondition::ResourcePathEndsWith("foo.js".to_string()), - RuleCondition::ContentTypeEmpty, - ]), - ]); - assert!( - condition - .matches( - virtual_source, - &*virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - assert!( - !condition - .matches( - non_virtual_source, - &*non_virtual_path.await?, - &ReferenceType::Undefined - ) - .await - .unwrap() - ); - } - anyhow::Ok(()) - }) - .await - .unwrap(); + RuleCondition::ContentTypeEmpty, + ]), + ]); + assert!( + condition + .matches( + virtual_source, + &*virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + assert!( + !condition + .matches( + non_virtual_source, + &*non_virtual_path.await?, + &ReferenceType::Undefined + ) + .await + .unwrap() + ); + } + anyhow::Ok(()) } }