1use swc_core::{
2 common::Mark,
3 ecma::ast::{Id, Ident},
4};
5
6pub(crate) use self::imports::ImportMap;
7
8pub mod builtin;
9pub mod bump_vec;
10pub(crate) mod cjs_ast;
11pub mod graph;
12pub mod imports;
13pub mod linker;
14pub mod side_effects;
15pub mod top_level_await;
16pub mod well_known;
17
18mod jsvalue;
19pub use bump_vec::BumpVec;
20pub use bumpalo::Bump;
21pub use jsvalue::*;
22pub use thread_local::ThreadLocal;
23pub use well_known::{kinds::*, require_context::*};
24
25fn is_unresolved(i: &Ident, unresolved_mark: Mark) -> bool {
26 i.ctxt.outer() == unresolved_mark
27}
28
29fn is_unresolved_id(i: &Id, unresolved_mark: Mark) -> bool {
30 i.1.outer() == unresolved_mark
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum Modified {
39 Yes,
40 No,
41}
42
43impl Modified {
44 pub fn is_modified(self) -> bool {
46 matches!(self, Modified::Yes)
47 }
48}
49
50impl From<bool> for Modified {
51 fn from(modified: bool) -> Self {
52 if modified {
53 Modified::Yes
54 } else {
55 Modified::No
56 }
57 }
58}
59
60#[doc(hidden)]
61pub mod test_utils {
62 use anyhow::Result;
63 use turbo_rcstr::rcstr;
64 use turbo_tasks::{FxIndexMap, PrettyPrintError, Vc};
65 use turbopack_core::compile_time_info::CompileTimeInfo;
66
67 use super::{
68 ConstantValue, JsValue, JsValueUrlKind, Modified, ModuleValue, WellKnownFunctionKind,
69 WellKnownObjectKind, builtin::early_replace_builtin, well_known::replace_well_known,
70 };
71 use crate::{
72 analyzer::{
73 Bump, RequireContextValue, ThreadLocal, builtin::replace_builtin,
74 imports::ImportAttributes, parse_require_context,
75 },
76 utils::module_value_to_well_known_object,
77 };
78
79 pub async fn early_visitor<'a>(
80 _arena: &'a ThreadLocal<Bump>,
81 mut v: JsValue<'a>,
82 ) -> Result<(JsValue<'a>, Modified)> {
83 let m = early_replace_builtin(&mut v);
84 Ok((v, m))
85 }
86
87 pub async fn visitor<'a>(
90 arena: &'a ThreadLocal<Bump>,
91 v: JsValue<'a>,
92 compile_time_info: Vc<CompileTimeInfo>,
93 attributes: &ImportAttributes,
94 ) -> Result<(JsValue<'a>, Modified)> {
95 let ImportAttributes { ignore, .. } = *attributes;
96 let mut new_value = match v {
97 JsValue::Call(_, ref call)
98 if matches!(
99 call.callee(),
100 JsValue::WellKnownFunction(WellKnownFunctionKind::Import)
101 ) =>
102 {
103 match &call.args()[0] {
104 JsValue::Constant(ConstantValue::Str(v)) => JsValue::promise(
105 arena.get_or_default(),
106 JsValue::Module(ModuleValue {
107 module: v.as_atom().into_owned().into(),
108 annotations: None,
109 analyze_for_constants: false,
110 reference: None,
111 }),
112 ),
113 _ => v.into_unknown(true, rcstr!("import() non constant")),
114 }
115 }
116 JsValue::Call(_, ref call)
117 if matches!(
118 call.callee(),
119 JsValue::WellKnownFunction(WellKnownFunctionKind::CreateRequire)
120 ) =>
121 {
122 if let [JsValue::Member(_, obj, prop)] = call.args()
123 && matches!(
124 &**obj,
125 JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta)
126 )
127 && let JsValue::Constant(ConstantValue::Str(prop)) = &**prop
128 && prop.as_str() == "url"
129 {
130 JsValue::WellKnownFunction(WellKnownFunctionKind::Require)
131 } else {
132 v.into_unknown(true, rcstr!("createRequire() non constant"))
133 }
134 }
135 JsValue::Call(_, ref call)
136 if matches!(
137 call.callee(),
138 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireResolve)
139 ) =>
140 {
141 match &call.args()[0] {
142 JsValue::Constant(v) => (v.to_string() + "/resolved/lib/index.js").into(),
143 _ => v.into_unknown(true, rcstr!("require.resolve non constant")),
144 }
145 }
146 JsValue::Call(_, ref call)
147 if matches!(
148 call.callee(),
149 JsValue::WellKnownFunction(WellKnownFunctionKind::ImportMetaGlob)
150 ) =>
151 {
152 v.into_unknown(false, rcstr!("import.meta.glob()"))
153 }
154 JsValue::Call(_, ref call)
155 if matches!(
156 call.callee(),
157 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContext)
158 ) =>
159 {
160 match parse_require_context(call.args()) {
161 Ok(options) => {
162 let mut map = FxIndexMap::default();
163
164 map.insert(
165 rcstr!("./a"),
166 format!("[context: {}]/a", options.dir).into(),
167 );
168 map.insert(
169 rcstr!("./b"),
170 format!("[context: {}]/b", options.dir).into(),
171 );
172 map.insert(
173 rcstr!("./c"),
174 format!("[context: {}]/c", options.dir).into(),
175 );
176
177 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequire(
178 Box::new(RequireContextValue(map)),
179 ))
180 }
181 Err(err) => v.into_unknown(true, PrettyPrintError(&err).to_string().into()),
182 }
183 }
184 JsValue::New(_, ref call)
185 if matches!(
186 call.callee(),
187 JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor)
188 ) =>
189 {
190 if let [
191 JsValue::Constant(ConstantValue::Str(url)),
192 JsValue::Member(_, obj, prop),
193 ] = call.args()
194 && matches!(
195 &**obj,
196 JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta)
197 )
198 && let JsValue::Constant(ConstantValue::Str(prop)) = &**prop
199 {
200 if prop.as_str() == "url" {
201 JsValue::Url(url.clone(), JsValueUrlKind::Relative)
203 } else {
204 v.into_unknown(true, rcstr!("new non constant"))
205 }
206 } else {
207 v.into_unknown(true, rcstr!("new non constant"))
208 }
209 }
210 JsValue::FreeVar(ref var) => match &**var {
211 "__dirname" => rcstr!("__dirname").into(),
212 "__filename" => rcstr!("__filename").into(),
213
214 "require" => JsValue::unknown_if(
215 ignore,
216 JsValue::WellKnownFunction(WellKnownFunctionKind::Require),
217 true,
218 rcstr!("ignored require"),
219 ),
220 "import" => JsValue::unknown_if(
221 ignore,
222 JsValue::WellKnownFunction(WellKnownFunctionKind::Import),
223 true,
224 rcstr!("ignored import"),
225 ),
226 "Worker" => JsValue::unknown_if(
227 ignore,
228 JsValue::WellKnownFunction(WellKnownFunctionKind::WorkerConstructor),
229 true,
230 rcstr!("ignored Worker constructor"),
231 ),
232 "define" => JsValue::WellKnownFunction(WellKnownFunctionKind::Define),
233 "URL" => JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor),
234 "process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessModule),
235 "Object" => JsValue::WellKnownObject(WellKnownObjectKind::GlobalObject),
236 "Buffer" => JsValue::WellKnownObject(WellKnownObjectKind::NodeBuffer),
237 _ => v.into_unknown(true, rcstr!("unknown global")),
238 },
239 JsValue::Module(ref mv) => {
240 if let Some(wko) = module_value_to_well_known_object(mv) {
241 wko
242 } else {
243 return Ok((v, Modified::No));
244 }
245 }
246 _ => {
247 let (mut v, m1) = replace_well_known(arena, v, compile_time_info, true).await?;
248 let m2 = replace_builtin(arena.get_or_default(), &mut v);
249 let m = if m1.is_modified() || m2.is_modified() {
250 Modified::Yes
251 } else {
252 Modified::from(v.make_nested_operations_unknown())
253 };
254 return Ok((v, m));
255 }
256 };
257 new_value.normalize_shallow(arena.get_or_default());
258 Ok((new_value, Modified::Yes))
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use std::{mem::take, path::PathBuf, sync::Arc, time::Instant};
265
266 use bumpalo::boxed::Box as BumpBox;
267 use parking_lot::Mutex;
268 use rustc_hash::FxHashMap;
269 use swc_core::{
270 common::{
271 FileName, FilePathMapping, GLOBALS, Globals, Mark, SourceMap,
272 comments::SingleThreadedComments,
273 },
274 ecma::{
275 ast::{EsVersion, Id},
276 parser::parse_file_as_program,
277 transforms::base::resolver,
278 visit::VisitMutWith,
279 },
280 testing::{NormalizedOutput, fixture},
281 };
282 use turbo_rcstr::{RcStr, rcstr};
283 use turbo_tasks::{ResolvedVc, TurboTasks, util::FormatDuration};
284 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
285 use turbopack_core::{
286 compile_time_info::CompileTimeInfo,
287 environment::{Environment, ExecutionEnvironment, NodeJsEnvironment, NodeJsVersion},
288 target::{Arch, CompileTarget, Endianness, Libc, Platform},
289 };
290
291 use super::{
292 BumpVec, JsValue,
293 graph::{ConditionalKind, Effect, EffectArg, EvalContext, VarGraph, create_graph},
294 linker::link,
295 };
296 use crate::{
297 AnalyzeMode, SpecifiedModuleType,
298 analyzer::{Bump, ThreadLocal, graph::AssignmentScopes, imports::ImportAttributes},
299 };
300
301 #[fixture("tests/analyzer/graph/**/input.js")]
302 fn fixture(input: PathBuf) {
303 let input = RcStr::from(input.to_str().unwrap());
304 let rt = tokio::runtime::Builder::new_multi_thread()
305 .worker_threads(2)
306 .enable_all()
307 .build()
308 .unwrap();
309 rt.block_on(async move {
310 let tt = TurboTasks::new(TurboTasksBackend::new(
311 BackendOptions::default(),
312 noop_backing_storage(),
313 ));
314 tt.run_once(async move {
315 fixture_op(input).read_strongly_consistent().await?;
316 anyhow::Ok(())
317 })
318 .await
319 .unwrap();
320 });
321 }
322
323 #[turbo_tasks::function(operation, root)]
324 async fn fixture_op(input: RcStr) -> anyhow::Result<()> {
325 let input = PathBuf::from(input.as_str());
326 let graph_snapshot_path = input.with_file_name("graph.snapshot");
327 let graph_explained_snapshot_path = input.with_file_name("graph-explained.snapshot");
328 let graph_effects_snapshot_path = input.with_file_name("graph-effects.snapshot");
329 let resolved_explained_snapshot_path = input.with_file_name("resolved-explained.snapshot");
330 let resolved_effects_snapshot_path = input.with_file_name("resolved-effects.snapshot");
331 let large_marker = input.with_file_name("large");
332
333 let cm: Arc<SourceMap> = Arc::new(SourceMap::new(FilePathMapping::empty()));
334 let globals = Arc::new(Globals::new());
335 let arena = ThreadLocal::new();
336
337 let (eval_context, mut var_graph) = GLOBALS.set(&globals, || {
341 let fm = cm.load_file(&input).unwrap();
342 let comments = SingleThreadedComments::default();
343 let mut m = parse_file_as_program(
344 &fm,
345 Default::default(),
346 EsVersion::latest(),
347 Some(&comments),
348 &mut vec![],
349 )
350 .map_err(|err| anyhow::anyhow!("parse error: {err:?}"))?;
351
352 let unresolved_mark = Mark::new();
353 let top_level_mark = Mark::new();
354 m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
355
356 let eval_context = EvalContext::new(
357 Some(&m),
358 unresolved_mark,
359 top_level_mark,
360 Default::default(),
361 Some(&comments),
362 );
363
364 let var_graph = create_graph(
365 arena.get_or_default(),
366 &m,
367 &eval_context,
368 AnalyzeMode::code_generation_and_tracing(),
369 true,
370 SpecifiedModuleType::EcmaScript,
371 true,
372 false,
373 );
374 anyhow::Ok((eval_context, var_graph))
375 })?;
376 let var_cache = Default::default();
377
378 let mut named_values = var_graph
379 .values
380 .iter()
381 .map(|((id, ctx), value)| {
382 let unique = var_graph.values.keys().filter(|(i, _)| id == i).count() == 1;
383 let value = value.clone_in(arena.get_or_default());
384 if unique {
385 (id.to_string(), ((id.clone(), *ctx), value))
386 } else {
387 (format!("{id}{ctx:?}"), ((id.clone(), *ctx), value))
388 }
389 })
390 .collect::<Vec<_>>();
391 named_values.sort_by(|a, b| a.0.cmp(&b.0));
392
393 fn explain_all<'x, 'a: 'x>(
394 values: impl IntoIterator<Item = (&'x String, &'x JsValue<'a>, Option<AssignmentScopes>)>,
395 ) -> String {
396 values
397 .into_iter()
398 .map(|(id, value, assignment_scopes)| {
399 let non_root_assignments = match assignment_scopes {
400 Some(AssignmentScopes::AllInModuleEvalScope) => " (const after eval)",
401 _ => "",
402 };
403 let (explainer, hints) = value.explain(10, 5);
404 format!("{id}{non_root_assignments} = {explainer}{hints}")
405 })
406 .collect::<Vec<_>>()
407 .join("\n\n")
408 }
409
410 {
411 let large = large_marker.exists();
414
415 if !large {
416 NormalizedOutput::from(format!(
417 "{:#?}",
418 named_values
419 .iter()
420 .map(|(name, (_, value))| (name, value))
421 .collect::<Vec<_>>()
422 ))
423 .compare_to_file(&graph_snapshot_path)
424 .unwrap();
425 }
426 NormalizedOutput::from(explain_all(named_values.iter().map(
427 |(name, (id, value))| {
428 (
429 name,
430 value,
431 eval_context.imports.assignment_scopes.get(id).copied(),
432 )
433 },
434 )))
435 .compare_to_file(&graph_explained_snapshot_path)
436 .unwrap();
437 if !large {
438 NormalizedOutput::from(format!("{:#?}", var_graph.effects))
439 .compare_to_file(&graph_effects_snapshot_path)
440 .unwrap();
441 }
442 }
443
444 {
445 let start = Instant::now();
448 let mut resolved = Vec::new();
449 for (name, id) in named_values.iter().map(|(name, (id, _))| (name, id)) {
450 let start = Instant::now();
451 let (res, steps) = resolve(
454 &arena,
455 &var_graph,
456 JsValue::Variable(id.clone()),
457 ImportAttributes::empty_ref(),
458 &var_cache,
459 )
460 .await;
461 let time = start.elapsed();
462 if time.as_millis() > 1 {
463 println!(
464 "linking {} {name} took {} in {} steps",
465 input.display(),
466 FormatDuration(time),
467 steps
468 );
469 }
470
471 resolved.push((name.clone(), res));
472 }
473 let time = start.elapsed();
474 if time.as_millis() > 1 {
475 println!("linking {} took {}", input.display(), FormatDuration(time));
476 }
477
478 let start = Instant::now();
479 let explainer = explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
480 let time = start.elapsed();
481 if time.as_millis() > 1 {
482 println!(
483 "explaining {} took {}",
484 input.display(),
485 FormatDuration(time)
486 );
487 }
488
489 NormalizedOutput::from(explainer)
490 .compare_to_file(&resolved_explained_snapshot_path)
491 .unwrap();
492 }
493
494 {
495 let start = Instant::now();
498 let mut resolved = Vec::new();
499 let mut queue = take(&mut var_graph.effects)
500 .into_iter()
501 .map(|effect| (0, effect))
502 .rev()
503 .collect::<Vec<_>>();
504 let mut i = 0;
505 while let Some((parent, effect)) = queue.pop() {
506 i += 1;
507 let start = Instant::now();
508 async fn handle_args<'a>(
509 arena: &'a ThreadLocal<Bump>,
510 args: BumpVec<'a, EffectArg<'a>>,
511 queue: &mut Vec<(usize, Effect<'a>)>,
512 var_graph: &VarGraph<'a>,
513 var_cache: &Mutex<FxHashMap<Id, JsValue<'a>>>,
514 i: usize,
515 ) -> Vec<JsValue<'a>> {
516 let mut new_args = Vec::with_capacity(args.len());
517 for arg in args {
518 match arg {
519 EffectArg::Value(v) => {
520 new_args.push(
521 resolve(
522 arena,
523 var_graph,
524 v,
525 ImportAttributes::empty_ref(),
526 var_cache,
527 )
528 .await
529 .0,
530 );
531 }
532 EffectArg::Closure(v, effects) => {
533 new_args.push(
534 resolve(
535 arena,
536 var_graph,
537 v,
538 ImportAttributes::empty_ref(),
539 var_cache,
540 )
541 .await
542 .0,
543 );
544 queue.extend(
545 BumpVec::from(BumpBox::into_inner(effects).effects)
546 .into_iter()
547 .rev()
548 .map(|e| (i, e)),
549 );
550 }
551 EffectArg::Spread => {
552 new_args.push(JsValue::unknown_empty(true, rcstr!("spread")));
553 }
554 }
555 }
556 new_args
557 }
558 let steps = match effect {
559 Effect::Conditional {
560 mut condition,
561 kind,
562 ..
563 } => {
564 let (condition, steps) = resolve(
565 &arena,
566 &var_graph,
567 take(&mut *condition),
568 ImportAttributes::empty_ref(),
569 &var_cache,
570 )
571 .await;
572 resolved.push((format!("{parent} -> {i} conditional"), condition));
573 match BumpBox::into_inner(kind) {
574 ConditionalKind::If { then } => {
575 queue.extend(
576 BumpVec::from(then.effects)
577 .into_iter()
578 .rev()
579 .map(|e| (i, e)),
580 );
581 }
582 ConditionalKind::Else { r#else } => {
583 queue.extend(
584 BumpVec::from(r#else.effects)
585 .into_iter()
586 .rev()
587 .map(|e| (i, e)),
588 );
589 }
590 ConditionalKind::IfElse { then, r#else }
591 | ConditionalKind::Ternary { then, r#else } => {
592 queue.extend(
593 BumpVec::from(r#else.effects)
594 .into_iter()
595 .rev()
596 .map(|e| (i, e)),
597 );
598 queue.extend(
599 BumpVec::from(then.effects)
600 .into_iter()
601 .rev()
602 .map(|e| (i, e)),
603 );
604 }
605 ConditionalKind::IfElseMultiple { then, r#else } => {
606 for then in BumpVec::from(then) {
607 queue.extend(
608 BumpVec::from(then.effects)
609 .into_iter()
610 .rev()
611 .map(|e| (i, e)),
612 );
613 }
614 for r#else in BumpVec::from(r#else) {
615 queue.extend(
616 BumpVec::from(r#else.effects)
617 .into_iter()
618 .rev()
619 .map(|e| (i, e)),
620 );
621 }
622 }
623 ConditionalKind::And { expr }
624 | ConditionalKind::Or { expr }
625 | ConditionalKind::NullishCoalescing { expr }
626 | ConditionalKind::Labeled { body: expr } => {
627 queue.extend(
628 BumpVec::from(expr.effects)
629 .into_iter()
630 .rev()
631 .map(|e| (i, e)),
632 );
633 }
634 };
635 steps
636 }
637 Effect::Call {
638 mut func,
639 args,
640 new,
641 span,
642 ..
643 } => {
644 let (func, steps) = resolve(
645 &arena,
646 &var_graph,
647 take(&mut *func),
648 eval_context.imports.get_attributes(span),
649 &var_cache,
650 )
651 .await;
652 let new_args =
653 handle_args(&arena, args, &mut queue, &var_graph, &var_cache, i).await;
654 resolved.push((
655 format!("{parent} -> {i} call"),
656 if new {
657 JsValue::new_from_iter(arena.get_or_default(), func, new_args)
658 } else {
659 JsValue::call_from_iter(arena.get_or_default(), func, new_args)
660 },
661 ));
662 steps
663 }
664 Effect::FreeVar { var, .. } => {
665 resolved.push((format!("{parent} -> {i} free var"), JsValue::FreeVar(var)));
666 0
667 }
668 Effect::TypeOf { mut arg, .. } => {
669 let (arg, steps) = resolve(
670 &arena,
671 &var_graph,
672 take(&mut *arg),
673 ImportAttributes::empty_ref(),
674 &var_cache,
675 )
676 .await;
677 resolved.push((
678 format!("{parent} -> {i} typeof"),
679 JsValue::type_of(arena.get_or_default(), arg),
680 ));
681 steps
682 }
683 Effect::MemberCall {
684 mut obj,
685 mut prop,
686 args,
687 ..
688 } => {
689 let (obj, obj_steps) = resolve(
690 &arena,
691 &var_graph,
692 take(&mut *obj),
693 ImportAttributes::empty_ref(),
694 &var_cache,
695 )
696 .await;
697 let (prop, prop_steps) = resolve(
698 &arena,
699 &var_graph,
700 take(&mut *prop),
701 ImportAttributes::empty_ref(),
702 &var_cache,
703 )
704 .await;
705 let new_args =
706 handle_args(&arena, args, &mut queue, &var_graph, &var_cache, i).await;
707 resolved.push((
708 format!("{parent} -> {i} member call"),
709 JsValue::member_call_from_iter(
710 arena.get_or_default(),
711 obj,
712 prop,
713 new_args,
714 ),
715 ));
716 obj_steps + prop_steps
717 }
718 Effect::DynamicImport { args, .. } => {
719 let new_args =
720 handle_args(&arena, args, &mut queue, &var_graph, &var_cache, i).await;
721 resolved.push((
722 format!("{parent} -> {i} dynamic import"),
723 JsValue::call_from_iter(
724 arena.get_or_default(),
725 JsValue::FreeVar("import".into()),
726 new_args,
727 ),
728 ));
729 0
730 }
731 Effect::Unreachable { .. } => {
732 resolved.push((
733 format!("{parent} -> {i} unreachable"),
734 JsValue::unknown_empty(true, rcstr!("unreachable")),
735 ));
736 0
737 }
738 Effect::ImportMeta { .. }
739 | Effect::ImportedBinding { .. }
740 | Effect::Member { .. }
741 | Effect::DestructuredMember { .. }
742 | Effect::In { .. } => 0,
743 };
744 let time = start.elapsed();
745 if time.as_millis() > 1 {
746 println!(
747 "linking effect {} took {} in {} steps",
748 input.display(),
749 FormatDuration(time),
750 steps
751 );
752 }
753 }
754 let time = start.elapsed();
755 if time.as_millis() > 1 {
756 println!(
757 "linking effects {} took {}",
758 input.display(),
759 FormatDuration(time)
760 );
761 }
762
763 let start = Instant::now();
764 let explainer = explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
765 let time = start.elapsed();
766 if time.as_millis() > 1 {
767 println!(
768 "explaining effects {} took {}",
769 input.display(),
770 FormatDuration(time)
771 );
772 }
773
774 NormalizedOutput::from(explainer)
775 .compare_to_file(&resolved_effects_snapshot_path)
776 .unwrap();
777 }
778
779 Ok(())
780 }
781
782 fn detect_direct_eval(src: &str) -> bool {
785 let cm: Arc<SourceMap> = Arc::new(SourceMap::new(FilePathMapping::empty()));
786 let globals = Globals::new();
787 GLOBALS.set(&globals, || {
788 let fm = cm.new_source_file(FileName::Anon.into(), src.to_string());
789 let mut m = parse_file_as_program(
790 &fm,
791 Default::default(),
792 EsVersion::latest(),
793 None,
794 &mut vec![],
795 )
796 .unwrap();
797 let unresolved_mark = Mark::new();
798 let top_level_mark = Mark::new();
799 m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
800 let eval_context = EvalContext::new(
801 Some(&m),
802 unresolved_mark,
803 top_level_mark,
804 Default::default(),
805 None,
806 );
807 let arena = Bump::new();
808 create_graph(
809 &arena,
810 &m,
811 &eval_context,
812 AnalyzeMode::code_generation(),
813 true,
814 SpecifiedModuleType::Automatic,
815 false,
816 false,
817 )
818 .has_direct_eval
819 })
820 }
821
822 #[test]
823 fn direct_eval_detection() {
824 for src in [
825 "eval('x')",
826 "(eval)('x')",
827 "((eval))('x')",
828 "function f() { return eval('x') }",
829 "(() => { eval('x') })()",
830 "class C { m() { eval('x') } }",
831 "if (cond) { eval('x') }",
832 "export const v = eval('x')",
833 ] {
834 assert!(detect_direct_eval(src), "expected direct eval: {src}");
835 }
836
837 for src in [
838 "(0, eval)('x')",
839 "globalThis.eval('x')",
840 "window.eval('x')",
841 "eval?.('x')",
842 "const e = eval; e('x')",
843 "new eval('x')",
844 "function f(eval) { return eval('x') }",
845 "var eval = (x) => x; eval('x')",
846 "export const v = 1",
847 ] {
848 assert!(!detect_direct_eval(src), "expected no direct eval: {src}");
849 }
850 }
851
852 async fn resolve<'a>(
853 arena: &'a ThreadLocal<Bump>,
854 var_graph: &VarGraph<'a>,
855 val: JsValue<'a>,
856 attributes: &ImportAttributes,
857 var_cache: &Mutex<FxHashMap<Id, JsValue<'a>>>,
858 ) -> (JsValue<'a>, u32) {
859 async {
862 let compile_time_info = CompileTimeInfo::builder(
863 Environment::new(ExecutionEnvironment::NodeJsLambda(
864 NodeJsEnvironment {
865 compile_target: CompileTarget {
866 arch: Arch::X64,
867 platform: Platform::Linux,
868 endianness: Endianness::Little,
869 libc: Libc::Glibc,
870 }
871 .resolved_cell(),
872 node_version: NodeJsVersion::default().resolved_cell(),
873 cwd: ResolvedVc::cell(None),
874 }
875 .resolved_cell(),
876 ))
877 .to_resolved()
878 .await?,
879 )
880 .cell()
881 .await?;
882 link(
883 arena,
884 var_graph,
885 val,
886 &(|val| Box::pin(super::test_utils::early_visitor(arena, val))),
887 &(|val| {
888 Box::pin(super::test_utils::visitor(
889 arena,
890 val,
891 compile_time_info,
892 attributes,
893 ))
894 }),
895 &Default::default(),
896 var_cache,
897 )
898 .await
899 }
900 .await
901 .unwrap()
902 }
903}