From f3aa1aa01cd6867032966cec257e696a543851e2 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 13 Nov 2025 15:41:31 -0800 Subject: [PATCH 1/8] Add a debug assertion that we do not produce duplicate code gens. --- .../crates/turbopack-ecmascript/src/code_gen.rs | 4 +++- .../turbopack-ecmascript/src/references/amd.rs | 6 +++++- .../turbopack-ecmascript/src/references/cjs.rs | 12 +++++++++--- .../src/references/constant_condition.rs | 4 +++- .../src/references/dynamic_expression.rs | 8 ++++++-- .../src/references/esm/dynamic.rs | 4 +++- .../turbopack-ecmascript/src/references/esm/meta.rs | 8 ++++++-- .../src/references/esm/module_id.rs | 4 +++- .../src/references/esm/module_item.rs | 4 +++- .../turbopack-ecmascript/src/references/esm/url.rs | 4 +++- .../turbopack-ecmascript/src/references/ident.rs | 4 +++- .../turbopack-ecmascript/src/references/member.rs | 4 +++- .../turbopack-ecmascript/src/references/mod.rs | 13 +++++++++---- .../src/references/require_context.rs | 4 +++- .../src/references/unreachable.rs | 4 +++- .../turbopack-ecmascript/src/references/worker.rs | 4 +++- turbopack/crates/turbopack-ecmascript/src/utils.rs | 2 +- 17 files changed, 69 insertions(+), 24 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/code_gen.rs b/turbopack/crates/turbopack-ecmascript/src/code_gen.rs index a0518805682ff..2af172fb6e00d 100644 --- a/turbopack/crates/turbopack-ecmascript/src/code_gen.rs +++ b/turbopack/crates/turbopack-ecmascript/src/code_gen.rs @@ -173,7 +173,9 @@ impl_modify!(visit_mut_block_stmt, BlockStmt); impl_modify!(visit_mut_switch_case, SwitchCase); impl_modify!(visit_mut_program, Program); -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub enum CodeGen { // AMD occurs very rarely and makes the enum much bigger AmdDefineWithDependenciesCodeGen(Box), diff --git a/turbopack/crates/turbopack-ecmascript/src/references/amd.rs b/turbopack/crates/turbopack-ecmascript/src/references/amd.rs index b6de7493329e5..ccaa1136b3e57 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/amd.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/amd.rs @@ -98,6 +98,7 @@ impl ChunkableModuleReference for AmdDefineAssetReference {} TraceRawVcs, Clone, NonLocalValue, + Hash, )] pub enum AmdDefineDependencyElement { Request { @@ -120,6 +121,7 @@ pub enum AmdDefineDependencyElement { Copy, Clone, NonLocalValue, + Hash, )] pub enum AmdDefineFactoryType { Unknown, @@ -127,7 +129,9 @@ pub enum AmdDefineFactoryType { Value, } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct AmdDefineWithDependenciesCodeGen { dependencies_requests: Vec, origin: ResolvedVc>, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/cjs.rs b/turbopack/crates/turbopack-ecmascript/src/references/cjs.rs index 1e6c8d255639d..d22a482133973 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/cjs.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/cjs.rs @@ -150,7 +150,9 @@ impl IntoCodeGenReference for CjsRequireAssetReference { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct CjsRequireAssetReferenceCodeGen { reference: ResolvedVc, path: AstPath, @@ -274,7 +276,9 @@ impl IntoCodeGenReference for CjsRequireResolveAssetReference { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct CjsRequireResolveAssetReferenceCodeGen { reference: ResolvedVc, path: AstPath, @@ -334,7 +338,9 @@ impl CjsRequireResolveAssetReferenceCodeGen { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, +)] pub struct CjsRequireCacheAccess { pub path: AstPath, } diff --git a/turbopack/crates/turbopack-ecmascript/src/references/constant_condition.rs b/turbopack/crates/turbopack-ecmascript/src/references/constant_condition.rs index 12d8a10baba5e..dc6fd8b57faca 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/constant_condition.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/constant_condition.rs @@ -19,7 +19,9 @@ pub enum ConstantConditionValue { Nullish, } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, +)] pub struct ConstantConditionCodeGen { value: ConstantConditionValue, path: AstPath, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/dynamic_expression.rs b/turbopack/crates/turbopack-ecmascript/src/references/dynamic_expression.rs index 02e5dba56d187..45fea8527893b 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/dynamic_expression.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/dynamic_expression.rs @@ -10,13 +10,17 @@ use crate::{ create_visitor, }; -#[derive(PartialEq, Eq, TraceRawVcs, Serialize, Deserialize, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, TraceRawVcs, Serialize, Deserialize, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] enum DynamicExpressionType { Promise, Normal, } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, +)] pub struct DynamicExpression { path: AstPath, ty: DynamicExpressionType, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/esm/dynamic.rs b/turbopack/crates/turbopack-ecmascript/src/references/esm/dynamic.rs index b77e5a82d06ba..33f438f3ed423 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/esm/dynamic.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/esm/dynamic.rs @@ -121,7 +121,9 @@ impl IntoCodeGenReference for EsmAsyncAssetReference { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct EsmAsyncAssetReferenceCodeGen { path: AstPath, reference: ResolvedVc, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/esm/meta.rs b/turbopack/crates/turbopack-ecmascript/src/references/esm/meta.rs index 88407cb45de2d..8b516d39e7e76 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/esm/meta.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/esm/meta.rs @@ -26,7 +26,9 @@ use crate::{ /// in the file. But we must only initialize the binding a single time. /// /// This singleton behavior must be enforced by the caller! -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, +)] pub struct ImportMetaBinding { path: FileSystemPath, } @@ -85,7 +87,9 @@ impl From for CodeGen { /// /// There can be many references to import.meta, and they appear at any nesting /// in the file. But all references refer to the same mutable object. -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct ImportMetaRef { ast_path: AstPath, } diff --git a/turbopack/crates/turbopack-ecmascript/src/references/esm/module_id.rs b/turbopack/crates/turbopack-ecmascript/src/references/esm/module_id.rs index 4e112856cbeb9..a5f69e967d526 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/esm/module_id.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/esm/module_id.rs @@ -73,7 +73,9 @@ impl IntoCodeGenReference for EsmModuleIdAssetReference { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct EsmModuleIdAssetReferenceCodeGen { path: AstPath, reference: ResolvedVc, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/esm/module_item.rs b/turbopack/crates/turbopack-ecmascript/src/references/esm/module_item.rs index 752c42bbe1f3c..67dc999ca5571 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/esm/module_item.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/esm/module_item.rs @@ -22,7 +22,9 @@ use crate::{ /// Makes code changes to remove export/import declarations and places the /// expr/decl in a normal statement. Unnamed expr/decl will be named with the /// magic identifier "export default" -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, +)] pub struct EsmModuleItem { pub path: AstPath, } diff --git a/turbopack/crates/turbopack-ecmascript/src/references/esm/url.rs b/turbopack/crates/turbopack-ecmascript/src/references/esm/url.rs index 8149229bff154..18a9a8bfa2856 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/esm/url.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/esm/url.rs @@ -146,7 +146,9 @@ impl IntoCodeGenReference for UrlAssetReference { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct UrlAssetReferenceCodeGen { reference: ResolvedVc, path: AstPath, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/ident.rs b/turbopack/crates/turbopack-ecmascript/src/references/ident.rs index 5920933589944..8e6167e87b445 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/ident.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/ident.rs @@ -11,7 +11,9 @@ use crate::{ create_visitor, }; -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, +)] pub struct IdentReplacement { value: RcStr, path: AstPath, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/member.rs b/turbopack/crates/turbopack-ecmascript/src/references/member.rs index f96232d35e58d..d78141970e210 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/member.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/member.rs @@ -20,7 +20,9 @@ use crate::{ create_visitor, }; -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct MemberReplacement { key: RcStr, value: RcStr, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index 3a7a0535fa49c..a1c2c5a7191fd 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -226,7 +226,7 @@ pub struct AnalyzeEcmascriptModuleResultBuilder { // This caches repeated access because EsmAssetReference::new is not a turbo task function. esm_references_rewritten: FxHashMap>>, - code_gens: Vec, + code_gens: FxIndexSet, exports: EcmascriptExports, async_module: ResolvedVc, successful: bool, @@ -292,7 +292,12 @@ impl AnalyzeEcmascriptModuleResultBuilder { C: Into, { if self.analyze_mode.is_code_gen() { - self.code_gens.push(code_gen.into()) + let (index, added) = self.code_gens.insert_full(code_gen.into()); + debug_assert!( + added, + "Duplicate code gen added: {:?}", + self.code_gens.get_index(index) + ); } } @@ -311,7 +316,7 @@ impl AnalyzeEcmascriptModuleResultBuilder { self.async_module = ResolvedVc::cell(Some(async_module)); } - /// Set whether this module is side-efffect free according to a user-provided directive. + /// Set whether this module is side-effect free according to a user-provided directive. pub fn set_has_side_effect_free_directive(&mut self, value: bool) { self.has_side_effect_free_directive = value; } @@ -415,7 +420,7 @@ impl AnalyzeEcmascriptModuleResultBuilder { esm_reexport_references: ResolvedVc::cell( esm_reexport_references.unwrap_or_default(), ), - code_generation: ResolvedVc::cell(self.code_gens), + code_generation: ResolvedVc::cell(self.code_gens.into_iter().collect::>()), exports: self.exports.resolved_cell(), async_module: self.async_module, has_side_effect_free_directive: self.has_side_effect_free_directive, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs b/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs index d34218b657e82..b6dccb9b7c871 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs @@ -316,7 +316,9 @@ impl IntoCodeGenReference for RequireContextAssetReference { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct RequireContextAssetReferenceCodeGen { path: AstPath, reference: ResolvedVc, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/unreachable.rs b/turbopack/crates/turbopack-ecmascript/src/references/unreachable.rs index babaa3c0fe4a9..805d51d150953 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/unreachable.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/unreachable.rs @@ -31,7 +31,9 @@ use crate::{ utils::AstPathRange, }; -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, +)] pub struct Unreachable { range: AstPathRange, diff --git a/turbopack/crates/turbopack-ecmascript/src/references/worker.rs b/turbopack/crates/turbopack-ecmascript/src/references/worker.rs index 17e7b474fd6ed..8c8d555a73325 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/worker.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/worker.rs @@ -125,7 +125,9 @@ impl IntoCodeGenReference for WorkerAssetReference { } } -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct WorkerAssetReferenceCodeGen { reference: ResolvedVc, path: AstPath, diff --git a/turbopack/crates/turbopack-ecmascript/src/utils.rs b/turbopack/crates/turbopack-ecmascript/src/utils.rs index b7e5523794cc5..4f0b24d6e1749 100644 --- a/turbopack/crates/turbopack-ecmascript/src/utils.rs +++ b/turbopack/crates/turbopack-ecmascript/src/utils.rs @@ -178,7 +178,7 @@ format_iter!(std::fmt::Pointer); format_iter!(std::fmt::UpperExp); format_iter!(std::fmt::UpperHex); -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, Debug, NonLocalValue)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, Debug, NonLocalValue, Hash)] pub enum AstPathRange { /// The ast path to the block or expression. Exact(#[turbo_tasks(trace_ignore)] Vec), From 8970d451c1d76d2bdab82de7bab9cfeb548e26e1 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 13 Nov 2025 17:12:31 -0800 Subject: [PATCH 2/8] Remove `clone` from `Effect` --- .../src/analyzer/graph.rs | 8 +- .../src/references/mod.rs | 1630 +++++++++-------- 2 files changed, 838 insertions(+), 800 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs index 3a4506e7090d6..1b2173f7e7ecf 100644 --- a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs +++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs @@ -35,7 +35,7 @@ use crate::{ utils::{AstPathRange, unparen}, }; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct EffectsBlock { pub effects: Vec, pub range: AstPathRange, @@ -47,7 +47,7 @@ impl EffectsBlock { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum ConditionalKind { /// The blocks of an `if` statement without an `else` block. If { then: Box }, @@ -117,7 +117,7 @@ impl ConditionalKind { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum EffectArg { Value(JsValue), Closure(JsValue, Box), @@ -140,7 +140,7 @@ impl EffectArg { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum Effect { /// Some condition which affects which effects might be executed. If the /// condition evaluates to some compile-time constant, we can use that diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index a1c2c5a7191fd..597b23225386b 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -1638,147 +1638,406 @@ async fn handle_call) + Send + Sync>( ignore_dynamic_requests, url_rewrite_behavior, collect_affecting_sources, - allow_project_root_tracing, + allow_project_root_tracing: _, .. } = state; - fn explain_args(args: &[JsValue]) -> (String, String) { - JsValue::explain_args(args, 10, 2) - } - let linked_args = |args: Vec| async move { - args.into_iter() - .map(|arg| { - let add_effects = &add_effects; - async move { - let value = match arg { - EffectArg::Value(value) => value, - EffectArg::Closure(value, block) => { - add_effects(block.effects); - value + + // Process all effects first so they happen exactly once. + // If we end up modeling the behavior of the closures passed to any of these functions then we + // will need to inline this into the appropriate spot just like Array.prototype.map support. + let unlinked_args = args + .into_iter() + .map(|effect_arg| match effect_arg { + EffectArg::Value(value) => value, + EffectArg::Closure(value, block) => { + add_effects(block.effects); + value + } + EffectArg::Spread => JsValue::unknown_empty(true, "spread is not supported yet"), + }) + .collect::>(); + + async fn handle_well_known_function_call( + func: WellKnownFunctionKind, + new: bool, + unlinked_args: Vec, + handler: &Handler, + span: Span, + ignore_dynamic_requests: bool, + analysis: &mut AnalyzeEcmascriptModuleResultBuilder, + origin: ResolvedVc>, + compile_time_info: ResolvedVc, + url_rewrite_behavior: Option, + source: ResolvedVc>, + ast_path: &[AstParentKind], + in_try: bool, + state: &AnalysisState<'_>, + collect_affecting_sources: bool, + ) -> Result<()> { + fn explain_args(args: &[JsValue]) -> (String, String) { + JsValue::explain_args(args, 10, 2) + } + + // Lazily link to avoid the expensive work when we can + let linked_args = |args: Vec| async move { + args.into_iter() + .map(|arg| state.link_value(arg, ImportAttributes::empty_ref())) + .try_join() + .await + }; + + let get_traced_project_dir = async || -> Result { + // readFileSync("./foo") should always be relative to the project root, but this is + // dangerous inside of node_modules as it can cause a lot of false positives in the + // tracing, if some package does `path.join(dynamic)`, it would include + // everything from the project root as well. + // + // Also, when there's no cwd set (i.e. in a tracing-specific module context, as we + // shouldn't assume a `process.cwd()` for all of node_modules), fallback to + // the source file directory. This still allows relative file accesses, just + // not from the project root. + if state.allow_project_root_tracing + && let Some(cwd) = compile_time_info.environment().cwd().owned().await? + { + Ok(cwd) + } else { + Ok(source.ident().path().await?.parent()) + } + }; + + let get_issue_source = + || IssueSource::from_swc_offsets(source, span.lo.to_u32(), span.hi.to_u32()); + if new { + match func { + WellKnownFunctionKind::URLConstructor => { + let args = linked_args(unlinked_args).await?; + if let [ + url, + JsValue::Member( + _, + box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta), + box JsValue::Constant(super::analyzer::ConstantValue::Str(meta_prop)), + ), + ] = &args[..] + && meta_prop.as_str() == "url" + { + let pat = js_value_to_pattern(url); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("new URL({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::NEW_URL_IMPORT_META + .to_string(), + ), + ); + if ignore_dynamic_requests { + return Ok(()); + } } - EffectArg::Spread => { - JsValue::unknown_empty(true, "spread is not supported yet") + analysis.add_reference_code_gen( + UrlAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + *compile_time_info.environment().rendering().await?, + issue_source(source, span), + in_try, + url_rewrite_behavior.unwrap_or(UrlRewriteBehavior::Relative), + ), + ast_path.to_vec().into(), + ); + } + return Ok(()); + } + WellKnownFunctionKind::WorkerConstructor => { + let args = linked_args(unlinked_args).await?; + if let Some(url @ JsValue::Url(_, JsValueUrlKind::Relative)) = args.first() { + let pat = js_value_to_pattern(url); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("new Worker({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), + ), + ); + if ignore_dynamic_requests { + return Ok(()); + } } - }; - state.link_value(value, ImportAttributes::empty_ref()).await + + if *compile_time_info.environment().rendering().await? == Rendering::Client + { + analysis.add_reference_code_gen( + WorkerAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + issue_source(source, span), + in_try, + ), + ast_path.to_vec().into(), + ); + } + + return Ok(()); + } + // Ignore (e.g. dynamic parameter or string literal), just as Webpack does + return Ok(()); } - }) - .try_join() - .await - }; + WellKnownFunctionKind::NodeWorkerConstructor + if analysis.analyze_mode.is_tracing() => + { + // Only for tracing, not for bundling (yet?) + let args = linked_args(unlinked_args).await?; + if !args.is_empty() { + let pat = js_value_to_pattern(&args[0]); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("new Worker({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), + ), + ); + if ignore_dynamic_requests { + return Ok(()); + } + } + analysis.add_reference( + FilePathModuleReference::new( + origin.asset_context(), + get_traced_project_dir().await?, + Pattern::new(pat), + collect_affecting_sources, + get_issue_source(), + ) + .to_resolved() + .await?, + ); + return Ok(()); + } + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("new Worker({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), + ), + ); + // Ignore (e.g. dynamic parameter or string literal) + return Ok(()); + } + _ => {} + } - let get_traced_project_dir = async || -> Result { - // readFileSync("./foo") should always be relative to the project root, but this is - // dangerous inside of node_modules as it can cause a lot of false positives in the tracing, - // if some package does `path.join(dynamic)`, it would include everything from the project - // root as well. - // - // Also, when there's no cwd set (i.e. in a tracing-specific module context, as we shouldn't - // assume a `process.cwd()` for all of node_modules), fallback to the source file directory. - // This still allows relative file accesses, just not from the project root. - if allow_project_root_tracing - && let Some(cwd) = compile_time_info.environment().cwd().owned().await? - { - Ok(cwd) - } else { - Ok(source.ident().path().await?.parent()) + return Ok(()); } - }; - - let get_issue_source = - || IssueSource::from_swc_offsets(source, span.lo.to_u32(), span.hi.to_u32()); - if new { match func { - JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor) => { - let args = linked_args(args).await?; - if let [ - url, - JsValue::Member( - _, - box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta), - box JsValue::Constant(super::analyzer::ConstantValue::Str(meta_prop)), - ), - ] = &args[..] - && meta_prop.as_str() == "url" - { - let pat = js_value_to_pattern(url); + WellKnownFunctionKind::Import => { + let args = linked_args(unlinked_args).await?; + if args.len() == 1 || args.len() == 2 { + let pat = js_value_to_pattern(&args[0]); + let options = args.get(1); + let import_annotations = options + .and_then(|options| { + if let JsValue::Object { parts, .. } = options { + parts.iter().find_map(|part| { + if let ObjectPart::KeyValue( + JsValue::Constant(super::analyzer::ConstantValue::Str(key)), + value, + ) = part + && key.as_str() == "with" + { + return Some(value); + } + None + }) + } else { + None + } + }) + .and_then(ImportAnnotations::parse_dynamic) + .unwrap_or_default(); if !pat.has_constant_parts() { let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("new URL({args}) is very dynamic{hints}",), + &format!("import({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NEW_URL_IMPORT_META - .to_string(), + errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), ), ); if ignore_dynamic_requests { + analysis.add_code_gen(DynamicExpression::new_promise( + ast_path.to_vec().into(), + )); return Ok(()); } } analysis.add_reference_code_gen( - UrlAssetReference::new( + EsmAsyncAssetReference::new( origin, Request::parse(pat).to_resolved().await?, - *compile_time_info.environment().rendering().await?, issue_source(source, span), + import_annotations, in_try, - url_rewrite_behavior.unwrap_or(UrlRewriteBehavior::Relative), + state.import_externals, ), ast_path.to_vec().into(), ); + return Ok(()); } - return Ok(()); + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("import({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), + ), + ) } - JsValue::WellKnownFunction(WellKnownFunctionKind::WorkerConstructor) => { - let args = linked_args(args).await?; - if let Some(url @ JsValue::Url(_, JsValueUrlKind::Relative)) = args.first() { - let pat = js_value_to_pattern(url); + WellKnownFunctionKind::Require => { + let args = linked_args(unlinked_args).await?; + if args.len() == 1 { + let pat = js_value_to_pattern(&args[0]); if !pat.has_constant_parts() { let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("new Worker({args}) is very dynamic{hints}",), + &format!("require({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), + errors::failed_to_analyze::ecmascript::REQUIRE.to_string(), ), ); if ignore_dynamic_requests { + analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); return Ok(()); } } + analysis.add_reference_code_gen( + CjsRequireAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + issue_source(source, span), + in_try, + ), + ast_path.to_vec().into(), + ); + return Ok(()); + } + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("require({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error(errors::failed_to_analyze::ecmascript::REQUIRE.to_string()), + ) + } + WellKnownFunctionKind::Define => { + analyze_amd_define( + source, + analysis, + origin, + handler, + span, + ast_path, + linked_args(unlinked_args).await?, + in_try, + ) + .await?; + } - if *compile_time_info.environment().rendering().await? == Rendering::Client { - analysis.add_reference_code_gen( - WorkerAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - issue_source(source, span), - in_try, + WellKnownFunctionKind::RequireResolve => { + let args = linked_args(unlinked_args).await?; + if args.len() == 1 || args.len() == 2 { + // TODO error TP1003 require.resolve(???*0*, {"paths": [???*1*]}) is not + // statically analyze-able with ignore_dynamic_requests = + // true + let pat = js_value_to_pattern(&args[0]); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("require.resolve({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), ), - ast_path.to_vec().into(), ); + if ignore_dynamic_requests { + analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); + return Ok(()); + } } - + analysis.add_reference_code_gen( + CjsRequireResolveAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + issue_source(source, span), + in_try, + ), + ast_path.to_vec().into(), + ); return Ok(()); } - // Ignore (e.g. dynamic parameter or string literal), just as Webpack does - return Ok(()); + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("require.resolve({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), + ), + ) } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodeWorkerConstructor) - if analysis.analyze_mode.is_tracing() => - { - // Only for tracing, not for bundling (yet?) - let args = linked_args(args).await?; + + WellKnownFunctionKind::RequireContext => { + let args = linked_args(unlinked_args).await?; + let options = match parse_require_context(&args) { + Ok(options) => options, + Err(err) => { + let (args, hints) = explain_args(&args); + handler.span_err_with_code( + span, + &format!( + "require.context({args}) is not statically analyze-able: {}{hints}", + PrettyPrintError(&err) + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::REQUIRE_CONTEXT.to_string(), + ), + ); + return Ok(()); + } + }; + + analysis.add_reference_code_gen( + RequireContextAssetReference::new( + source, + origin, + options.dir, + options.include_subdirs, + options.filter.cell(), + Some(issue_source(source, span)), + in_try, + ) + .await?, + ast_path.to_vec().into(), + ); + } + + WellKnownFunctionKind::FsReadMethod(name) if analysis.analyze_mode.is_tracing() => { + let args = linked_args(unlinked_args).await?; if !args.is_empty() { let pat = js_value_to_pattern(&args[0]); if !pat.has_constant_parts() { let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("new Worker({args}) is very dynamic{hints}",), + &format!("fs.{name}({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), + errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), ), ); if ignore_dynamic_requests { @@ -1786,8 +2045,7 @@ async fn handle_call) + Send + Sync>( } } analysis.add_reference( - FilePathModuleReference::new( - origin.asset_context(), + FileSourceReference::new( get_traced_project_dir().await?, Pattern::new(pat), collect_affecting_sources, @@ -1801,816 +2059,596 @@ async fn handle_call) + Send + Sync>( let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("new Worker({args}) is not statically analyze-able{hints}",), + &format!("fs.{name}({args}) is not statically analyze-able{hints}",), DiagnosticId::Error( errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), ), - ); - // Ignore (e.g. dynamic parameter or string literal) - return Ok(()); + ) } - _ => {} - } - // linked_args wasn't called, so manually add the closure effects - for arg in args { - if let EffectArg::Closure(_, block) = arg { - add_effects(block.effects); - } - } - return Ok(()); - } + WellKnownFunctionKind::PathResolve(..) if analysis.analyze_mode.is_tracing() => { + let parent_path = origin.origin_path().owned().await?.parent(); + let args = linked_args(unlinked_args).await?; - match func { - JsValue::Alternatives { - total_nodes: _, - values, - logical_property: _, - } => { - for alt in values { - Box::pin(handle_call( - ast_path, - span, - alt, - args.clone(), - state, - add_effects, - analysis, - in_try, - new, - )) - .await?; - } - } - JsValue::WellKnownFunction(WellKnownFunctionKind::Import) => { - let args = linked_args(args).await?; - if args.len() == 1 || args.len() == 2 { - let pat = js_value_to_pattern(&args[0]); - let options = args.get(1); - let import_annotations = options - .and_then(|options| { - if let JsValue::Object { parts, .. } = options { - parts.iter().find_map(|part| { - if let ObjectPart::KeyValue( - JsValue::Constant(super::analyzer::ConstantValue::Str(key)), - value, - ) = part - && key.as_str() == "with" - { - return Some(value); - } - None - }) - } else { - None - } - }) - .and_then(ImportAnnotations::parse_dynamic) - .unwrap_or_default(); + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction( + WellKnownFunctionKind::PathResolve(Box::new( + parent_path.path.as_str().into(), + )), + )), + args.clone(), + ), + ImportAttributes::empty_ref(), + ) + .await?; + + let pat = js_value_to_pattern(&linked_func_call); if !pat.has_constant_parts() { let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("import({args}) is very dynamic{hints}",), + &format!("path.resolve({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), + errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), ), ); if ignore_dynamic_requests { - analysis - .add_code_gen(DynamicExpression::new_promise(ast_path.to_vec().into())); return Ok(()); } } - analysis.add_reference_code_gen( - EsmAsyncAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - issue_source(source, span), - import_annotations, - in_try, - state.import_externals, - ), - ast_path.to_vec().into(), + analysis.add_reference( + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(pat), + get_issue_source(), + ) + .to_resolved() + .await?, ); return Ok(()); } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("import({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::Require) => { - let args = linked_args(args).await?; - if args.len() == 1 { - let pat = js_value_to_pattern(&args[0]); + + WellKnownFunctionKind::PathJoin if analysis.analyze_mode.is_tracing() => { + let context_path = source.ident().path().await?; + // ignore path.join in `node-gyp`, it will includes too many files + if context_path.path.contains("node_modules/node-gyp") { + return Ok(()); + } + let args = linked_args(unlinked_args).await?; + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::PathJoin)), + args.clone(), + ), + ImportAttributes::empty_ref(), + ) + .await?; + let pat = js_value_to_pattern(&linked_func_call); if !pat.has_constant_parts() { let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("require({args}) is very dynamic{hints}",), + &format!("path.join({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::REQUIRE.to_string(), + errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), ), ); if ignore_dynamic_requests { - analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); return Ok(()); } } - analysis.add_reference_code_gen( - CjsRequireAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - issue_source(source, span), - in_try, - ), - ast_path.to_vec().into(), + analysis.add_reference( + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(pat), + get_issue_source(), + ) + .to_resolved() + .await?, ); return Ok(()); } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("require({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error(errors::failed_to_analyze::ecmascript::REQUIRE.to_string()), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::Define) => { - analyze_amd_define( - source, - analysis, - origin, - handler, - span, - ast_path, - linked_args(args).await?, - in_try, - ) - .await?; - } + WellKnownFunctionKind::ChildProcessSpawnMethod(name) + if analysis.analyze_mode.is_tracing() => + { + let args = linked_args(unlinked_args).await?; - JsValue::WellKnownFunction(WellKnownFunctionKind::RequireResolve) => { - let args = linked_args(args).await?; - if args.len() == 1 || args.len() == 2 { - // TODO error TP1003 require.resolve(???*0*, {"paths": [???*1*]}) is not statically - // analyze-able with ignore_dynamic_requests = true - let pat = js_value_to_pattern(&args[0]); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("require.resolve({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), - ), - ); - if ignore_dynamic_requests { - analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); - return Ok(()); + // Is this specifically `spawn(process.argv[0], ['-e', ...])`? + if is_invoking_node_process_eval(&args) { + return Ok(()); + } + + if !args.is_empty() { + let mut show_dynamic_warning = false; + let pat = js_value_to_pattern(&args[0]); + if pat.is_match_ignore_dynamic("node") && args.len() >= 2 { + let first_arg = + JsValue::member(Box::new(args[1].clone()), Box::new(0_f64.into())); + let first_arg = state + .link_value(first_arg, ImportAttributes::empty_ref()) + .await?; + let pat = js_value_to_pattern(&first_arg); + let dynamic = !pat.has_constant_parts(); + if dynamic { + show_dynamic_warning = true; + } + if !dynamic || !ignore_dynamic_requests { + analysis.add_reference( + CjsAssetReference::new( + *origin, + Request::parse(pat), + issue_source(source, span), + in_try, + ) + .to_resolved() + .await?, + ); + } + } + let dynamic = !pat.has_constant_parts(); + if dynamic { + show_dynamic_warning = true; + } + if !dynamic || !ignore_dynamic_requests { + analysis.add_reference( + FileSourceReference::new( + get_traced_project_dir().await?, + Pattern::new(pat), + collect_affecting_sources, + IssueSource::from_swc_offsets( + source, + span.lo.to_u32(), + span.hi.to_u32(), + ), + ) + .to_resolved() + .await?, + ); + } + if show_dynamic_warning { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("child_process.{name}({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN + .to_string(), + ), + ); } + return Ok(()); } - analysis.add_reference_code_gen( - CjsRequireResolveAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - issue_source(source, span), - in_try, + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("child_process.{name}({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), ), - ast_path.to_vec().into(), - ); - return Ok(()); + ) } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("require.resolve({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), - ), - ) - } - - JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContext) => { - let args = linked_args(args).await?; - let options = match parse_require_context(&args) { - Ok(options) => options, - Err(err) => { - let (args, hints) = explain_args(&args); - handler.span_err_with_code( - span, - &format!( - "require.context({args}) is not statically analyze-able: {}{hints}", - PrettyPrintError(&err) - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::REQUIRE_CONTEXT.to_string(), - ), + WellKnownFunctionKind::ChildProcessFork if analysis.analyze_mode.is_tracing() => { + let args = linked_args(unlinked_args).await?; + if !args.is_empty() { + let first_arg = &args[0]; + let pat = js_value_to_pattern(first_arg); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("child_process.fork({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN + .to_string(), + ), + ); + if ignore_dynamic_requests { + return Ok(()); + } + } + analysis.add_reference( + CjsAssetReference::new( + *origin, + Request::parse(pat), + issue_source(source, span), + in_try, + ) + .to_resolved() + .await?, ); return Ok(()); } - }; - - analysis.add_reference_code_gen( - RequireContextAssetReference::new( - source, - origin, - options.dir, - options.include_subdirs, - options.filter.cell(), - Some(issue_source(source, span)), - in_try, + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("child_process.fork({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), + ), ) - .await?, - ast_path.to_vec().into(), - ); - } + } + WellKnownFunctionKind::NodePreGypFind if analysis.analyze_mode.is_tracing() => { + use turbopack_resolve::node_native_binding::NodePreGypConfigReference; - JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadMethod(name)) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args(args).await?; - if !args.is_empty() { - let pat = js_value_to_pattern(&args[0]); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("fs.{name}({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), - ), - ); - if ignore_dynamic_requests { + let args = linked_args(unlinked_args).await?; + if args.len() == 1 { + let first_arg = &args[0]; + let pat = js_value_to_pattern(first_arg); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("node-pre-gyp.find({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND + .to_string(), + ), + ); + // Always ignore this dynamic request return Ok(()); } + analysis.add_reference( + NodePreGypConfigReference::new( + origin.origin_path().await?.parent(), + Pattern::new(pat), + compile_time_info.environment().compile_target(), + collect_affecting_sources, + ) + .to_resolved() + .await?, + ); + return Ok(()); } - analysis.add_reference( - FileSourceReference::new( - get_traced_project_dir().await?, - Pattern::new(pat), - collect_affecting_sources, - get_issue_source(), - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("fs.{name}({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error(errors::failed_to_analyze::ecmascript::FS_METHOD.to_string()), - ) - } - - JsValue::WellKnownFunction(WellKnownFunctionKind::PathResolve(..)) - if analysis.analyze_mode.is_tracing() => - { - let parent_path = origin.origin_path().owned().await?.parent(); - let args = linked_args(args).await?; - - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction( - WellKnownFunctionKind::PathResolve(Box::new( - parent_path.path.as_str().into(), - )), - )), - args.clone(), - ), - ImportAttributes::empty_ref(), - ) - .await?; - - let pat = js_value_to_pattern(&linked_func_call); - if !pat.has_constant_parts() { let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("path.resolve({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), + &format!( + "require('@mapbox/node-pre-gyp').find({args}) is not statically \ + analyze-able{hints}", ), - ); - if ignore_dynamic_requests { - return Ok(()); - } - } - analysis.add_reference( - DirAssetReference::new( - get_traced_project_dir().await?, - Pattern::new(pat), - get_issue_source(), - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - - JsValue::WellKnownFunction(WellKnownFunctionKind::PathJoin) - if analysis.analyze_mode.is_tracing() => - { - let context_path = source.ident().path().await?; - // ignore path.join in `node-gyp`, it will includes too many files - if context_path.path.contains("node_modules/node-gyp") { - return Ok(()); - } - let args = linked_args(args).await?; - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::PathJoin)), - args.clone(), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND.to_string(), ), - ImportAttributes::empty_ref(), ) - .await?; - let pat = js_value_to_pattern(&linked_func_call); - if !pat.has_constant_parts() { + } + WellKnownFunctionKind::NodeGypBuild if analysis.analyze_mode.is_tracing() => { + use turbopack_resolve::node_native_binding::NodeGypBuildReference; + + let args = linked_args(unlinked_args).await?; + if args.len() == 1 { + let first_arg = state + .link_value(args[0].clone(), ImportAttributes::empty_ref()) + .await?; + if let Some(s) = first_arg.as_str() { + // TODO this resolving should happen within Vc + let current_context = origin + .origin_path() + .await? + .root() + .await? + .join(s.trim_start_matches("/ROOT/"))?; + analysis.add_reference( + NodeGypBuildReference::new( + current_context, + collect_affecting_sources, + compile_time_info.environment().compile_target(), + ) + .to_resolved() + .await?, + ); + return Ok(()); + } + } let (args, hints) = explain_args(&args); handler.span_warn_with_code( span, - &format!("path.join({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), + &format!( + "require('node-gyp-build')({args}) is not statically analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_GYP_BUILD.to_string(), ), - ); - if ignore_dynamic_requests { - return Ok(()); - } - } - analysis.add_reference( - DirAssetReference::new( - get_traced_project_dir().await?, - Pattern::new(pat), - get_issue_source(), ) - .to_resolved() - .await?, - ); - return Ok(()); - } - JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessSpawnMethod(name)) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args(args).await?; - - // Is this specifically `spawn(process.argv[0], ['-e', ...])`? - if is_invoking_node_process_eval(&args) { - return Ok(()); } + WellKnownFunctionKind::NodeBindings if analysis.analyze_mode.is_tracing() => { + use turbopack_resolve::node_native_binding::NodeBindingsReference; - if !args.is_empty() { - let mut show_dynamic_warning = false; - let pat = js_value_to_pattern(&args[0]); - if pat.is_match_ignore_dynamic("node") && args.len() >= 2 { - let first_arg = - JsValue::member(Box::new(args[1].clone()), Box::new(0_f64.into())); + let args = linked_args(unlinked_args).await?; + if args.len() == 1 { let first_arg = state - .link_value(first_arg, ImportAttributes::empty_ref()) + .link_value(args[0].clone(), ImportAttributes::empty_ref()) .await?; - let pat = js_value_to_pattern(&first_arg); - let dynamic = !pat.has_constant_parts(); - if dynamic { - show_dynamic_warning = true; - } - if !dynamic || !ignore_dynamic_requests { + if let Some(s) = first_arg.as_str() { analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(pat), - issue_source(source, span), - in_try, + NodeBindingsReference::new( + origin.origin_path().owned().await?, + s.into(), + collect_affecting_sources, ) .to_resolved() .await?, ); + return Ok(()); } } - let dynamic = !pat.has_constant_parts(); - if dynamic { - show_dynamic_warning = true; - } - if !dynamic || !ignore_dynamic_requests { - analysis.add_reference( - FileSourceReference::new( - get_traced_project_dir().await?, - Pattern::new(pat), - collect_affecting_sources, - IssueSource::from_swc_offsets( - source, - span.lo.to_u32(), - span.hi.to_u32(), - ), - ) - .to_resolved() - .await?, - ); - } - if show_dynamic_warning { - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("child_process.{name}({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), - ), - ); - } - return Ok(()); + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("require('bindings')({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_BINDINGS.to_string(), + ), + ) } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("child_process.{name}({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessFork) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args(args).await?; - if !args.is_empty() { - let first_arg = &args[0]; - let pat = js_value_to_pattern(first_arg); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("child_process.fork({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), - ), - ); - if ignore_dynamic_requests { + WellKnownFunctionKind::NodeExpressSet if analysis.analyze_mode.is_tracing() => { + let args = linked_args(unlinked_args).await?; + if args.len() == 2 + && let Some(s) = args.first().and_then(|arg| arg.as_str()) + { + let pkg_or_dir = args.get(1).unwrap(); + let pat = js_value_to_pattern(pkg_or_dir); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!("require('express')().set({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), + ), + ); + // Always ignore this dynamic request return Ok(()); } + match s { + "views" => { + if let Pattern::Constant(p) = &pat { + let abs_pattern = if p.starts_with("/ROOT/") { + pat + } else { + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction( + WellKnownFunctionKind::PathJoin, + )), + vec![ + JsValue::FreeVar(atom!("__dirname")), + pkg_or_dir.clone(), + ], + ), + ImportAttributes::empty_ref(), + ) + .await?; + js_value_to_pattern(&linked_func_call) + }; + analysis.add_reference( + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(abs_pattern), + get_issue_source(), + ) + .to_resolved() + .await?, + ); + return Ok(()); + } + } + "view engine" => { + if let Some(pkg) = pkg_or_dir.as_str() { + if pkg != "html" { + let pat = js_value_to_pattern(pkg_or_dir); + analysis.add_reference( + CjsAssetReference::new( + *origin, + Request::parse(pat), + issue_source(source, span), + in_try, + ) + .to_resolved() + .await?, + ); + } + return Ok(()); + } + } + _ => {} + } } - analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(pat), - issue_source(source, span), - in_try, - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("child_process.fork({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodePreGypFind) - if analysis.analyze_mode.is_tracing() => - { - use turbopack_resolve::node_native_binding::NodePreGypConfigReference; - - let args = linked_args(args).await?; - if args.len() == 1 { - let first_arg = &args[0]; - let pat = js_value_to_pattern(first_arg); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("node-pre-gyp.find({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND.to_string(), - ), - ); - // Always ignore this dynamic request - return Ok(()); - } - analysis.add_reference( - NodePreGypConfigReference::new( - origin.origin_path().await?.parent(), - Pattern::new(pat), - compile_time_info.environment().compile_target(), - collect_affecting_sources, - ) - .to_resolved() - .await?, - ); - return Ok(()); + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!( + "require('express')().set({args}) is not statically analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), + ), + ) } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!( - "require('@mapbox/node-pre-gyp').find({args}) is not statically \ - analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodeGypBuild) - if analysis.analyze_mode.is_tracing() => - { - use turbopack_resolve::node_native_binding::NodeGypBuildReference; - - let args = linked_args(args).await?; - if args.len() == 1 { - let first_arg = state - .link_value(args[0].clone(), ImportAttributes::empty_ref()) - .await?; - if let Some(s) = first_arg.as_str() { - // TODO this resolving should happen within Vc - let current_context = origin - .origin_path() - .await? - .root() - .await? - .join(s.trim_start_matches("/ROOT/"))?; + WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir + if analysis.analyze_mode.is_tracing() => + { + let args = linked_args(unlinked_args).await?; + if let Some(p) = args.first().and_then(|arg| arg.as_str()) { + let abs_pattern = if p.starts_with("/ROOT/") { + Pattern::Constant(format!("{p}/intl").into()) + } else { + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction( + WellKnownFunctionKind::PathJoin, + )), + vec![ + JsValue::FreeVar(atom!("__dirname")), + p.into(), + atom!("intl").into(), + ], + ), + ImportAttributes::empty_ref(), + ) + .await?; + js_value_to_pattern(&linked_func_call) + }; analysis.add_reference( - NodeGypBuildReference::new( - current_context, - collect_affecting_sources, - compile_time_info.environment().compile_target(), + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(abs_pattern), + get_issue_source(), ) .to_resolved() .await?, ); return Ok(()); } + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!( + "require('strong-globalize').SetRootDir({args}) is not statically \ + analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_GYP_BUILD.to_string(), + ), + ) } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!( - "require('node-gyp-build')({args}) is not statically analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_GYP_BUILD.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodeBindings) - if analysis.analyze_mode.is_tracing() => - { - use turbopack_resolve::node_native_binding::NodeBindingsReference; - - let args = linked_args(args).await?; - if args.len() == 1 { - let first_arg = state - .link_value(args[0].clone(), ImportAttributes::empty_ref()) - .await?; - if let Some(s) = first_arg.as_str() { + WellKnownFunctionKind::NodeResolveFrom if analysis.analyze_mode.is_tracing() => { + let args = linked_args(unlinked_args).await?; + if args.len() == 2 && args.get(1).and_then(|arg| arg.as_str()).is_some() { analysis.add_reference( - NodeBindingsReference::new( - origin.origin_path().owned().await?, - s.into(), - collect_affecting_sources, + CjsAssetReference::new( + *origin, + Request::parse(js_value_to_pattern(&args[1])), + issue_source(source, span), + in_try, ) .to_resolved() .await?, ); return Ok(()); } + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!( + "require('resolve-from')({args}) is not statically analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_RESOLVE_FROM.to_string(), + ), + ) } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("require('bindings')({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_BINDINGS.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodeExpressSet) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args(args).await?; - if args.len() == 2 - && let Some(s) = args.first().and_then(|arg| arg.as_str()) - { - let pkg_or_dir = args.get(1).unwrap(); - let pat = js_value_to_pattern(pkg_or_dir); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("require('express')().set({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), - ), - ); - // Always ignore this dynamic request - return Ok(()); - } - match s { - "views" => { - if let Pattern::Constant(p) = &pat { - let abs_pattern = if p.starts_with("/ROOT/") { - pat - } else { - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction( - WellKnownFunctionKind::PathJoin, - )), - vec![ - JsValue::FreeVar(atom!("__dirname")), - pkg_or_dir.clone(), - ], - ), - ImportAttributes::empty_ref(), - ) - .await?; - js_value_to_pattern(&linked_func_call) - }; - analysis.add_reference( - DirAssetReference::new( - get_traced_project_dir().await?, - Pattern::new(abs_pattern), - get_issue_source(), - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - } - "view engine" => { - if let Some(pkg) = pkg_or_dir.as_str() { - if pkg != "html" { - let pat = js_value_to_pattern(pkg_or_dir); - analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(pat), - issue_source(source, span), - in_try, - ) - .to_resolved() - .await?, - ); + WellKnownFunctionKind::NodeProtobufLoad if analysis.analyze_mode.is_tracing() => { + let args = linked_args(unlinked_args).await?; + if args.len() == 2 + && let Some(JsValue::Object { parts, .. }) = args.get(1) + { + let context_dir = get_traced_project_dir().await?; + let resolved_dirs = parts + .iter() + .filter_map(|object_part| match object_part { + ObjectPart::KeyValue( + JsValue::Constant(key), + JsValue::Array { items: dirs, .. }, + ) if key.as_str() == Some("includeDirs") => { + Some(dirs.iter().filter_map(|dir| dir.as_str())) } - return Ok(()); - } + _ => None, + }) + .flatten() + .map(|dir| { + DirAssetReference::new( + context_dir.clone(), + Pattern::new(Pattern::Constant(dir.into())), + get_issue_source(), + ) + .to_resolved() + }) + .try_join() + .await?; + + for resolved_dir_ref in resolved_dirs { + analysis.add_reference(resolved_dir_ref); } - _ => {} + + return Ok(()); } + let (args, hints) = explain_args(&args); + handler.span_warn_with_code( + span, + &format!( + "require('@grpc/proto-loader').load({args}) is not statically \ + analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_PROTOBUF_LOADER.to_string(), + ), + ) } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("require('express')().set({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args(args).await?; - if let Some(p) = args.first().and_then(|arg| arg.as_str()) { - let abs_pattern = if p.starts_with("/ROOT/") { - Pattern::Constant(format!("{p}/intl").into()) - } else { - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction( - WellKnownFunctionKind::PathJoin, - )), - vec![ - JsValue::FreeVar(atom!("__dirname")), - p.into(), - atom!("intl").into(), - ], - ), - ImportAttributes::empty_ref(), + _ => {} + }; + Ok(()) + } + + match func { + JsValue::Alternatives { + total_nodes: _, + values, + logical_property: _, + } => { + for alt in values { + match alt { + JsValue::WellKnownFunction(wkf) => { + handle_well_known_function_call( + wkf, + new, + unlinked_args.clone(), + handler, + span, + ignore_dynamic_requests, + analysis, + origin, + compile_time_info, + url_rewrite_behavior, + source, + ast_path, + in_try, + state, + collect_affecting_sources, ) .await?; - js_value_to_pattern(&linked_func_call) - }; - analysis.add_reference( - DirAssetReference::new( - get_traced_project_dir().await?, - Pattern::new(abs_pattern), - get_issue_source(), - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!( - "require('strong-globalize').SetRootDir({args}) is not statically \ - analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_GYP_BUILD.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args(args).await?; - if args.len() == 2 && args.get(1).and_then(|arg| arg.as_str()).is_some() { - analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(js_value_to_pattern(&args[1])), - issue_source(source, span), - in_try, - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( - span, - &format!("require('resolve-from')({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_RESOLVE_FROM.to_string(), - ), - ) - } - JsValue::WellKnownFunction(WellKnownFunctionKind::NodeProtobufLoad) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args(args).await?; - if args.len() == 2 - && let Some(JsValue::Object { parts, .. }) = args.get(1) - { - let context_dir = get_traced_project_dir().await?; - let resolved_dirs = parts - .iter() - .filter_map(|object_part| match object_part { - ObjectPart::KeyValue( - JsValue::Constant(key), - JsValue::Array { items: dirs, .. }, - ) if key.as_str() == Some("includeDirs") => { - Some(dirs.iter().filter_map(|dir| dir.as_str())) - } - _ => None, - }) - .flatten() - .map(|dir| { - DirAssetReference::new( - context_dir.clone(), - Pattern::new(Pattern::Constant(dir.into())), - get_issue_source(), - ) - .to_resolved() - }) - .try_join() - .await?; - - for resolved_dir_ref in resolved_dirs { - analysis.add_reference(resolved_dir_ref); + } + _ => {} } - - return Ok(()); } - let (args, hints) = explain_args(&args); - handler.span_warn_with_code( + } + JsValue::WellKnownFunction(wkf) => { + handle_well_known_function_call( + wkf, + new, + unlinked_args, + handler, span, - &format!( - "require('@grpc/proto-loader').load({args}) is not statically \ - analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_PROTOBUF_LOADER.to_string(), - ), + ignore_dynamic_requests, + analysis, + origin, + compile_time_info, + url_rewrite_behavior, + source, + ast_path, + in_try, + state, + collect_affecting_sources, ) + .await?; } - _ => { - for arg in args { - if let EffectArg::Closure(_, block) = arg { - add_effects(block.effects); - } - } - } + _ => {} } + Ok(()) } From 7083348e1e97d029b33d3e1839f7b85d14a07adb Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Fri, 14 Nov 2025 06:51:06 -0800 Subject: [PATCH 3/8] avoid linking args multiple times in the case of alternates --- .../src/references/mod.rs | 185 ++++++++++-------- 1 file changed, 98 insertions(+), 87 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index 597b23225386b..6582a8a4ace2d 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -59,6 +59,7 @@ use swc_core::{ }, }, }; +use tokio::sync::OnceCell; use tracing::Instrument; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ @@ -1657,10 +1658,10 @@ async fn handle_call) + Send + Sync>( }) .collect::>(); - async fn handle_well_known_function_call( + async fn handle_well_known_function_call<'a, F, Fut>( func: WellKnownFunctionKind, new: bool, - unlinked_args: Vec, + linked_args: &F, handler: &Handler, span: Span, ignore_dynamic_requests: bool, @@ -1671,21 +1672,17 @@ async fn handle_call) + Send + Sync>( source: ResolvedVc>, ast_path: &[AstParentKind], in_try: bool, - state: &AnalysisState<'_>, + state: &'a AnalysisState<'a>, collect_affecting_sources: bool, - ) -> Result<()> { + ) -> Result<()> + where + F: Fn() -> Fut, + Fut: Future>>, + { fn explain_args(args: &[JsValue]) -> (String, String) { JsValue::explain_args(args, 10, 2) } - // Lazily link to avoid the expensive work when we can - let linked_args = |args: Vec| async move { - args.into_iter() - .map(|arg| state.link_value(arg, ImportAttributes::empty_ref())) - .try_join() - .await - }; - let get_traced_project_dir = async || -> Result { // readFileSync("./foo") should always be relative to the project root, but this is // dangerous inside of node_modules as it can cause a lot of false positives in the @@ -1710,7 +1707,7 @@ async fn handle_call) + Send + Sync>( if new { match func { WellKnownFunctionKind::URLConstructor => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if let [ url, JsValue::Member( @@ -1723,7 +1720,7 @@ async fn handle_call) + Send + Sync>( { let pat = js_value_to_pattern(url); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("new URL({args}) is very dynamic{hints}",), @@ -1751,11 +1748,11 @@ async fn handle_call) + Send + Sync>( return Ok(()); } WellKnownFunctionKind::WorkerConstructor => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if let Some(url @ JsValue::Url(_, JsValueUrlKind::Relative)) = args.first() { let pat = js_value_to_pattern(url); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("new Worker({args}) is very dynamic{hints}",), @@ -1790,11 +1787,11 @@ async fn handle_call) + Send + Sync>( if analysis.analyze_mode.is_tracing() => { // Only for tracing, not for bundling (yet?) - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if !args.is_empty() { let pat = js_value_to_pattern(&args[0]); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("new Worker({args}) is very dynamic{hints}",), @@ -1819,7 +1816,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("new Worker({args}) is not statically analyze-able{hints}",), @@ -1838,7 +1835,7 @@ async fn handle_call) + Send + Sync>( match func { WellKnownFunctionKind::Import => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 1 || args.len() == 2 { let pat = js_value_to_pattern(&args[0]); let options = args.get(1); @@ -1863,7 +1860,7 @@ async fn handle_call) + Send + Sync>( .and_then(ImportAnnotations::parse_dynamic) .unwrap_or_default(); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("import({args}) is very dynamic{hints}",), @@ -1891,7 +1888,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("import({args}) is not statically analyze-able{hints}",), @@ -1901,11 +1898,11 @@ async fn handle_call) + Send + Sync>( ) } WellKnownFunctionKind::Require => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 1 { let pat = js_value_to_pattern(&args[0]); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("require({args}) is very dynamic{hints}",), @@ -1929,7 +1926,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("require({args}) is not statically analyze-able{hints}",), @@ -1944,21 +1941,21 @@ async fn handle_call) + Send + Sync>( handler, span, ast_path, - linked_args(unlinked_args).await?, + linked_args().await?, in_try, ) .await?; } WellKnownFunctionKind::RequireResolve => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 1 || args.len() == 2 { // TODO error TP1003 require.resolve(???*0*, {"paths": [???*1*]}) is not // statically analyze-able with ignore_dynamic_requests = // true let pat = js_value_to_pattern(&args[0]); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("require.resolve({args}) is very dynamic{hints}",), @@ -1982,7 +1979,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("require.resolve({args}) is not statically analyze-able{hints}",), @@ -1993,11 +1990,11 @@ async fn handle_call) + Send + Sync>( } WellKnownFunctionKind::RequireContext => { - let args = linked_args(unlinked_args).await?; - let options = match parse_require_context(&args) { + let args = linked_args().await?; + let options = match parse_require_context(args) { Ok(options) => options, Err(err) => { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_err_with_code( span, &format!( @@ -2028,11 +2025,11 @@ async fn handle_call) + Send + Sync>( } WellKnownFunctionKind::FsReadMethod(name) if analysis.analyze_mode.is_tracing() => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if !args.is_empty() { let pat = js_value_to_pattern(&args[0]); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("fs.{name}({args}) is very dynamic{hints}",), @@ -2056,7 +2053,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("fs.{name}({args}) is not statically analyze-able{hints}",), @@ -2068,7 +2065,7 @@ async fn handle_call) + Send + Sync>( WellKnownFunctionKind::PathResolve(..) if analysis.analyze_mode.is_tracing() => { let parent_path = origin.origin_path().owned().await?.parent(); - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; let linked_func_call = state .link_value( @@ -2086,7 +2083,7 @@ async fn handle_call) + Send + Sync>( let pat = js_value_to_pattern(&linked_func_call); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("path.resolve({args}) is very dynamic{hints}",), @@ -2116,7 +2113,7 @@ async fn handle_call) + Send + Sync>( if context_path.path.contains("node_modules/node-gyp") { return Ok(()); } - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; let linked_func_call = state .link_value( JsValue::call( @@ -2128,7 +2125,7 @@ async fn handle_call) + Send + Sync>( .await?; let pat = js_value_to_pattern(&linked_func_call); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("path.join({args}) is very dynamic{hints}",), @@ -2154,10 +2151,10 @@ async fn handle_call) + Send + Sync>( WellKnownFunctionKind::ChildProcessSpawnMethod(name) if analysis.analyze_mode.is_tracing() => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; // Is this specifically `spawn(process.argv[0], ['-e', ...])`? - if is_invoking_node_process_eval(&args) { + if is_invoking_node_process_eval(args) { return Ok(()); } @@ -2209,7 +2206,7 @@ async fn handle_call) + Send + Sync>( ); } if show_dynamic_warning { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("child_process.{name}({args}) is very dynamic{hints}",), @@ -2221,7 +2218,7 @@ async fn handle_call) + Send + Sync>( } return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("child_process.{name}({args}) is not statically analyze-able{hints}",), @@ -2231,12 +2228,12 @@ async fn handle_call) + Send + Sync>( ) } WellKnownFunctionKind::ChildProcessFork if analysis.analyze_mode.is_tracing() => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if !args.is_empty() { let first_arg = &args[0]; let pat = js_value_to_pattern(first_arg); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("child_process.fork({args}) is very dynamic{hints}",), @@ -2261,7 +2258,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("child_process.fork({args}) is not statically analyze-able{hints}",), @@ -2273,12 +2270,12 @@ async fn handle_call) + Send + Sync>( WellKnownFunctionKind::NodePreGypFind if analysis.analyze_mode.is_tracing() => { use turbopack_resolve::node_native_binding::NodePreGypConfigReference; - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 1 { let first_arg = &args[0]; let pat = js_value_to_pattern(first_arg); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("node-pre-gyp.find({args}) is very dynamic{hints}",), @@ -2302,7 +2299,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!( @@ -2317,7 +2314,7 @@ async fn handle_call) + Send + Sync>( WellKnownFunctionKind::NodeGypBuild if analysis.analyze_mode.is_tracing() => { use turbopack_resolve::node_native_binding::NodeGypBuildReference; - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 1 { let first_arg = state .link_value(args[0].clone(), ImportAttributes::empty_ref()) @@ -2342,7 +2339,7 @@ async fn handle_call) + Send + Sync>( return Ok(()); } } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!( @@ -2356,7 +2353,7 @@ async fn handle_call) + Send + Sync>( WellKnownFunctionKind::NodeBindings if analysis.analyze_mode.is_tracing() => { use turbopack_resolve::node_native_binding::NodeBindingsReference; - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 1 { let first_arg = state .link_value(args[0].clone(), ImportAttributes::empty_ref()) @@ -2374,7 +2371,7 @@ async fn handle_call) + Send + Sync>( return Ok(()); } } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("require('bindings')({args}) is not statically analyze-able{hints}",), @@ -2384,14 +2381,14 @@ async fn handle_call) + Send + Sync>( ) } WellKnownFunctionKind::NodeExpressSet if analysis.analyze_mode.is_tracing() => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 2 && let Some(s) = args.first().and_then(|arg| arg.as_str()) { let pkg_or_dir = args.get(1).unwrap(); let pat = js_value_to_pattern(pkg_or_dir); if !pat.has_constant_parts() { - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!("require('express')().set({args}) is very dynamic{hints}",), @@ -2457,7 +2454,7 @@ async fn handle_call) + Send + Sync>( _ => {} } } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!( @@ -2471,7 +2468,7 @@ async fn handle_call) + Send + Sync>( WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir if analysis.analyze_mode.is_tracing() => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if let Some(p) = args.first().and_then(|arg| arg.as_str()) { let abs_pattern = if p.starts_with("/ROOT/") { Pattern::Constant(format!("{p}/intl").into()) @@ -2504,7 +2501,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!( @@ -2517,7 +2514,7 @@ async fn handle_call) + Send + Sync>( ) } WellKnownFunctionKind::NodeResolveFrom if analysis.analyze_mode.is_tracing() => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 2 && args.get(1).and_then(|arg| arg.as_str()).is_some() { analysis.add_reference( CjsAssetReference::new( @@ -2531,7 +2528,7 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!( @@ -2543,7 +2540,7 @@ async fn handle_call) + Send + Sync>( ) } WellKnownFunctionKind::NodeProtobufLoad if analysis.analyze_mode.is_tracing() => { - let args = linked_args(unlinked_args).await?; + let args = linked_args().await?; if args.len() == 2 && let Some(JsValue::Object { parts, .. }) = args.get(1) { @@ -2577,7 +2574,7 @@ async fn handle_call) + Send + Sync>( return Ok(()); } - let (args, hints) = explain_args(&args); + let (args, hints) = explain_args(args); handler.span_warn_with_code( span, &format!( @@ -2594,6 +2591,23 @@ async fn handle_call) + Send + Sync>( Ok(()) } + // Create a OnceCell to cache linked args across multiple calls + let linked_args_cache = OnceCell::new(); + + // Create the lazy linking closure that will be passed to handle_well_known_function_call + let linked_args = || async { + linked_args_cache + .get_or_try_init(|| async { + unlinked_args + .iter() + .cloned() + .map(|arg| state.link_value(arg, ImportAttributes::empty_ref())) + .try_join() + .await + }) + .await + }; + match func { JsValue::Alternatives { total_nodes: _, @@ -2601,28 +2615,25 @@ async fn handle_call) + Send + Sync>( logical_property: _, } => { for alt in values { - match alt { - JsValue::WellKnownFunction(wkf) => { - handle_well_known_function_call( - wkf, - new, - unlinked_args.clone(), - handler, - span, - ignore_dynamic_requests, - analysis, - origin, - compile_time_info, - url_rewrite_behavior, - source, - ast_path, - in_try, - state, - collect_affecting_sources, - ) - .await?; - } - _ => {} + if let JsValue::WellKnownFunction(wkf) = alt { + handle_well_known_function_call( + wkf, + new, + &linked_args, + handler, + span, + ignore_dynamic_requests, + analysis, + origin, + compile_time_info, + url_rewrite_behavior, + source, + ast_path, + in_try, + state, + collect_affecting_sources, + ) + .await?; } } } @@ -2630,7 +2641,7 @@ async fn handle_call) + Send + Sync>( handle_well_known_function_call( wkf, new, - unlinked_args, + &linked_args, handler, span, ignore_dynamic_requests, @@ -2886,10 +2897,10 @@ async fn analyze_amd_define( handler: &Handler, span: Span, ast_path: &[AstParentKind], - args: Vec, + args: &[JsValue], in_try: bool, ) -> Result<()> { - match &args[..] { + match args { [JsValue::Constant(id), JsValue::Array { items: deps, .. }, _] if id.as_str().is_some() => { analyze_amd_define_with_deps( source, From da96e23e32acf5313ec92b19319254a1658a02a3 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Fri, 14 Nov 2025 07:45:56 -0800 Subject: [PATCH 4/8] make the debug assertions conditional --- .../src/references/exports_info.rs | 8 +++-- .../src/references/mod.rs | 36 ++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/references/exports_info.rs b/turbopack/crates/turbopack-ecmascript/src/references/exports_info.rs index 95abefb922e27..d37379a9f91c1 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/exports_info.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/exports_info.rs @@ -23,7 +23,9 @@ use crate::{ /// initialize the binding a single time. /// /// This singleton behavior must be enforced by the caller! -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct ExportsInfoBinding {} impl ExportsInfoBinding { @@ -89,7 +91,9 @@ impl From for CodeGen { /// /// There can be many references, and they appear at any nesting in the file. But all references /// refer to the same mutable object. -#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)] +#[derive( + PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, +)] pub struct ExportsInfoRef { ast_path: AstPath, } diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index 6582a8a4ace2d..23a88005b865f 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -211,6 +211,13 @@ impl AnalyzeEcmascriptModuleResult { } } +/// In debug builds, use FxIndexSet to catch duplicate code gens +/// In release builds, use Vec for better performance +#[cfg(debug_assertions)] +type CodeGenCollection = FxIndexSet; +#[cfg(not(debug_assertions))] +type CodeGenCollection = Vec; + /// A temporary analysis result builder to pass around, to be turned into an /// `Vc` eventually. pub struct AnalyzeEcmascriptModuleResultBuilder { @@ -227,7 +234,7 @@ pub struct AnalyzeEcmascriptModuleResultBuilder { // This caches repeated access because EsmAssetReference::new is not a turbo task function. esm_references_rewritten: FxHashMap>>, - code_gens: FxIndexSet, + code_gens: CodeGenCollection, exports: EcmascriptExports, async_module: ResolvedVc, successful: bool, @@ -293,12 +300,19 @@ impl AnalyzeEcmascriptModuleResultBuilder { C: Into, { if self.analyze_mode.is_code_gen() { - let (index, added) = self.code_gens.insert_full(code_gen.into()); - debug_assert!( - added, - "Duplicate code gen added: {:?}", - self.code_gens.get_index(index) - ); + #[cfg(debug_assertions)] + { + let (index, added) = self.code_gens.insert_full(code_gen.into()); + debug_assert!( + added, + "Duplicate code gen added: {:?}", + self.code_gens.get_index(index) + ); + } + #[cfg(not(debug_assertions))] + { + self.code_gens.push(code_gen.into()); + } } } @@ -413,6 +427,12 @@ impl AnalyzeEcmascriptModuleResultBuilder { } self.code_gens.shrink_to_fit(); + + #[cfg(debug_assertions)] + let code_generation = self.code_gens.into_iter().collect::>(); + #[cfg(not(debug_assertions))] + let code_generation = self.code_gens; + Ok(AnalyzeEcmascriptModuleResult::cell( AnalyzeEcmascriptModuleResult { references, @@ -421,7 +441,7 @@ impl AnalyzeEcmascriptModuleResultBuilder { esm_reexport_references: ResolvedVc::cell( esm_reexport_references.unwrap_or_default(), ), - code_generation: ResolvedVc::cell(self.code_gens.into_iter().collect::>()), + code_generation: ResolvedVc::cell(code_generation), exports: self.exports.resolved_cell(), async_module: self.async_module, has_side_effect_free_directive: self.has_side_effect_free_directive, From 557c3f7d723091982256ff4c3b66a10062a0f319 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Fri, 14 Nov 2025 09:11:30 -0800 Subject: [PATCH 5/8] Fix a bug in how unreachable effects were created for finally blocks --- .../turbopack-ecmascript/src/analyzer/graph.rs | 8 +++++--- .../turbopack-ecmascript/src/references/mod.rs | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs index 1b2173f7e7ecf..fc8d456c9f6c7 100644 --- a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs +++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs @@ -2428,9 +2428,11 @@ impl VisitAstPath for Analyzer<'_> { self.effects.append(&mut block); self.effects.append(&mut handler); if let Some(finalizer) = stmt.finalizer.as_ref() { - let mut ast_path = - ast_path.with_guard(AstParentNodeRef::TryStmt(stmt, TryStmtField::Finalizer)); - finalizer.visit_with_ast_path(self, &mut ast_path); + { + let mut ast_path = + ast_path.with_guard(AstParentNodeRef::TryStmt(stmt, TryStmtField::Finalizer)); + finalizer.visit_with_ast_path(self, &mut ast_path); + } // If a finally block early returns the parent block does too. if self.end_early_return_block() { self.early_return_stack.push(EarlyReturn::Always { diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index 23a88005b865f..41ba84edf3093 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -220,7 +220,7 @@ type CodeGenCollection = Vec; /// A temporary analysis result builder to pass around, to be turned into an /// `Vc` eventually. -pub struct AnalyzeEcmascriptModuleResultBuilder { +struct AnalyzeEcmascriptModuleResultBuilder { analyze_mode: AnalyzeMode, references: FxIndexSet>>, @@ -240,10 +240,12 @@ pub struct AnalyzeEcmascriptModuleResultBuilder { successful: bool, source_map: Option>>, has_side_effect_free_directive: bool, + #[cfg(debug_assertions)] + ident: RcStr, } impl AnalyzeEcmascriptModuleResultBuilder { - pub fn new(analyze_mode: AnalyzeMode) -> Self { + fn new(analyze_mode: AnalyzeMode) -> Self { Self { analyze_mode, references: Default::default(), @@ -258,6 +260,8 @@ impl AnalyzeEcmascriptModuleResultBuilder { successful: false, source_map: None, has_side_effect_free_directive: false, + #[cfg(debug_assertions)] + ident: Default::default(), } } @@ -305,8 +309,9 @@ impl AnalyzeEcmascriptModuleResultBuilder { let (index, added) = self.code_gens.insert_full(code_gen.into()); debug_assert!( added, - "Duplicate code gen added: {:?}", - self.code_gens.get_index(index) + "Duplicate code gen added: {:?} in {}", + self.code_gens.get_index(index).unwrap(), + self.ident ); } #[cfg(not(debug_assertions))] @@ -553,8 +558,11 @@ async fn analyze_ecmascript_module_internal( let analyze_mode = options.analyze_mode; let origin = ResolvedVc::upcast::>(module); - let mut analysis = AnalyzeEcmascriptModuleResultBuilder::new(analyze_mode); let path = &*origin.origin_path().await?; + let mut analysis = AnalyzeEcmascriptModuleResultBuilder::new(analyze_mode); + if cfg!(debug_assertions) { + analysis.ident = source.ident().to_string().owned().await?; + } // Is this a typescript file that requires analyzing type references? let analyze_types = match &ty { From 454a043d7586f9b8dc248eae075c2f3c71160d8b Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Fri, 14 Nov 2025 09:33:19 -0800 Subject: [PATCH 6/8] clippy! --- turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs index fc8d456c9f6c7..fb7fa8cc38e9a 100644 --- a/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs +++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs @@ -2437,7 +2437,7 @@ impl VisitAstPath for Analyzer<'_> { if self.end_early_return_block() { self.early_return_stack.push(EarlyReturn::Always { prev_effects: take(&mut self.effects), - start_ast_path: as_parent_path(&ast_path), + start_ast_path: as_parent_path(ast_path), }); } }; From df9234210a9071652e3236e4499cf9ac5d04f268 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Fri, 14 Nov 2025 09:36:55 -0800 Subject: [PATCH 7/8] fix release build --- turbopack/crates/turbopack-ecmascript/src/references/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index 41ba84edf3093..1592a8e03abba 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -560,7 +560,8 @@ async fn analyze_ecmascript_module_internal( let origin = ResolvedVc::upcast::>(module); let path = &*origin.origin_path().await?; let mut analysis = AnalyzeEcmascriptModuleResultBuilder::new(analyze_mode); - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { analysis.ident = source.ident().to_string().owned().await?; } From 89d47600c8959b9ef3029043f4c43d1be245b7f7 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Fri, 14 Nov 2025 09:44:57 -0800 Subject: [PATCH 8/8] try moving handle_well_known_function_call to improve diff alignment --- .../src/references/mod.rs | 1612 ++++++++--------- 1 file changed, 799 insertions(+), 813 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index 1592a8e03abba..1e5fdfdbdb86e 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -1687,373 +1687,204 @@ async fn handle_call) + Send + Sync>( }) .collect::>(); - async fn handle_well_known_function_call<'a, F, Fut>( - func: WellKnownFunctionKind, - new: bool, - linked_args: &F, - handler: &Handler, - span: Span, - ignore_dynamic_requests: bool, - analysis: &mut AnalyzeEcmascriptModuleResultBuilder, - origin: ResolvedVc>, - compile_time_info: ResolvedVc, - url_rewrite_behavior: Option, - source: ResolvedVc>, - ast_path: &[AstParentKind], - in_try: bool, - state: &'a AnalysisState<'a>, - collect_affecting_sources: bool, - ) -> Result<()> - where - F: Fn() -> Fut, - Fut: Future>>, - { - fn explain_args(args: &[JsValue]) -> (String, String) { - JsValue::explain_args(args, 10, 2) - } - - let get_traced_project_dir = async || -> Result { - // readFileSync("./foo") should always be relative to the project root, but this is - // dangerous inside of node_modules as it can cause a lot of false positives in the - // tracing, if some package does `path.join(dynamic)`, it would include - // everything from the project root as well. - // - // Also, when there's no cwd set (i.e. in a tracing-specific module context, as we - // shouldn't assume a `process.cwd()` for all of node_modules), fallback to - // the source file directory. This still allows relative file accesses, just - // not from the project root. - if state.allow_project_root_tracing - && let Some(cwd) = compile_time_info.environment().cwd().owned().await? - { - Ok(cwd) - } else { - Ok(source.ident().path().await?.parent()) - } - }; - - let get_issue_source = - || IssueSource::from_swc_offsets(source, span.lo.to_u32(), span.hi.to_u32()); - if new { - match func { - WellKnownFunctionKind::URLConstructor => { - let args = linked_args().await?; - if let [ - url, - JsValue::Member( - _, - box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta), - box JsValue::Constant(super::analyzer::ConstantValue::Str(meta_prop)), - ), - ] = &args[..] - && meta_prop.as_str() == "url" - { - let pat = js_value_to_pattern(url); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("new URL({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NEW_URL_IMPORT_META - .to_string(), - ), - ); - if ignore_dynamic_requests { - return Ok(()); - } - } - analysis.add_reference_code_gen( - UrlAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - *compile_time_info.environment().rendering().await?, - issue_source(source, span), - in_try, - url_rewrite_behavior.unwrap_or(UrlRewriteBehavior::Relative), - ), - ast_path.to_vec().into(), - ); - } - return Ok(()); - } - WellKnownFunctionKind::WorkerConstructor => { - let args = linked_args().await?; - if let Some(url @ JsValue::Url(_, JsValueUrlKind::Relative)) = args.first() { - let pat = js_value_to_pattern(url); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("new Worker({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), - ), - ); - if ignore_dynamic_requests { - return Ok(()); - } - } + // Create a OnceCell to cache linked args across multiple calls + let linked_args_cache = OnceCell::new(); - if *compile_time_info.environment().rendering().await? == Rendering::Client - { - analysis.add_reference_code_gen( - WorkerAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - issue_source(source, span), - in_try, - ), - ast_path.to_vec().into(), - ); - } + // Create the lazy linking closure that will be passed to handle_well_known_function_call + let linked_args = || async { + linked_args_cache + .get_or_try_init(|| async { + unlinked_args + .iter() + .cloned() + .map(|arg| state.link_value(arg, ImportAttributes::empty_ref())) + .try_join() + .await + }) + .await + }; - return Ok(()); - } - // Ignore (e.g. dynamic parameter or string literal), just as Webpack does - return Ok(()); - } - WellKnownFunctionKind::NodeWorkerConstructor - if analysis.analyze_mode.is_tracing() => - { - // Only for tracing, not for bundling (yet?) - let args = linked_args().await?; - if !args.is_empty() { - let pat = js_value_to_pattern(&args[0]); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("new Worker({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), - ), - ); - if ignore_dynamic_requests { - return Ok(()); - } - } - analysis.add_reference( - FilePathModuleReference::new( - origin.asset_context(), - get_traced_project_dir().await?, - Pattern::new(pat), - collect_affecting_sources, - get_issue_source(), - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( + match func { + JsValue::Alternatives { + total_nodes: _, + values, + logical_property: _, + } => { + for alt in values { + if let JsValue::WellKnownFunction(wkf) = alt { + handle_well_known_function_call( + wkf, + new, + &linked_args, + handler, span, - &format!("new Worker({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), - ), - ); - // Ignore (e.g. dynamic parameter or string literal) - return Ok(()); + ignore_dynamic_requests, + analysis, + origin, + compile_time_info, + url_rewrite_behavior, + source, + ast_path, + in_try, + state, + collect_affecting_sources, + ) + .await?; } - _ => {} } + } + JsValue::WellKnownFunction(wkf) => { + handle_well_known_function_call( + wkf, + new, + &linked_args, + handler, + span, + ignore_dynamic_requests, + analysis, + origin, + compile_time_info, + url_rewrite_behavior, + source, + ast_path, + in_try, + state, + collect_affecting_sources, + ) + .await?; + } + _ => {} + } - return Ok(()); + Ok(()) +} + +async fn handle_well_known_function_call<'a, F, Fut>( + func: WellKnownFunctionKind, + new: bool, + linked_args: &F, + handler: &Handler, + span: Span, + ignore_dynamic_requests: bool, + analysis: &mut AnalyzeEcmascriptModuleResultBuilder, + origin: ResolvedVc>, + compile_time_info: ResolvedVc, + url_rewrite_behavior: Option, + source: ResolvedVc>, + ast_path: &[AstParentKind], + in_try: bool, + state: &'a AnalysisState<'a>, + collect_affecting_sources: bool, +) -> Result<()> +where + F: Fn() -> Fut, + Fut: Future>>, +{ + fn explain_args(args: &[JsValue]) -> (String, String) { + JsValue::explain_args(args, 10, 2) + } + + let get_traced_project_dir = async || -> Result { + // readFileSync("./foo") should always be relative to the project root, but this is + // dangerous inside of node_modules as it can cause a lot of false positives in the + // tracing, if some package does `path.join(dynamic)`, it would include + // everything from the project root as well. + // + // Also, when there's no cwd set (i.e. in a tracing-specific module context, as we + // shouldn't assume a `process.cwd()` for all of node_modules), fallback to + // the source file directory. This still allows relative file accesses, just + // not from the project root. + if state.allow_project_root_tracing + && let Some(cwd) = compile_time_info.environment().cwd().owned().await? + { + Ok(cwd) + } else { + Ok(source.ident().path().await?.parent()) } + }; + let get_issue_source = + || IssueSource::from_swc_offsets(source, span.lo.to_u32(), span.hi.to_u32()); + if new { match func { - WellKnownFunctionKind::Import => { + WellKnownFunctionKind::URLConstructor => { let args = linked_args().await?; - if args.len() == 1 || args.len() == 2 { - let pat = js_value_to_pattern(&args[0]); - let options = args.get(1); - let import_annotations = options - .and_then(|options| { - if let JsValue::Object { parts, .. } = options { - parts.iter().find_map(|part| { - if let ObjectPart::KeyValue( - JsValue::Constant(super::analyzer::ConstantValue::Str(key)), - value, - ) = part - && key.as_str() == "with" - { - return Some(value); - } - None - }) - } else { - None - } - }) - .and_then(ImportAnnotations::parse_dynamic) - .unwrap_or_default(); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("import({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), - ), - ); - if ignore_dynamic_requests { - analysis.add_code_gen(DynamicExpression::new_promise( - ast_path.to_vec().into(), - )); - return Ok(()); - } - } - analysis.add_reference_code_gen( - EsmAsyncAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - issue_source(source, span), - import_annotations, - in_try, - state.import_externals, - ), - ast_path.to_vec().into(), - ); - return Ok(()); - } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("import({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), + if let [ + url, + JsValue::Member( + _, + box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta), + box JsValue::Constant(super::analyzer::ConstantValue::Str(meta_prop)), ), - ) - } - WellKnownFunctionKind::Require => { - let args = linked_args().await?; - if args.len() == 1 { - let pat = js_value_to_pattern(&args[0]); + ] = &args[..] + && meta_prop.as_str() == "url" + { + let pat = js_value_to_pattern(url); if !pat.has_constant_parts() { let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!("require({args}) is very dynamic{hints}",), + &format!("new URL({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::REQUIRE.to_string(), + errors::failed_to_analyze::ecmascript::NEW_URL_IMPORT_META + .to_string(), ), ); if ignore_dynamic_requests { - analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); return Ok(()); } } analysis.add_reference_code_gen( - CjsRequireAssetReference::new( + UrlAssetReference::new( origin, Request::parse(pat).to_resolved().await?, + *compile_time_info.environment().rendering().await?, issue_source(source, span), in_try, + url_rewrite_behavior.unwrap_or(UrlRewriteBehavior::Relative), ), ast_path.to_vec().into(), ); - return Ok(()); } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("require({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error(errors::failed_to_analyze::ecmascript::REQUIRE.to_string()), - ) - } - WellKnownFunctionKind::Define => { - analyze_amd_define( - source, - analysis, - origin, - handler, - span, - ast_path, - linked_args().await?, - in_try, - ) - .await?; + return Ok(()); } - - WellKnownFunctionKind::RequireResolve => { + WellKnownFunctionKind::WorkerConstructor => { let args = linked_args().await?; - if args.len() == 1 || args.len() == 2 { - // TODO error TP1003 require.resolve(???*0*, {"paths": [???*1*]}) is not - // statically analyze-able with ignore_dynamic_requests = - // true - let pat = js_value_to_pattern(&args[0]); + if let Some(url @ JsValue::Url(_, JsValueUrlKind::Relative)) = args.first() { + let pat = js_value_to_pattern(url); if !pat.has_constant_parts() { let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!("require.resolve({args}) is very dynamic{hints}",), + &format!("new Worker({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), + errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), ), ); if ignore_dynamic_requests { - analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); return Ok(()); } } - analysis.add_reference_code_gen( - CjsRequireResolveAssetReference::new( - origin, - Request::parse(pat).to_resolved().await?, - issue_source(source, span), - in_try, - ), - ast_path.to_vec().into(), - ); - return Ok(()); - } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("require.resolve({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), - ), - ) - } - WellKnownFunctionKind::RequireContext => { - let args = linked_args().await?; - let options = match parse_require_context(args) { - Ok(options) => options, - Err(err) => { - let (args, hints) = explain_args(args); - handler.span_err_with_code( - span, - &format!( - "require.context({args}) is not statically analyze-able: {}{hints}", - PrettyPrintError(&err) - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::REQUIRE_CONTEXT.to_string(), + if *compile_time_info.environment().rendering().await? == Rendering::Client { + analysis.add_reference_code_gen( + WorkerAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + issue_source(source, span), + in_try, ), + ast_path.to_vec().into(), ); - return Ok(()); } - }; - analysis.add_reference_code_gen( - RequireContextAssetReference::new( - source, - origin, - options.dir, - options.include_subdirs, - options.filter.cell(), - Some(issue_source(source, span)), - in_try, - ) - .await?, - ast_path.to_vec().into(), - ); + return Ok(()); + } + // Ignore (e.g. dynamic parameter or string literal), just as Webpack does + return Ok(()); } - - WellKnownFunctionKind::FsReadMethod(name) if analysis.analyze_mode.is_tracing() => { + WellKnownFunctionKind::NodeWorkerConstructor if analysis.analyze_mode.is_tracing() => { + // Only for tracing, not for bundling (yet?) let args = linked_args().await?; if !args.is_empty() { let pat = js_value_to_pattern(&args[0]); @@ -2061,9 +1892,9 @@ async fn handle_call) + Send + Sync>( let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!("fs.{name}({args}) is very dynamic{hints}",), + &format!("new Worker({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), + errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(), ), ); if ignore_dynamic_requests { @@ -2071,7 +1902,8 @@ async fn handle_call) + Send + Sync>( } } analysis.add_reference( - FileSourceReference::new( + FilePathModuleReference::new( + origin.asset_context(), get_traced_project_dir().await?, Pattern::new(pat), collect_affecting_sources, @@ -2085,81 +1917,221 @@ async fn handle_call) + Send + Sync>( let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!("fs.{name}({args}) is not statically analyze-able{hints}",), + &format!("new Worker({args}) is not statically analyze-able{hints}",), DiagnosticId::Error( errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), ), - ) + ); + // Ignore (e.g. dynamic parameter or string literal) + return Ok(()); } + _ => {} + } - WellKnownFunctionKind::PathResolve(..) if analysis.analyze_mode.is_tracing() => { - let parent_path = origin.origin_path().owned().await?.parent(); - let args = linked_args().await?; - - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction( - WellKnownFunctionKind::PathResolve(Box::new( - parent_path.path.as_str().into(), - )), - )), - args.clone(), - ), - ImportAttributes::empty_ref(), - ) - .await?; + return Ok(()); + } - let pat = js_value_to_pattern(&linked_func_call); + match func { + WellKnownFunctionKind::Import => { + let args = linked_args().await?; + if args.len() == 1 || args.len() == 2 { + let pat = js_value_to_pattern(&args[0]); + let options = args.get(1); + let import_annotations = options + .and_then(|options| { + if let JsValue::Object { parts, .. } = options { + parts.iter().find_map(|part| { + if let ObjectPart::KeyValue( + JsValue::Constant(super::analyzer::ConstantValue::Str(key)), + value, + ) = part + && key.as_str() == "with" + { + return Some(value); + } + None + }) + } else { + None + } + }) + .and_then(ImportAnnotations::parse_dynamic) + .unwrap_or_default(); if !pat.has_constant_parts() { let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!("path.resolve({args}) is very dynamic{hints}",), + &format!("import({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), + errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), ), ); if ignore_dynamic_requests { + analysis + .add_code_gen(DynamicExpression::new_promise(ast_path.to_vec().into())); return Ok(()); } } - analysis.add_reference( - DirAssetReference::new( - get_traced_project_dir().await?, - Pattern::new(pat), - get_issue_source(), - ) - .to_resolved() - .await?, + analysis.add_reference_code_gen( + EsmAsyncAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + issue_source(source, span), + import_annotations, + in_try, + state.import_externals, + ), + ast_path.to_vec().into(), + ); + return Ok(()); + } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("import({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::DYNAMIC_IMPORT.to_string(), + ), + ) + } + WellKnownFunctionKind::Require => { + let args = linked_args().await?; + if args.len() == 1 { + let pat = js_value_to_pattern(&args[0]); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::REQUIRE.to_string(), + ), + ); + if ignore_dynamic_requests { + analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); + return Ok(()); + } + } + analysis.add_reference_code_gen( + CjsRequireAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + issue_source(source, span), + in_try, + ), + ast_path.to_vec().into(), ); return Ok(()); } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error(errors::failed_to_analyze::ecmascript::REQUIRE.to_string()), + ) + } + WellKnownFunctionKind::Define => { + analyze_amd_define( + source, + analysis, + origin, + handler, + span, + ast_path, + linked_args().await?, + in_try, + ) + .await?; + } - WellKnownFunctionKind::PathJoin if analysis.analyze_mode.is_tracing() => { - let context_path = source.ident().path().await?; - // ignore path.join in `node-gyp`, it will includes too many files - if context_path.path.contains("node_modules/node-gyp") { - return Ok(()); + WellKnownFunctionKind::RequireResolve => { + let args = linked_args().await?; + if args.len() == 1 || args.len() == 2 { + // TODO error TP1003 require.resolve(???*0*, {"paths": [???*1*]}) is not + // statically analyze-able with ignore_dynamic_requests = + // true + let pat = js_value_to_pattern(&args[0]); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require.resolve({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), + ), + ); + if ignore_dynamic_requests { + analysis.add_code_gen(DynamicExpression::new(ast_path.to_vec().into())); + return Ok(()); + } } - let args = linked_args().await?; - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::PathJoin)), - args.clone(), + analysis.add_reference_code_gen( + CjsRequireResolveAssetReference::new( + origin, + Request::parse(pat).to_resolved().await?, + issue_source(source, span), + in_try, + ), + ast_path.to_vec().into(), + ); + return Ok(()); + } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require.resolve({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::REQUIRE_RESOLVE.to_string(), + ), + ) + } + + WellKnownFunctionKind::RequireContext => { + let args = linked_args().await?; + let options = match parse_require_context(args) { + Ok(options) => options, + Err(err) => { + let (args, hints) = explain_args(args); + handler.span_err_with_code( + span, + &format!( + "require.context({args}) is not statically analyze-able: {}{hints}", + PrettyPrintError(&err) ), - ImportAttributes::empty_ref(), - ) - .await?; - let pat = js_value_to_pattern(&linked_func_call); + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::REQUIRE_CONTEXT.to_string(), + ), + ); + return Ok(()); + } + }; + + analysis.add_reference_code_gen( + RequireContextAssetReference::new( + source, + origin, + options.dir, + options.include_subdirs, + options.filter.cell(), + Some(issue_source(source, span)), + in_try, + ) + .await?, + ast_path.to_vec().into(), + ); + } + + WellKnownFunctionKind::FsReadMethod(name) if analysis.analyze_mode.is_tracing() => { + let args = linked_args().await?; + if !args.is_empty() { + let pat = js_value_to_pattern(&args[0]); if !pat.has_constant_parts() { let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!("path.join({args}) is very dynamic{hints}",), + &format!("fs.{name}({args}) is very dynamic{hints}",), DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), + errors::failed_to_analyze::ecmascript::FS_METHOD.to_string(), ), ); if ignore_dynamic_requests { @@ -2167,9 +2139,10 @@ async fn handle_call) + Send + Sync>( } } analysis.add_reference( - DirAssetReference::new( + FileSourceReference::new( get_traced_project_dir().await?, Pattern::new(pat), + collect_affecting_sources, get_issue_source(), ) .to_resolved() @@ -2177,521 +2150,534 @@ async fn handle_call) + Send + Sync>( ); return Ok(()); } - WellKnownFunctionKind::ChildProcessSpawnMethod(name) - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args().await?; + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("fs.{name}({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error(errors::failed_to_analyze::ecmascript::FS_METHOD.to_string()), + ) + } - // Is this specifically `spawn(process.argv[0], ['-e', ...])`? - if is_invoking_node_process_eval(args) { - return Ok(()); - } + WellKnownFunctionKind::PathResolve(..) if analysis.analyze_mode.is_tracing() => { + let parent_path = origin.origin_path().owned().await?.parent(); + let args = linked_args().await?; - if !args.is_empty() { - let mut show_dynamic_warning = false; - let pat = js_value_to_pattern(&args[0]); - if pat.is_match_ignore_dynamic("node") && args.len() >= 2 { - let first_arg = - JsValue::member(Box::new(args[1].clone()), Box::new(0_f64.into())); - let first_arg = state - .link_value(first_arg, ImportAttributes::empty_ref()) - .await?; - let pat = js_value_to_pattern(&first_arg); - let dynamic = !pat.has_constant_parts(); - if dynamic { - show_dynamic_warning = true; - } - if !dynamic || !ignore_dynamic_requests { - analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(pat), - issue_source(source, span), - in_try, - ) - .to_resolved() - .await?, - ); - } - } - let dynamic = !pat.has_constant_parts(); - if dynamic { - show_dynamic_warning = true; - } - if !dynamic || !ignore_dynamic_requests { - analysis.add_reference( - FileSourceReference::new( - get_traced_project_dir().await?, - Pattern::new(pat), - collect_affecting_sources, - IssueSource::from_swc_offsets( - source, - span.lo.to_u32(), - span.hi.to_u32(), - ), - ) - .to_resolved() - .await?, - ); - } - if show_dynamic_warning { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("child_process.{name}({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN - .to_string(), - ), - ); - } - return Ok(()); - } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("child_process.{name}({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction( + WellKnownFunctionKind::PathResolve(Box::new( + parent_path.path.as_str().into(), + )), + )), + args.clone(), ), + ImportAttributes::empty_ref(), ) - } - WellKnownFunctionKind::ChildProcessFork if analysis.analyze_mode.is_tracing() => { - let args = linked_args().await?; - if !args.is_empty() { - let first_arg = &args[0]; - let pat = js_value_to_pattern(first_arg); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("child_process.fork({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN - .to_string(), - ), - ); - if ignore_dynamic_requests { - return Ok(()); - } - } - analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(pat), - issue_source(source, span), - in_try, - ) - .to_resolved() - .await?, - ); - return Ok(()); - } + .await?; + + let pat = js_value_to_pattern(&linked_func_call); + if !pat.has_constant_parts() { let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!("child_process.fork({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), + &format!("path.resolve({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), ), - ) - } - WellKnownFunctionKind::NodePreGypFind if analysis.analyze_mode.is_tracing() => { - use turbopack_resolve::node_native_binding::NodePreGypConfigReference; - - let args = linked_args().await?; - if args.len() == 1 { - let first_arg = &args[0]; - let pat = js_value_to_pattern(first_arg); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("node-pre-gyp.find({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND - .to_string(), - ), - ); - // Always ignore this dynamic request - return Ok(()); - } - analysis.add_reference( - NodePreGypConfigReference::new( - origin.origin_path().await?.parent(), - Pattern::new(pat), - compile_time_info.environment().compile_target(), - collect_affecting_sources, - ) - .to_resolved() - .await?, - ); + ); + if ignore_dynamic_requests { return Ok(()); } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!( - "require('@mapbox/node-pre-gyp').find({args}) is not statically \ - analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND.to_string(), - ), - ) } - WellKnownFunctionKind::NodeGypBuild if analysis.analyze_mode.is_tracing() => { - use turbopack_resolve::node_native_binding::NodeGypBuildReference; + analysis.add_reference( + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(pat), + get_issue_source(), + ) + .to_resolved() + .await?, + ); + return Ok(()); + } - let args = linked_args().await?; - if args.len() == 1 { - let first_arg = state - .link_value(args[0].clone(), ImportAttributes::empty_ref()) - .await?; - if let Some(s) = first_arg.as_str() { - // TODO this resolving should happen within Vc - let current_context = origin - .origin_path() - .await? - .root() - .await? - .join(s.trim_start_matches("/ROOT/"))?; - analysis.add_reference( - NodeGypBuildReference::new( - current_context, - collect_affecting_sources, - compile_time_info.environment().compile_target(), - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - } + WellKnownFunctionKind::PathJoin if analysis.analyze_mode.is_tracing() => { + let context_path = source.ident().path().await?; + // ignore path.join in `node-gyp`, it will includes too many files + if context_path.path.contains("node_modules/node-gyp") { + return Ok(()); + } + let args = linked_args().await?; + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::PathJoin)), + args.clone(), + ), + ImportAttributes::empty_ref(), + ) + .await?; + let pat = js_value_to_pattern(&linked_func_call); + if !pat.has_constant_parts() { let (args, hints) = explain_args(args); handler.span_warn_with_code( span, - &format!( - "require('node-gyp-build')({args}) is not statically analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_GYP_BUILD.to_string(), + &format!("path.join({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::PATH_METHOD.to_string(), ), + ); + if ignore_dynamic_requests { + return Ok(()); + } + } + analysis.add_reference( + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(pat), + get_issue_source(), ) + .to_resolved() + .await?, + ); + return Ok(()); + } + WellKnownFunctionKind::ChildProcessSpawnMethod(name) + if analysis.analyze_mode.is_tracing() => + { + let args = linked_args().await?; + + // Is this specifically `spawn(process.argv[0], ['-e', ...])`? + if is_invoking_node_process_eval(args) { + return Ok(()); } - WellKnownFunctionKind::NodeBindings if analysis.analyze_mode.is_tracing() => { - use turbopack_resolve::node_native_binding::NodeBindingsReference; - let args = linked_args().await?; - if args.len() == 1 { + if !args.is_empty() { + let mut show_dynamic_warning = false; + let pat = js_value_to_pattern(&args[0]); + if pat.is_match_ignore_dynamic("node") && args.len() >= 2 { + let first_arg = + JsValue::member(Box::new(args[1].clone()), Box::new(0_f64.into())); let first_arg = state - .link_value(args[0].clone(), ImportAttributes::empty_ref()) + .link_value(first_arg, ImportAttributes::empty_ref()) .await?; - if let Some(s) = first_arg.as_str() { + let pat = js_value_to_pattern(&first_arg); + let dynamic = !pat.has_constant_parts(); + if dynamic { + show_dynamic_warning = true; + } + if !dynamic || !ignore_dynamic_requests { analysis.add_reference( - NodeBindingsReference::new( - origin.origin_path().owned().await?, - s.into(), - collect_affecting_sources, + CjsAssetReference::new( + *origin, + Request::parse(pat), + issue_source(source, span), + in_try, ) .to_resolved() .await?, ); - return Ok(()); } } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("require('bindings')({args}) is not statically analyze-able{hints}",), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_BINDINGS.to_string(), - ), - ) - } - WellKnownFunctionKind::NodeExpressSet if analysis.analyze_mode.is_tracing() => { - let args = linked_args().await?; - if args.len() == 2 - && let Some(s) = args.first().and_then(|arg| arg.as_str()) - { - let pkg_or_dir = args.get(1).unwrap(); - let pat = js_value_to_pattern(pkg_or_dir); - if !pat.has_constant_parts() { - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!("require('express')().set({args}) is very dynamic{hints}",), - DiagnosticId::Lint( - errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), + let dynamic = !pat.has_constant_parts(); + if dynamic { + show_dynamic_warning = true; + } + if !dynamic || !ignore_dynamic_requests { + analysis.add_reference( + FileSourceReference::new( + get_traced_project_dir().await?, + Pattern::new(pat), + collect_affecting_sources, + IssueSource::from_swc_offsets( + source, + span.lo.to_u32(), + span.hi.to_u32(), ), - ); - // Always ignore this dynamic request + ) + .to_resolved() + .await?, + ); + } + if show_dynamic_warning { + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("child_process.{name}({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), + ), + ); + } + return Ok(()); + } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("child_process.{name}({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), + ), + ) + } + WellKnownFunctionKind::ChildProcessFork if analysis.analyze_mode.is_tracing() => { + let args = linked_args().await?; + if !args.is_empty() { + let first_arg = &args[0]; + let pat = js_value_to_pattern(first_arg); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("child_process.fork({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), + ), + ); + if ignore_dynamic_requests { return Ok(()); } - match s { - "views" => { - if let Pattern::Constant(p) = &pat { - let abs_pattern = if p.starts_with("/ROOT/") { - pat - } else { - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction( - WellKnownFunctionKind::PathJoin, - )), - vec![ - JsValue::FreeVar(atom!("__dirname")), - pkg_or_dir.clone(), - ], - ), - ImportAttributes::empty_ref(), - ) - .await?; - js_value_to_pattern(&linked_func_call) - }; - analysis.add_reference( - DirAssetReference::new( - get_traced_project_dir().await?, - Pattern::new(abs_pattern), - get_issue_source(), - ) - .to_resolved() - .await?, - ); - return Ok(()); - } - } - "view engine" => { - if let Some(pkg) = pkg_or_dir.as_str() { - if pkg != "html" { - let pat = js_value_to_pattern(pkg_or_dir); - analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(pat), - issue_source(source, span), - in_try, - ) - .to_resolved() - .await?, - ); - } - return Ok(()); - } - } - _ => {} - } } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!( - "require('express')().set({args}) is not statically analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), - ), - ) + analysis.add_reference( + CjsAssetReference::new( + *origin, + Request::parse(pat), + issue_source(source, span), + in_try, + ) + .to_resolved() + .await?, + ); + return Ok(()); } - WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir - if analysis.analyze_mode.is_tracing() => - { - let args = linked_args().await?; - if let Some(p) = args.first().and_then(|arg| arg.as_str()) { - let abs_pattern = if p.starts_with("/ROOT/") { - Pattern::Constant(format!("{p}/intl").into()) - } else { - let linked_func_call = state - .link_value( - JsValue::call( - Box::new(JsValue::WellKnownFunction( - WellKnownFunctionKind::PathJoin, - )), - vec![ - JsValue::FreeVar(atom!("__dirname")), - p.into(), - atom!("intl").into(), - ], - ), - ImportAttributes::empty_ref(), - ) - .await?; - js_value_to_pattern(&linked_func_call) - }; + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("child_process.fork({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::CHILD_PROCESS_SPAWN.to_string(), + ), + ) + } + WellKnownFunctionKind::NodePreGypFind if analysis.analyze_mode.is_tracing() => { + use turbopack_resolve::node_native_binding::NodePreGypConfigReference; + + let args = linked_args().await?; + if args.len() == 1 { + let first_arg = &args[0]; + let pat = js_value_to_pattern(first_arg); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("node-pre-gyp.find({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND.to_string(), + ), + ); + // Always ignore this dynamic request + return Ok(()); + } + analysis.add_reference( + NodePreGypConfigReference::new( + origin.origin_path().await?.parent(), + Pattern::new(pat), + compile_time_info.environment().compile_target(), + collect_affecting_sources, + ) + .to_resolved() + .await?, + ); + return Ok(()); + } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!( + "require('@mapbox/node-pre-gyp').find({args}) is not statically \ + analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_PRE_GYP_FIND.to_string(), + ), + ) + } + WellKnownFunctionKind::NodeGypBuild if analysis.analyze_mode.is_tracing() => { + use turbopack_resolve::node_native_binding::NodeGypBuildReference; + + let args = linked_args().await?; + if args.len() == 1 { + let first_arg = state + .link_value(args[0].clone(), ImportAttributes::empty_ref()) + .await?; + if let Some(s) = first_arg.as_str() { + // TODO this resolving should happen within Vc + let current_context = origin + .origin_path() + .await? + .root() + .await? + .join(s.trim_start_matches("/ROOT/"))?; analysis.add_reference( - DirAssetReference::new( - get_traced_project_dir().await?, - Pattern::new(abs_pattern), - get_issue_source(), + NodeGypBuildReference::new( + current_context, + collect_affecting_sources, + compile_time_info.environment().compile_target(), ) .to_resolved() .await?, ); return Ok(()); } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( + } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( span, &format!( - "require('strong-globalize').SetRootDir({args}) is not statically \ - analyze-able{hints}", + "require('node-gyp-build')({args}) is not statically analyze-able{hints}", ), DiagnosticId::Error( errors::failed_to_analyze::ecmascript::NODE_GYP_BUILD.to_string(), ), ) - } - WellKnownFunctionKind::NodeResolveFrom if analysis.analyze_mode.is_tracing() => { - let args = linked_args().await?; - if args.len() == 2 && args.get(1).and_then(|arg| arg.as_str()).is_some() { + } + WellKnownFunctionKind::NodeBindings if analysis.analyze_mode.is_tracing() => { + use turbopack_resolve::node_native_binding::NodeBindingsReference; + + let args = linked_args().await?; + if args.len() == 1 { + let first_arg = state + .link_value(args[0].clone(), ImportAttributes::empty_ref()) + .await?; + if let Some(s) = first_arg.as_str() { analysis.add_reference( - CjsAssetReference::new( - *origin, - Request::parse(js_value_to_pattern(&args[1])), - issue_source(source, span), - in_try, + NodeBindingsReference::new( + origin.origin_path().owned().await?, + s.into(), + collect_affecting_sources, ) .to_resolved() .await?, ); return Ok(()); } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!( - "require('resolve-from')({args}) is not statically analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_RESOLVE_FROM.to_string(), - ), - ) } - WellKnownFunctionKind::NodeProtobufLoad if analysis.analyze_mode.is_tracing() => { - let args = linked_args().await?; - if args.len() == 2 - && let Some(JsValue::Object { parts, .. }) = args.get(1) - { - let context_dir = get_traced_project_dir().await?; - let resolved_dirs = parts - .iter() - .filter_map(|object_part| match object_part { - ObjectPart::KeyValue( - JsValue::Constant(key), - JsValue::Array { items: dirs, .. }, - ) if key.as_str() == Some("includeDirs") => { - Some(dirs.iter().filter_map(|dir| dir.as_str())) + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require('bindings')({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_BINDINGS.to_string(), + ), + ) + } + WellKnownFunctionKind::NodeExpressSet if analysis.analyze_mode.is_tracing() => { + let args = linked_args().await?; + if args.len() == 2 + && let Some(s) = args.first().and_then(|arg| arg.as_str()) + { + let pkg_or_dir = args.get(1).unwrap(); + let pat = js_value_to_pattern(pkg_or_dir); + if !pat.has_constant_parts() { + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require('express')().set({args}) is very dynamic{hints}",), + DiagnosticId::Lint( + errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), + ), + ); + // Always ignore this dynamic request + return Ok(()); + } + match s { + "views" => { + if let Pattern::Constant(p) = &pat { + let abs_pattern = if p.starts_with("/ROOT/") { + pat + } else { + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction( + WellKnownFunctionKind::PathJoin, + )), + vec![ + JsValue::FreeVar(atom!("__dirname")), + pkg_or_dir.clone(), + ], + ), + ImportAttributes::empty_ref(), + ) + .await?; + js_value_to_pattern(&linked_func_call) + }; + analysis.add_reference( + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(abs_pattern), + get_issue_source(), + ) + .to_resolved() + .await?, + ); + return Ok(()); + } + } + "view engine" => { + if let Some(pkg) = pkg_or_dir.as_str() { + if pkg != "html" { + let pat = js_value_to_pattern(pkg_or_dir); + analysis.add_reference( + CjsAssetReference::new( + *origin, + Request::parse(pat), + issue_source(source, span), + in_try, + ) + .to_resolved() + .await?, + ); } - _ => None, - }) - .flatten() - .map(|dir| { - DirAssetReference::new( - context_dir.clone(), - Pattern::new(Pattern::Constant(dir.into())), - get_issue_source(), - ) - .to_resolved() - }) - .try_join() - .await?; - - for resolved_dir_ref in resolved_dirs { - analysis.add_reference(resolved_dir_ref); + return Ok(()); + } } - - return Ok(()); + _ => {} } - let (args, hints) = explain_args(args); - handler.span_warn_with_code( - span, - &format!( - "require('@grpc/proto-loader').load({args}) is not statically \ - analyze-able{hints}", - ), - DiagnosticId::Error( - errors::failed_to_analyze::ecmascript::NODE_PROTOBUF_LOADER.to_string(), - ), - ) } - _ => {} - }; - Ok(()) - } - - // Create a OnceCell to cache linked args across multiple calls - let linked_args_cache = OnceCell::new(); - - // Create the lazy linking closure that will be passed to handle_well_known_function_call - let linked_args = || async { - linked_args_cache - .get_or_try_init(|| async { - unlinked_args - .iter() - .cloned() - .map(|arg| state.link_value(arg, ImportAttributes::empty_ref())) - .try_join() - .await - }) - .await - }; - - match func { - JsValue::Alternatives { - total_nodes: _, - values, - logical_property: _, - } => { - for alt in values { - if let JsValue::WellKnownFunction(wkf) = alt { - handle_well_known_function_call( - wkf, - new, - &linked_args, - handler, - span, - ignore_dynamic_requests, - analysis, - origin, - compile_time_info, - url_rewrite_behavior, - source, - ast_path, + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require('express')().set({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_EXPRESS.to_string(), + ), + ) + } + WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir + if analysis.analyze_mode.is_tracing() => + { + let args = linked_args().await?; + if let Some(p) = args.first().and_then(|arg| arg.as_str()) { + let abs_pattern = if p.starts_with("/ROOT/") { + Pattern::Constant(format!("{p}/intl").into()) + } else { + let linked_func_call = state + .link_value( + JsValue::call( + Box::new(JsValue::WellKnownFunction( + WellKnownFunctionKind::PathJoin, + )), + vec![ + JsValue::FreeVar(atom!("__dirname")), + p.into(), + atom!("intl").into(), + ], + ), + ImportAttributes::empty_ref(), + ) + .await?; + js_value_to_pattern(&linked_func_call) + }; + analysis.add_reference( + DirAssetReference::new( + get_traced_project_dir().await?, + Pattern::new(abs_pattern), + get_issue_source(), + ) + .to_resolved() + .await?, + ); + return Ok(()); + } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!( + "require('strong-globalize').SetRootDir({args}) is not statically \ + analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_GYP_BUILD.to_string(), + ), + ) + } + WellKnownFunctionKind::NodeResolveFrom if analysis.analyze_mode.is_tracing() => { + let args = linked_args().await?; + if args.len() == 2 && args.get(1).and_then(|arg| arg.as_str()).is_some() { + analysis.add_reference( + CjsAssetReference::new( + *origin, + Request::parse(js_value_to_pattern(&args[1])), + issue_source(source, span), in_try, - state, - collect_affecting_sources, ) + .to_resolved() + .await?, + ); + return Ok(()); + } + let (args, hints) = explain_args(args); + handler.span_warn_with_code( + span, + &format!("require('resolve-from')({args}) is not statically analyze-able{hints}",), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_RESOLVE_FROM.to_string(), + ), + ) + } + WellKnownFunctionKind::NodeProtobufLoad if analysis.analyze_mode.is_tracing() => { + let args = linked_args().await?; + if args.len() == 2 + && let Some(JsValue::Object { parts, .. }) = args.get(1) + { + let context_dir = get_traced_project_dir().await?; + let resolved_dirs = parts + .iter() + .filter_map(|object_part| match object_part { + ObjectPart::KeyValue( + JsValue::Constant(key), + JsValue::Array { items: dirs, .. }, + ) if key.as_str() == Some("includeDirs") => { + Some(dirs.iter().filter_map(|dir| dir.as_str())) + } + _ => None, + }) + .flatten() + .map(|dir| { + DirAssetReference::new( + context_dir.clone(), + Pattern::new(Pattern::Constant(dir.into())), + get_issue_source(), + ) + .to_resolved() + }) + .try_join() .await?; + + for resolved_dir_ref in resolved_dirs { + analysis.add_reference(resolved_dir_ref); } + + return Ok(()); } - } - JsValue::WellKnownFunction(wkf) => { - handle_well_known_function_call( - wkf, - new, - &linked_args, - handler, + let (args, hints) = explain_args(args); + handler.span_warn_with_code( span, - ignore_dynamic_requests, - analysis, - origin, - compile_time_info, - url_rewrite_behavior, - source, - ast_path, - in_try, - state, - collect_affecting_sources, + &format!( + "require('@grpc/proto-loader').load({args}) is not statically \ + analyze-able{hints}", + ), + DiagnosticId::Error( + errors::failed_to_analyze::ecmascript::NODE_PROTOBUF_LOADER.to_string(), + ), ) - .await?; } _ => {} - } - + }; Ok(()) } - async fn handle_member( ast_path: &[AstParentKind], link_obj: impl Future> + Send + Sync,