Skip to content

Normalize filepaths when parsing patterns from js values #80511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ opt-level = 3
[profile.release.package.serde]
opt-level = 3

# Use a custom profile for CI where many tests are performance sensitive but we still want the additional validation of debug-assertions
[profile.release-with-assertions]
inherits = "release"
debug-assertions = true
overflow-checks = true

[workspace.dependencies]
# Workspace crates
next-api = { path = "crates/next-api" }
Expand Down
2 changes: 1 addition & 1 deletion packages/next-swc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"rust-check-fmt": "cd ../..; cargo fmt -- --check",
"rust-check-clippy": "cargo clippy --workspace --all-targets -- -D warnings -A deprecated",
"rust-check-napi": "cargo check -p next-swc-napi",
"test-cargo-unit": "cargo nextest run --workspace --exclude next-swc-napi --exclude turbo-tasks-macros --release --no-fail-fast"
"test-cargo-unit": "cargo nextest run --workspace --exclude next-swc-napi --exclude turbo-tasks-macros --cargo-profile release-with-assertions --no-fail-fast"
},
"napi": {
"name": "next-swc",
Expand Down
13 changes: 3 additions & 10 deletions turbopack/crates/turbopack-core/src/resolve/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,17 +532,9 @@ impl Pattern {
/// Order into Alternatives -> Concatenation -> Constant/Dynamic
/// Merge when possible
pub fn normalize(&mut self) {
let mut alternatives = [Vec::new()];
match self {
Pattern::Constant(c) => {
for alt in alternatives.iter_mut() {
alt.push(Pattern::Constant(c.clone()));
}
}
Pattern::Dynamic => {
for alt in alternatives.iter_mut() {
alt.push(Pattern::Dynamic);
}
Pattern::Dynamic | Pattern::Constant(_) => {
// already normalized
}
Pattern::Alternatives(list) => {
for alt in list.iter_mut() {
Expand Down Expand Up @@ -630,6 +622,7 @@ impl Pattern {
})
.collect(),
);
// The recursive call will deduplicate the alternatives after simplifying them
self.normalize();
} else {
let mut new_parts = Vec::new();
Expand Down
7 changes: 7 additions & 0 deletions turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ impl ConstantString {
}
}

pub fn as_rcstr(&self) -> RcStr {
match self {
Self::Atom(s) => RcStr::from(s.as_str()),
Self::RcStr(s) => s.clone(),
}
}

pub fn as_atom(&self) -> Cow<Atom> {
match self {
Self::Atom(s) => Cow::Borrowed(s),
Expand Down
68 changes: 56 additions & 12 deletions turbopack/crates/turbopack-ecmascript/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use swc_core::{
visit::AstParentKind,
},
};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{NonLocalValue, trace::TraceRawVcs};
use turbopack_core::{chunk::ModuleId, resolve::pattern::Pattern};

Expand All @@ -24,36 +25,50 @@ pub fn unparen(expr: &Expr) -> &Expr {
expr
}

/// Converts a js-value into a Pattern for matching resources.
pub fn js_value_to_pattern(value: &JsValue) -> Pattern {
let mut result = match value {
match value {
JsValue::Constant(v) => Pattern::Constant(match v {
ConstantValue::Str(str) => str.as_str().into(),
ConstantValue::True => "true".into(),
ConstantValue::False => "false".into(),
ConstantValue::Null => "null".into(),
ConstantValue::Str(str) => {
// Normalize windows file paths when constructing the pattern.
// See PACK-3279
if str.as_str().contains("\\") {
RcStr::from(str.to_string().replace('\\', "/"))
} else {
str.as_rcstr()
}
}
ConstantValue::True => rcstr!("true"),
ConstantValue::False => rcstr!("false"),
ConstantValue::Null => rcstr!("null"),
ConstantValue::Num(ConstantNumber(n)) => n.to_string().into(),
ConstantValue::BigInt(n) => n.to_string().into(),
ConstantValue::Regex(box (exp, flags)) => format!("/{exp}/{flags}").into(),
ConstantValue::Undefined => "undefined".into(),
ConstantValue::Undefined => rcstr!("undefined"),
}),
JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_str().into()),
JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_rcstr()),
JsValue::Alternatives {
total_nodes: _,
values,
logical_property: _,
} => Pattern::Alternatives(values.iter().map(js_value_to_pattern).collect()),
} => {
let mut alts = Pattern::Alternatives(values.iter().map(js_value_to_pattern).collect());
alts.normalize();
alts
}
JsValue::Concat(_, parts) => {
Pattern::Concatenation(parts.iter().map(js_value_to_pattern).collect())
let mut concats =
Pattern::Concatenation(parts.iter().map(js_value_to_pattern).collect());
concats.normalize();
concats
}
JsValue::Add(..) => {
// TODO do we need to handle that here
// or is that already covered by normalization of JsValue
Pattern::Dynamic
}
_ => Pattern::Dynamic,
};
result.normalize();
result
}
}

const JS_MAX_SAFE_INTEGER: u64 = (1u64 << 53) - 1;
Expand Down Expand Up @@ -200,3 +215,32 @@ pub fn module_value_to_well_known_object(module_value: &ModuleValue) -> Option<J
_ => return None,
})
}

#[cfg(test)]
mod tests {
use turbo_rcstr::rcstr;
use turbopack_core::resolve::pattern::Pattern;

use crate::{
analyzer::{ConstantString, ConstantValue, JsValue},
utils::js_value_to_pattern,
};

#[test]
fn test_path_normalization_in_pattern() {
assert_eq!(
Pattern::Constant(rcstr!("hello/world")),
js_value_to_pattern(&JsValue::Constant(ConstantValue::Str(
ConstantString::RcStr(rcstr!("hello\\world"))
)))
);

assert_eq!(
Pattern::Constant(rcstr!("hello/world")),
js_value_to_pattern(&JsValue::Concat(
1,
vec!["hello".into(), "\\".into(), "world".into()]
))
);
}
}
9 changes: 2 additions & 7 deletions turbopack/crates/turbopack-tests/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ use serde::Deserialize;
use serde_json::json;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
ReadConsistency, ReadRef, ResolvedVc, TryJoinIterExt, TurboTasks, ValueToString, Vc,
apply_effects,
ReadConsistency, ReadRef, ResolvedVc, TurboTasks, ValueToString, Vc, apply_effects,
};
use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
use turbo_tasks_env::DotenvProcessEnv;
Expand Down Expand Up @@ -493,11 +492,7 @@ async fn walk_asset(
.await?
.iter()
.copied()
.map(|asset| async move { Ok(ResolvedVc::try_downcast::<Box<dyn OutputAsset>>(asset)) })
.try_join()
.await?
.into_iter()
.flatten(),
.flat_map(ResolvedVc::try_downcast::<Box<dyn OutputAsset>>),
);

Ok(())
Expand Down
Loading