1use std::sync::Arc;
2
3use anyhow::{Ok, Result};
4use rustc_hash::FxHashSet;
5use swc_core::{
6 base::try_with_handler,
7 common::{GLOBALS, Mark, SourceMap, Spanned, SyntaxContext, comments::Comments, sync::Lrc},
8 ecma::{ast::*, atoms::atom},
9};
10use turbo_rcstr::{RcStr, rcstr};
11
12use crate::{
13 SpecifiedModuleType,
14 analyzer::{
15 Bump, BumpVec, ConstantNumber, ConstantValue, ImportMap, JsValue, ObjectPart,
16 WellKnownObjectKind, is_unresolved, is_unresolved_id,
17 },
18 references::constant_value::parse_single_expr_lit,
19 utils::unparen,
20};
21
22#[derive(Debug)]
24pub struct EvalContext {
25 pub(crate) unresolved_mark: Mark,
27 pub(crate) top_level_mark: Mark,
29 pub(crate) imports: ImportMap,
30 pub(crate) force_free_values: Arc<FxHashSet<Id>>,
31}
32
33impl EvalContext {
34 pub fn new(
42 module: Option<&Program>,
43 unresolved_mark: Mark,
44 top_level_mark: Mark,
45 force_free_values: Arc<FxHashSet<Id>>,
46 comments: Option<&dyn Comments>,
47 ) -> Self {
48 Self {
49 unresolved_mark,
50 top_level_mark,
51 imports: module.map_or(ImportMap::default(), |m| {
52 ImportMap::analyze(unresolved_mark, m, comments)
53 }),
54 force_free_values,
55 }
56 }
57
58 pub fn is_esm(&self, specified_type: SpecifiedModuleType) -> bool {
59 self.imports.is_esm(specified_type)
60 }
61
62 pub fn is_cjs(&self, specified_type: SpecifiedModuleType) -> bool {
63 self.imports.is_cjs(specified_type)
64 }
65
66 pub(super) fn eval_prop_name<'a>(&self, arena: &'a Bump, prop: &PropName) -> JsValue<'a> {
67 match prop {
68 PropName::Ident(ident) => ident.sym.clone().into(),
69 PropName::Str(str) => str.value.clone().to_atom_lossy().into_owned().into(),
70 PropName::Num(num) => num.value.into(),
71 PropName::Computed(ComputedPropName { expr, .. }) => self.eval(arena, expr),
72 PropName::BigInt(bigint) => (*bigint.value.clone()).into(),
73 }
74 }
75
76 pub(super) fn eval_member_prop<'a>(
77 &self,
78 arena: &'a Bump,
79 prop: &MemberProp,
80 ) -> Option<JsValue<'a>> {
81 match prop {
82 MemberProp::Ident(ident) => Some(ident.sym.clone().into()),
83 MemberProp::Computed(ComputedPropName { expr, .. }) => Some(self.eval(arena, expr)),
84 MemberProp::PrivateName(_) => None,
85 }
86 }
87
88 fn eval_tpl<'a>(&self, arena: &'a Bump, e: &Tpl, raw: bool) -> JsValue<'a> {
89 debug_assert!(e.quasis.len() == e.exprs.len() + 1);
90
91 let mut values = vec![];
92
93 for idx in 0..(e.quasis.len() + e.exprs.len()) {
94 if idx.is_multiple_of(2) {
95 let idx = idx / 2;
96 let e = &e.quasis[idx];
97 if raw {
98 if !e.raw.is_empty() {
101 values.push(JsValue::from(e.raw.clone()));
102 }
103 } else {
104 match &e.cooked {
105 Some(v) => {
106 if !v.is_empty() {
107 values.push(JsValue::from(v.clone().to_atom_lossy().into_owned()));
108 }
109 }
110 None => return JsValue::unknown_empty(true, rcstr!("")),
112 }
113 }
114 } else {
115 let idx = idx / 2;
116 let e = &e.exprs[idx];
117
118 values.push(self.eval(arena, e));
119 }
120 }
121
122 match values.len() {
123 0 => JsValue::Constant(ConstantValue::Str(rcstr!("").into())),
124 1 => values.into_iter().next().unwrap(),
125 _ => JsValue::concat(BumpVec::from_iter_in(arena, values)),
126 }
127 }
128
129 pub fn eval_id<'a>(&self, arena: &'a Bump, id: Id) -> JsValue<'a> {
130 if let Some(imported) = self.imports.get_import(arena, &id) {
131 return imported;
132 }
133 if is_unresolved_id(&id, self.unresolved_mark) || self.force_free_values.contains(&id) {
134 match id.0.as_str() {
137 "undefined" => JsValue::Constant(ConstantValue::Undefined),
138 "NaN" => JsValue::Constant(ConstantValue::Num(f64::NAN.into())),
139 "Infinity" => JsValue::Constant(ConstantValue::Num(f64::INFINITY.into())),
140 _ => JsValue::FreeVar(id.0.clone()),
141 }
142 } else {
143 JsValue::Variable(id)
144 }
145 }
146
147 pub fn eval<'a>(&self, arena: &'a Bump, e: &Expr) -> JsValue<'a> {
148 let value = self.eval_inner(arena, e);
149 if self.imports.get_attributes(e.span()).ignore {
158 JsValue::unknown(value, true, rcstr!("turbopackIgnore"))
159 } else {
160 value
161 }
162 }
163
164 fn eval_inner<'a>(&self, arena: &'a Bump, e: &Expr) -> JsValue<'a> {
165 debug_assert!(
166 GLOBALS.is_set(),
167 "Eval requires globals from its parsed result"
168 );
169 match e {
170 Expr::Paren(e) => self.eval(arena, &e.expr),
171 Expr::Lit(e) => JsValue::Constant(e.clone().into()),
172 Expr::Ident(i) => self.eval_id(arena, i.to_id()),
173 Expr::Unary(UnaryExpr {
174 op: op!("void"),
175 arg: Expr::Lit(_),
179 ..
180 }) => JsValue::Constant(ConstantValue::Undefined),
181
182 Expr::Unary(UnaryExpr {
183 op: op!(unary, "-"),
184 arg: Expr::Lit(Lit::Num(n)),
185 ..
186 }) => JsValue::Constant(ConstantValue::Num(ConstantNumber(-n.value))),
187
188 Expr::Unary(UnaryExpr {
189 op: op!("!"), arg, ..
190 }) => {
191 let arg = self.eval(arena, arg);
192
193 JsValue::logical_not(arena, arg)
194 }
195
196 Expr::Unary(UnaryExpr {
197 op: op!("typeof"),
198 arg,
199 ..
200 }) => {
201 let arg = self.eval(arena, arg);
202
203 JsValue::type_of(arena, arg)
204 }
205
206 Expr::Bin(BinExpr {
207 op: op!(bin, "+"),
208 left,
209 right,
210 ..
211 }) => {
212 let l = self.eval(arena, left);
213 let r = self.eval(arena, right);
214
215 match (l, r) {
216 (JsValue::Add(c, mut l), r) => {
217 let total = c + r.total_nodes();
218 l.push(arena, r);
219 JsValue::Add(total, l)
220 }
221 (l, r) => JsValue::add(BumpVec::from_iter_in(arena, [l, r])),
222 }
223 }
224
225 Expr::Bin(BinExpr {
226 op: op!("&&"),
227 left,
228 right,
229 ..
230 }) => JsValue::logical_and(BumpVec::from_iter_in(
231 arena,
232 [self.eval(arena, left), self.eval(arena, right)],
233 )),
234
235 Expr::Bin(BinExpr {
236 op: op!("||"),
237 left,
238 right,
239 ..
240 }) => JsValue::logical_or(BumpVec::from_iter_in(
241 arena,
242 [self.eval(arena, left), self.eval(arena, right)],
243 )),
244
245 Expr::Bin(BinExpr {
246 op: op!("??"),
247 left,
248 right,
249 ..
250 }) => JsValue::nullish_coalescing(BumpVec::from_iter_in(
251 arena,
252 [self.eval(arena, left), self.eval(arena, right)],
253 )),
254
255 Expr::Bin(BinExpr {
256 op: op!("=="),
257 left,
258 right,
259 ..
260 }) => JsValue::equal(arena, self.eval(arena, left), self.eval(arena, right)),
261
262 Expr::Bin(BinExpr {
263 op: op!("!="),
264 left,
265 right,
266 ..
267 }) => JsValue::not_equal(arena, self.eval(arena, left), self.eval(arena, right)),
268
269 Expr::Bin(BinExpr {
270 op: op!("==="),
271 left,
272 right,
273 ..
274 }) => JsValue::strict_equal(arena, self.eval(arena, left), self.eval(arena, right)),
275
276 Expr::Bin(BinExpr {
277 op: op!("!=="),
278 left,
279 right,
280 ..
281 }) => JsValue::strict_not_equal(arena, self.eval(arena, left), self.eval(arena, right)),
282
283 Expr::Bin(BinExpr {
284 op: op!("in"),
285 left,
286 right,
287 ..
288 }) => JsValue::r#in(arena, self.eval(arena, left), self.eval(arena, right)),
289
290 &Expr::Cond(CondExpr {
291 ref cons,
292 ref alt,
293 ref test,
294 ..
295 }) => {
296 let test = self.eval(arena, test);
297 if let Some(truthy) = test.is_truthy() {
298 if truthy {
299 self.eval(arena, cons)
300 } else {
301 self.eval(arena, alt)
302 }
303 } else {
304 JsValue::tenary(arena, test, self.eval(arena, cons), self.eval(arena, alt))
305 }
306 }
307
308 Expr::Tpl(e) => self.eval_tpl(arena, e, false),
309
310 Expr::TaggedTpl(TaggedTpl {
311 tag:
312 Expr::Member(MemberExpr {
313 obj: Expr::Ident(tag_obj),
314 prop: MemberProp::Ident(tag_prop),
315 ..
316 }),
317 tpl,
318 ..
319 }) => {
320 if &*tag_obj.sym == "String"
321 && &*tag_prop.sym == "raw"
322 && is_unresolved(tag_obj, self.unresolved_mark)
323 {
324 self.eval_tpl(arena, tpl, true)
325 } else {
326 JsValue::unknown_empty(
327 true,
328 rcstr!("tagged template literal is not supported yet"),
329 )
330 }
331 }
332
333 Expr::Fn(expr) => {
334 if let Some(ident) = &expr.ident {
335 JsValue::Variable(ident.to_id())
336 } else {
337 JsValue::Variable((
338 format!("*anonymous function {}*", expr.function.span.lo.0).into(),
339 SyntaxContext::empty(),
340 ))
341 }
342 }
343 Expr::Arrow(expr) => JsValue::Variable((
344 format!("*arrow function {}*", expr.span.lo.0).into(),
345 SyntaxContext::empty(),
346 )),
347
348 Expr::Await(AwaitExpr { arg, .. }) => JsValue::awaited(arena, self.eval(arena, arg)),
349
350 Expr::Seq(e) => {
351 let mut seq = e.exprs.iter().map(|e| self.eval(arena, e)).peekable();
352 let mut side_effects = false;
353 let mut last = seq.next().unwrap();
354 for e in seq {
355 side_effects |= last.has_side_effects();
356 last = e;
357 }
358 if side_effects {
359 last.make_unknown(true, rcstr!("sequence with side effects"));
360 }
361 last
362 }
363
364 Expr::Member(MemberExpr {
365 obj,
366 prop: MemberProp::Ident(prop),
367 ..
368 }) => {
369 let obj = self.eval(arena, obj);
370 JsValue::member(arena, obj, prop.sym.clone().into())
371 }
372
373 Expr::Member(MemberExpr {
374 obj,
375 prop: MemberProp::Computed(computed),
376 ..
377 }) => {
378 let obj = self.eval(arena, obj);
379 let prop = self.eval(arena, &computed.expr);
380 JsValue::member(arena, obj, prop)
381 }
382
383 Expr::New(NewExpr { callee, args, .. }) => {
384 let args = args.as_deref().unwrap_or(&[]);
385 if args.iter().any(|arg| arg.spread.is_some()) {
387 return JsValue::unknown_empty(
388 true,
389 rcstr!("spread in new calls is not supported"),
390 );
391 }
392
393 JsValue::new_from_iter(
394 arena,
395 self.eval(arena, callee),
396 args.iter().map(|arg| self.eval(arena, &arg.expr)),
397 )
398 }
399
400 Expr::Call(CallExpr {
401 callee: Callee::Expr(callee),
402 args,
403 ..
404 }) => {
405 if args.iter().any(|arg| arg.spread.is_some()) {
407 return JsValue::unknown_empty(
408 true,
409 rcstr!("spread in function calls is not supported"),
410 );
411 }
412
413 if let Expr::Member(MemberExpr { obj, prop, .. }) = unparen(callee) {
414 let prop = match prop {
415 MemberProp::Ident(i) => i.sym.clone().into(),
416 MemberProp::PrivateName(_) => {
417 return JsValue::unknown_empty(
418 false,
419 rcstr!("private names in function calls is not supported"),
420 );
421 }
422 MemberProp::Computed(ComputedPropName { expr, .. }) => {
423 self.eval(arena, expr)
424 }
425 };
426 let obj = self.eval(arena, obj);
427 JsValue::member_call_from_iter(
428 arena,
429 obj,
430 prop,
431 args.iter().map(|arg| self.eval(arena, &arg.expr)),
432 )
433 } else {
434 JsValue::call_from_iter(
435 arena,
436 self.eval(arena, callee),
437 args.iter().map(|arg| self.eval(arena, &arg.expr)),
438 )
439 }
440 }
441
442 Expr::Call(CallExpr {
443 callee: Callee::Super(_),
444 args,
445 ..
446 }) => {
447 if args.iter().any(|arg| arg.spread.is_some()) {
449 return JsValue::unknown_empty(
450 true,
451 rcstr!("spread in function calls is not supported"),
452 );
453 }
454
455 let args = bumpalo::collections::Vec::from_iter_in(
456 args.iter().map(|arg| self.eval(arena, &arg.expr)),
457 arena,
458 )
459 .into_boxed_slice();
460
461 JsValue::super_call(args)
462 }
463
464 Expr::Call(CallExpr {
465 callee: Callee::Import(_),
466 args,
467 ..
468 }) => {
469 if args.iter().any(|arg| arg.spread.is_some()) {
471 return JsValue::unknown_empty(
472 true,
473 rcstr!("spread in import() is not supported"),
474 );
475 }
476 JsValue::call_from_iter(
477 arena,
478 JsValue::FreeVar(atom!("import")),
479 args.iter().map(|arg| self.eval(arena, &arg.expr)),
480 )
481 }
482
483 Expr::Array(arr) => {
484 if arr.elems.iter().flatten().any(|v| v.spread.is_some()) {
485 return JsValue::unknown_empty(true, rcstr!("spread is not supported"));
486 }
487
488 let arr = BumpVec::from_iter_in(
489 arena,
490 arr.elems.iter().map(|e| match e {
491 Some(e) => self.eval(arena, &e.expr),
492 _ => JsValue::Constant(ConstantValue::Undefined),
493 }),
494 );
495 JsValue::array(arr)
496 }
497
498 Expr::Object(obj) => JsValue::object(BumpVec::from_iter_in(
499 arena,
500 obj.props.iter().map(|prop| match prop {
501 PropOrSpread::Spread(SpreadElement { expr, .. }) => {
502 ObjectPart::Spread(self.eval(arena, expr))
503 }
504 PropOrSpread::Prop(Prop::KeyValue(KeyValueProp { key, value })) => {
505 ObjectPart::KeyValue(
506 self.eval_prop_name(arena, key),
507 self.eval(arena, value),
508 )
509 }
510 PropOrSpread::Prop(Prop::Shorthand(ident)) => ObjectPart::KeyValue(
511 ident.sym.clone().into(),
512 self.eval(arena, &Expr::Ident(ident.clone())),
513 ),
514 _ => ObjectPart::Spread(JsValue::unknown_empty(
515 true,
516 rcstr!("unsupported object part"),
517 )),
518 }),
519 )),
520
521 Expr::MetaProp(MetaPropExpr {
522 kind: MetaPropKind::ImportMeta,
523 ..
524 }) => JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta),
525
526 Expr::Assign(AssignExpr { op, .. }) => match op {
527 AssignOp::Assign => JsValue::unknown_empty(true, rcstr!("assignment expression")),
530 _ => JsValue::unknown_empty(true, rcstr!("compound assignment expression")),
531 },
532
533 _ => JsValue::unknown_empty(true, rcstr!("unsupported expression")),
534 }
535 }
536
537 pub fn eval_single_expr_lit<'a>(arena: &'a Bump, expr_lit: &RcStr) -> Result<JsValue<'a>> {
538 let cm = Lrc::new(SourceMap::default());
539
540 let js_value = try_with_handler(cm, Default::default(), |_| {
541 GLOBALS.set(&Default::default(), || {
542 let expr = parse_single_expr_lit(expr_lit);
543 let eval_context =
544 EvalContext::new(None, Mark::new(), Mark::new(), Default::default(), None);
545
546 Ok(eval_context.eval(arena, &expr))
547 })
548 })
549 .map_err(|e| e.to_pretty_error())?;
550
551 Ok(js_value)
552 }
553}