1use std::{iter, mem::take};
2
3pub mod kinds;
4pub mod require_context;
5
6use anyhow::Result;
7use either::Either;
8use smallvec::SmallVec;
9use turbo_rcstr::rcstr;
10use turbo_tasks::Vc;
11use turbopack_core::{compile_time_info::CompileTimeInfo, environment::Rendering};
12use url::Url;
13
14use super::{
15 ConstantValue, JsValue, JsValueUrlKind, Modified, ModuleValue, WellKnownFunctionKind,
16 WellKnownObjectKind,
17};
18use crate::analyzer::{Bump, BumpVec, RequireContextValue, ThreadLocal};
19
20pub async fn replace_well_known<'a>(
21 arena: &'a ThreadLocal<Bump>,
22 value: JsValue<'a>,
23 compile_time_info: Vc<CompileTimeInfo>,
24 allow_project_root_tracing: bool,
25) -> Result<(JsValue<'a>, Modified)> {
26 Ok(match value {
27 JsValue::Call(_, call) if matches!(call.callee(), JsValue::WellKnownFunction(_)) => {
28 let (callee, args) = call.into_parts();
29 let JsValue::WellKnownFunction(kind) = callee else {
30 unreachable!()
31 };
32 (
33 well_known_function_call(
34 arena,
35 kind,
36 JsValue::unknown_empty(false, rcstr!("this is not analyzed yet")),
37 args,
38 compile_time_info,
39 allow_project_root_tracing,
40 )
41 .await?,
42 Modified::Yes,
43 )
44 }
45 JsValue::Member(_, mut obj, mut prop) if matches!(&*obj, JsValue::WellKnownObject(_)) => {
46 let JsValue::WellKnownObject(kind) = take(&mut *obj) else {
47 unreachable!()
48 };
49 well_known_object_member(arena, kind, take(&mut *prop), compile_time_info).await?
50 }
51 JsValue::Member(_, mut obj, mut prop) if matches!(&*obj, JsValue::WellKnownFunction(_)) => {
52 let JsValue::WellKnownFunction(kind) = take(&mut *obj) else {
53 unreachable!()
54 };
55 well_known_function_member(arena.get_or_default(), kind, take(&mut *prop))
56 }
57 JsValue::Member(_, mut obj, mut prop) if matches!(&*obj, JsValue::Array { .. }) => {
58 match prop.as_str() {
59 Some("filter") => (
60 JsValue::WellKnownFunction(WellKnownFunctionKind::ArrayFilter),
61 Modified::Yes,
62 ),
63 Some("forEach") => (
64 JsValue::WellKnownFunction(WellKnownFunctionKind::ArrayForEach),
65 Modified::Yes,
66 ),
67 Some("map") => (
68 JsValue::WellKnownFunction(WellKnownFunctionKind::ArrayMap),
69 Modified::Yes,
70 ),
71 _ => (
72 JsValue::member(arena.get_or_default(), take(&mut *obj), take(&mut *prop)),
73 Modified::No,
74 ),
75 }
76 }
77 JsValue::Member(_, obj, prop)
79 if matches!(&*obj, JsValue::FreeVar(name) if &**name == "module")
80 && prop.as_str() == Some("hot")
81 && compile_time_info.await?.hot_module_replacement_enabled =>
82 {
83 (
84 JsValue::WellKnownObject(WellKnownObjectKind::ModuleHot),
85 Modified::Yes,
86 )
87 }
88 _ => (value, Modified::No),
89 })
90}
91
92pub async fn well_known_function_call<'a>(
93 arena: &'a ThreadLocal<Bump>,
94 kind: WellKnownFunctionKind<'a>,
95 _this: JsValue<'a>,
96 args: BumpVec<'a, JsValue<'a>>,
97 compile_time_info: Vc<CompileTimeInfo>,
98 allow_project_root_tracing: bool,
99) -> Result<JsValue<'a>> {
100 Ok(match kind {
101 WellKnownFunctionKind::ObjectAssign => object_assign(arena.get_or_default(), args),
102 WellKnownFunctionKind::PathJoin => path_join(arena.get_or_default(), args),
103 WellKnownFunctionKind::PathDirname => path_dirname(arena.get_or_default(), args),
104 WellKnownFunctionKind::PathResolve(cwd) => path_resolve(
105 arena.get_or_default(),
106 cwd.clone_in(arena.get_or_default()),
107 args,
108 ),
109 WellKnownFunctionKind::Import => import(arena.get_or_default(), args),
110 WellKnownFunctionKind::Require => require(arena.get_or_default(), args),
111 WellKnownFunctionKind::RequireContextRequire(value) => {
112 require_context_require(arena.get_or_default(), value, args)?
113 }
114 WellKnownFunctionKind::RequireContextRequireKeys(value) => {
115 require_context_require_keys(arena.get_or_default(), value, args)?
116 }
117 WellKnownFunctionKind::RequireContextRequireResolve(value) => {
118 require_context_require_resolve(arena.get_or_default(), value, args)?
119 }
120 WellKnownFunctionKind::PathToFileUrl => path_to_file_url(arena.get_or_default(), args),
121 WellKnownFunctionKind::OsArch => compile_time_info
122 .environment()
123 .compile_target()
124 .await?
125 .arch
126 .as_str()
127 .into(),
128 WellKnownFunctionKind::OsPlatform => compile_time_info
129 .environment()
130 .compile_target()
131 .await?
132 .platform
133 .as_str()
134 .into(),
135 WellKnownFunctionKind::ProcessCwd => {
136 if allow_project_root_tracing
137 && let Some(cwd) = &*compile_time_info.environment().cwd().await?
138 {
139 format!("/ROOT/{}", cwd.path).into()
140 } else {
141 JsValue::unknown(
142 JsValue::call_from_parts(
143 arena.get_or_default(),
144 JsValue::WellKnownFunction(kind),
145 args,
146 ),
147 true,
148 rcstr!("process.cwd is not specified in the environment"),
149 )
150 }
151 }
152 WellKnownFunctionKind::OsEndianness => compile_time_info
153 .environment()
154 .compile_target()
155 .await?
156 .endianness
157 .as_str()
158 .into(),
159 WellKnownFunctionKind::NodeExpress => {
160 JsValue::WellKnownObject(WellKnownObjectKind::NodeExpressApp)
161 }
162 WellKnownFunctionKind::NodeResolveFrom => {
164 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom)
165 }
166
167 _ => JsValue::unknown(
168 JsValue::call_from_parts(
169 arena.get_or_default(),
170 JsValue::WellKnownFunction(kind),
171 args,
172 ),
173 true,
174 rcstr!("unsupported function"),
175 ),
176 })
177}
178
179fn object_assign<'a>(arena: &'a Bump, args: BumpVec<'a, JsValue<'a>>) -> JsValue<'a> {
180 if args.iter().all(|arg| matches!(arg, JsValue::Object { .. })) {
181 if let Some(mut merged_object) = args.into_iter().reduce(|mut acc, cur| {
182 if let JsValue::Object {
183 parts, mutability, ..
184 } = &mut acc
185 && let JsValue::Object {
186 parts: next_parts,
187 mutability: next_mutability,
188 ..
189 } = &cur
190 {
191 parts.extend(arena, next_parts.iter().map(|p| p.clone_in(arena)));
192 mutability.merge_with(*next_mutability);
193 }
194 acc
195 }) {
196 merged_object.update_total_nodes();
197 merged_object
198 } else {
199 JsValue::unknown(
200 JsValue::call_from_iter(
201 arena,
202 JsValue::WellKnownFunction(WellKnownFunctionKind::ObjectAssign),
203 [],
204 ),
205 true,
206 rcstr!("empty arguments for Object.assign"),
207 )
208 }
209 } else {
210 JsValue::unknown(
211 JsValue::call_from_parts(
212 arena,
213 JsValue::WellKnownFunction(WellKnownFunctionKind::ObjectAssign),
214 args,
215 ),
216 true,
217 rcstr!("only const object assign is supported"),
218 )
219 }
220}
221
222fn path_join<'a>(arena: &'a Bump, args: BumpVec<'a, JsValue<'a>>) -> JsValue<'a> {
223 if args.is_empty() {
224 return rcstr!(".").into();
225 }
226 let mut locked_prefix: SmallVec<[JsValue<'a>; 16]> = SmallVec::new();
227 let mut segments: SmallVec<[JsValue<'a>; 16]> = SmallVec::new();
228 for arg in args {
229 let arg_parts = if let Some(str) = arg.as_str() {
230 let split = str.split('/');
231 Either::Left(split.map(|s| s.into()))
232 } else {
233 Either::Right(iter::once(arg))
234 };
235 for item in arg_parts {
236 if let Some(str) = item.as_str() {
237 match str {
238 "" | "." => {
239 if locked_prefix.is_empty() && segments.is_empty() {
240 locked_prefix.push(item);
241 }
242 }
243 ".." => {
244 if segments.pop().is_none() {
245 locked_prefix.push(item);
246 }
247 }
248 _ => segments.push(item),
249 }
250 } else {
251 locked_prefix.append(&mut segments);
252 locked_prefix.push(item);
253 }
254 }
255 }
256 locked_prefix.append(&mut segments);
257 let mut iter = locked_prefix.into_iter();
258 let first = iter.next().unwrap();
259 let mut last_is_str = first.as_str().is_some();
260 let mut result = segments;
263 result.push(first);
264 for part in iter {
265 let is_str = part.as_str().is_some();
266 if last_is_str && is_str {
267 result.push(rcstr!("/").into());
268 } else {
269 result.push(JsValue::alternatives(BumpVec::from_iter_in(
270 arena,
271 [rcstr!("/").into(), rcstr!("").into()],
272 )));
273 }
274 result.push(part);
275 last_is_str = is_str;
276 }
277 JsValue::concat(BumpVec::from_iter_in(arena, result))
278}
279
280fn path_resolve<'a>(
281 arena: &'a Bump,
282 cwd: JsValue<'a>,
283 mut args: BumpVec<'a, JsValue<'a>>,
284) -> JsValue<'a> {
285 if args.is_empty() {
288 return JsValue::unknown_empty(false, rcstr!("cwd is not static analyzable"));
289 }
290 if args.len() == 1 {
291 return args.into_iter().next().unwrap();
292 }
293
294 for (idx, arg) in args.iter().enumerate().rev() {
296 if idx != 0
297 && let Some(str) = arg.as_str()
298 && str.starts_with('/')
299 {
300 return path_resolve(arena, cwd, args.split_off(arena, idx));
301 }
302 }
303
304 let mut results_final: SmallVec<[JsValue<'a>; 16]> = SmallVec::new();
305 let mut results: SmallVec<[JsValue<'a>; 16]> = SmallVec::new();
306 for arg in args {
307 let arg_parts = if let Some(str) = arg.as_str() {
308 let split = str.split('/');
309 Either::Left(split.map(|s| s.into()))
310 } else {
311 Either::Right(iter::once(arg))
312 };
313 for item in arg_parts {
314 if let Some(str) = item.as_str() {
315 match str {
316 "" | "." => {
317 if results_final.is_empty() && results.is_empty() {
318 results_final.push(item);
319 }
320 }
321 ".." => {
322 if results.pop().is_none() {
323 results_final.push(item);
324 }
325 }
326 _ => results.push(item),
327 }
328 } else {
329 results_final.append(&mut results);
330 results_final.push(item);
331 }
332 }
333 }
334 results_final.append(&mut results);
335 let mut iter = results_final.into_iter();
336 let first = iter.next().unwrap();
337
338 let is_already_absolute =
339 first.is_empty_string() == Some(true) || first.starts_with("/") == Some(true);
340
341 let mut last_was_str = first.as_str().is_some();
342
343 if !is_already_absolute {
344 results.push(cwd);
345 }
346
347 results.push(first);
348 for part in iter {
349 let is_str = part.as_str().is_some();
350 if last_was_str && is_str {
351 results.push(rcstr!("/").into());
352 } else {
353 results.push(JsValue::alternatives(BumpVec::from_iter_in(
354 arena,
355 [rcstr!("/").into(), rcstr!("").into()],
356 )));
357 }
358 results.push(part);
359 last_was_str = is_str;
360 }
361
362 JsValue::concat(BumpVec::from_iter_in(arena, results))
363}
364
365fn path_dirname<'a>(arena: &'a Bump, mut args: BumpVec<'a, JsValue<'a>>) -> JsValue<'a> {
366 if let Some(arg) = args.iter_mut().next() {
367 if let Some(str) = arg.as_str() {
368 if let Some(i) = str.rfind('/') {
369 return JsValue::Constant(ConstantValue::Str(str[..i].to_string().into()));
370 } else {
371 return JsValue::Constant(ConstantValue::Str(rcstr!("").into()));
372 }
373 } else if let JsValue::Concat(_, items) = arg
374 && let Some(last) = items.last_mut()
375 && let Some(str) = last.as_str()
376 && let Some(i) = str.rfind('/')
377 {
378 *last = JsValue::Constant(ConstantValue::Str(str[..i].to_string().into()));
379 return take(arg);
380 }
381 }
382 JsValue::unknown(
383 JsValue::call_from_parts(
384 arena,
385 JsValue::WellKnownFunction(WellKnownFunctionKind::PathDirname),
386 args,
387 ),
388 true,
389 rcstr!("path.dirname with unsupported arguments"),
390 )
391}
392
393pub fn import<'a>(arena: &'a Bump, args: BumpVec<'a, JsValue<'a>>) -> JsValue<'a> {
396 match &args[..] {
397 [JsValue::Constant(ConstantValue::Str(v))] => JsValue::promise(
398 arena,
399 JsValue::Module(ModuleValue {
400 module: v.as_atom().into_owned().into(),
401 annotations: None,
402 analyze_for_constants: false,
403 reference: None,
404 }),
405 ),
406 _ => JsValue::unknown(
407 JsValue::call_from_parts(
408 arena,
409 JsValue::WellKnownFunction(WellKnownFunctionKind::Import),
410 args,
411 ),
412 true,
413 rcstr!("only a single constant argument is supported"),
414 ),
415 }
416}
417
418fn require<'a>(arena: &'a Bump, args: BumpVec<'a, JsValue<'a>>) -> JsValue<'a> {
421 if args.len() == 1 {
422 if let Some(s) = args[0].as_str() {
423 JsValue::Module(ModuleValue {
424 module: s.into(),
425 annotations: None,
426 analyze_for_constants: false,
427 reference: None,
428 })
429 } else {
430 JsValue::unknown(
431 JsValue::call_from_parts(
432 arena,
433 JsValue::WellKnownFunction(WellKnownFunctionKind::Require),
434 args,
435 ),
436 true,
437 rcstr!("only constant argument is supported"),
438 )
439 }
440 } else {
441 JsValue::unknown(
442 JsValue::call_from_parts(
443 arena,
444 JsValue::WellKnownFunction(WellKnownFunctionKind::Require),
445 args,
446 ),
447 true,
448 rcstr!("only a single argument is supported"),
449 )
450 }
451}
452
453fn require_context_require<'a>(
455 arena: &'a Bump,
456 val: Box<RequireContextValue>,
457 args: BumpVec<'a, JsValue<'a>>,
458) -> Result<JsValue<'a>> {
459 if args.is_empty() {
460 return Ok(JsValue::unknown(
461 JsValue::call_from_parts(
462 arena,
463 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequire(val)),
464 args,
465 ),
466 true,
467 rcstr!(
468 "require.context(...).require() requires an argument specifying the module path"
469 ),
470 ));
471 }
472
473 let Some(s) = args[0].as_str() else {
474 return Ok(JsValue::unknown(
475 JsValue::call_from_parts(
476 arena,
477 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequire(val)),
478 args,
479 ),
480 true,
481 rcstr!(
482 "require.context(...).require() only accepts a single, constant string argument"
483 ),
484 ));
485 };
486
487 let Some(m) = val.0.get(s) else {
488 return Ok(JsValue::unknown(
489 JsValue::call_from_parts(
490 arena,
491 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequire(val)),
492 args,
493 ),
494 true,
495 rcstr!(
496 "require.context(...).require() can only be called with an argument that's in the \
497 context"
498 ),
499 ));
500 };
501
502 Ok(JsValue::Module(ModuleValue {
503 module: m.to_string().into(),
504 annotations: None,
505 analyze_for_constants: false,
506 reference: None,
507 }))
508}
509
510fn require_context_require_keys<'a>(
512 arena: &'a Bump,
513 val: Box<RequireContextValue>,
514 args: BumpVec<'a, JsValue<'a>>,
515) -> Result<JsValue<'a>> {
516 Ok(if args.is_empty() {
517 JsValue::array(BumpVec::from_iter_in(
518 arena,
519 val.0.keys().cloned().map(|k| k.into()),
520 ))
521 } else {
522 JsValue::unknown(
523 JsValue::call_from_parts(
524 arena,
525 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireKeys(val)),
526 args,
527 ),
528 true,
529 rcstr!("require.context(...).keys() does not accept arguments"),
530 )
531 })
532}
533
534fn require_context_require_resolve<'a>(
536 arena: &'a Bump,
537 val: Box<RequireContextValue>,
538 args: BumpVec<'a, JsValue<'a>>,
539) -> Result<JsValue<'a>> {
540 if args.len() != 1 {
541 return Ok(JsValue::unknown(
542 JsValue::call_from_parts(
543 arena,
544 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireResolve(
545 val,
546 )),
547 args,
548 ),
549 true,
550 rcstr!(
551 "require.context(...).resolve() only accepts a single, constant string argument"
552 ),
553 ));
554 }
555
556 let Some(s) = args[0].as_str() else {
557 return Ok(JsValue::unknown(
558 JsValue::call_from_parts(
559 arena,
560 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireResolve(
561 val,
562 )),
563 args,
564 ),
565 true,
566 rcstr!(
567 "require.context(...).resolve() only accepts a single, constant string argument"
568 ),
569 ));
570 };
571
572 let Some(m) = val.0.get(s) else {
573 return Ok(JsValue::unknown(
574 JsValue::call_from_parts(
575 arena,
576 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireResolve(
577 val,
578 )),
579 args,
580 ),
581 true,
582 rcstr!(
583 "require.context(...).resolve() can only be called with an argument that's in the \
584 context"
585 ),
586 ));
587 };
588
589 Ok(m.as_str().into())
590}
591
592fn path_to_file_url<'a>(arena: &'a Bump, args: BumpVec<'a, JsValue<'a>>) -> JsValue<'a> {
593 if args.len() == 1 {
594 if let Some(path) = args[0].as_str() {
595 Url::from_file_path(path)
596 .map(|url| JsValue::Url(String::from(url).into(), JsValueUrlKind::Absolute))
597 .unwrap_or_else(|_| {
598 JsValue::unknown(
599 JsValue::call_from_parts(
600 arena,
601 JsValue::WellKnownFunction(WellKnownFunctionKind::PathToFileUrl),
602 args,
603 ),
604 true,
605 rcstr!("url not parseable: path is relative or has an invalid prefix"),
606 )
607 })
608 } else {
609 JsValue::unknown(
610 JsValue::call_from_parts(
611 arena,
612 JsValue::WellKnownFunction(WellKnownFunctionKind::PathToFileUrl),
613 args,
614 ),
615 true,
616 rcstr!("only constant argument is supported"),
617 )
618 }
619 } else {
620 JsValue::unknown(
621 JsValue::call_from_parts(
622 arena,
623 JsValue::WellKnownFunction(WellKnownFunctionKind::PathToFileUrl),
624 args,
625 ),
626 true,
627 rcstr!("only a single argument is supported"),
628 )
629 }
630}
631
632fn well_known_function_member<'a>(
633 arena: &'a Bump,
634 kind: WellKnownFunctionKind<'a>,
635 prop: JsValue<'a>,
636) -> (JsValue<'a>, Modified) {
637 let new_value = match (kind, prop.as_str()) {
638 (WellKnownFunctionKind::Require, Some("resolve")) => {
639 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireResolve)
640 }
641 (WellKnownFunctionKind::Require, Some("cache")) => {
642 JsValue::WellKnownObject(WellKnownObjectKind::RequireCache)
643 }
644 (WellKnownFunctionKind::Require, Some("context")) => {
645 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContext)
646 }
647 (WellKnownFunctionKind::RequireContextRequire(val), Some("resolve")) => {
648 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireResolve(val))
649 }
650 (WellKnownFunctionKind::RequireContextRequire(val), Some("keys")) => {
651 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireKeys(val))
652 }
653 (WellKnownFunctionKind::NodeStrongGlobalize, Some("SetRootDir")) => {
654 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir)
655 }
656 (WellKnownFunctionKind::NodeResolveFrom, Some("silent")) => {
657 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom)
658 }
659 (WellKnownFunctionKind::Import, Some("meta")) => {
660 JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta)
661 }
662 #[allow(unreachable_patterns)]
663 (kind, _) => {
664 return (
665 JsValue::member(arena, JsValue::WellKnownFunction(kind), prop),
666 Modified::No,
667 );
668 }
669 };
670 (new_value, Modified::Yes)
671}
672
673async fn well_known_object_member<'a>(
674 arena: &'a ThreadLocal<Bump>,
675 kind: WellKnownObjectKind,
676 prop: JsValue<'a>,
677 compile_time_info: Vc<CompileTimeInfo>,
678) -> Result<(JsValue<'a>, Modified)> {
679 let new_value = match kind {
680 WellKnownObjectKind::GlobalObject => global_object(arena.get_or_default(), prop),
681 WellKnownObjectKind::PathModule | WellKnownObjectKind::PathModuleDefault => {
682 path_module_member(arena, kind, prop, compile_time_info).await?
683 }
684 WellKnownObjectKind::FsModule
685 | WellKnownObjectKind::FsModuleDefault
686 | WellKnownObjectKind::FsModulePromises
687 | WellKnownObjectKind::GracefulFsModule
688 | WellKnownObjectKind::GracefulFsModuleDefault => {
689 fs_module_member(arena.get_or_default(), kind, prop)
690 }
691 WellKnownObjectKind::FsExtraModule | WellKnownObjectKind::FsExtraModuleDefault => {
692 fs_extra_module_member(arena.get_or_default(), kind, prop)
693 }
694 WellKnownObjectKind::ModuleModule | WellKnownObjectKind::ModuleModuleDefault => {
695 module_module_member(arena.get_or_default(), kind, prop)
696 }
697 WellKnownObjectKind::UrlModule | WellKnownObjectKind::UrlModuleDefault => {
698 url_module_member(arena.get_or_default(), kind, prop)
699 }
700 WellKnownObjectKind::WorkerThreadsModule
701 | WellKnownObjectKind::WorkerThreadsModuleDefault => {
702 worker_threads_module_member(arena.get_or_default(), kind, prop)
703 }
704 WellKnownObjectKind::ChildProcessModule
705 | WellKnownObjectKind::ChildProcessModuleDefault => {
706 child_process_module_member(arena.get_or_default(), kind, prop)
707 }
708 WellKnownObjectKind::OsModule | WellKnownObjectKind::OsModuleDefault => {
709 os_module_member(arena.get_or_default(), kind, prop)
710 }
711 WellKnownObjectKind::NodeProcessModule => {
712 node_process_member(arena, prop, compile_time_info).await?
713 }
714 WellKnownObjectKind::NodePreGyp => node_pre_gyp(arena.get_or_default(), prop),
715 WellKnownObjectKind::NodeExpressApp => express(arena.get_or_default(), prop),
716 WellKnownObjectKind::NodeProtobufLoader => protobuf_loader(arena.get_or_default(), prop),
717 WellKnownObjectKind::ImportMeta => match prop.as_str() {
718 Some("env") => JsValue::WellKnownObject(WellKnownObjectKind::ImportMetaEnv),
719 Some("turbopackHot") if compile_time_info.await?.hot_module_replacement_enabled => {
721 JsValue::WellKnownObject(WellKnownObjectKind::ModuleHot)
722 }
723 Some("glob") => JsValue::WellKnownFunction(WellKnownFunctionKind::ImportMetaGlob),
727 _ => {
728 return Ok((
729 JsValue::member(arena.get_or_default(), JsValue::WellKnownObject(kind), prop),
730 Modified::No,
731 ));
732 }
733 },
734 WellKnownObjectKind::ImportMetaEnv => {
735 let compile_time_info = compile_time_info.await?;
736 let mode = compile_time_info
737 .defines
738 .read_process_env(rcstr!("NODE_ENV"))
739 .owned()
740 .await?
741 .unwrap_or_else(|| rcstr!("development"));
742 let is_prod = mode == "production";
743
744 match prop.as_str() {
745 Some("MODE") => JsValue::from(mode),
746 Some("PROD") => JsValue::from(ConstantValue::from(is_prod)),
747 Some("DEV") => JsValue::from(ConstantValue::from(!is_prod)),
748 Some("BASE_URL") => {
749 JsValue::from(compile_time_info.import_meta_env_base_url.clone())
750 }
751 Some("SSR") => JsValue::from(ConstantValue::from(matches!(
752 *compile_time_info.environment.rendering().await?,
753 Rendering::Server
754 ))),
755 Some(_) => JsValue::Constant(ConstantValue::Undefined),
756 None => {
757 return Ok((
758 JsValue::member(
759 arena.get_or_default(),
760 JsValue::WellKnownObject(kind),
761 prop,
762 ),
763 Modified::No,
764 ));
765 }
766 }
767 }
768 WellKnownObjectKind::ModuleHot => match prop.as_str() {
769 Some("accept") => JsValue::WellKnownFunction(WellKnownFunctionKind::ModuleHotAccept),
770 Some("decline") => JsValue::WellKnownFunction(WellKnownFunctionKind::ModuleHotDecline),
771 _ => {
772 return Ok((
773 JsValue::unknown(
774 JsValue::member(
775 arena.get_or_default(),
776 JsValue::WellKnownObject(kind),
777 prop,
778 ),
779 true,
780 rcstr!("unsupported property on module.hot"),
781 ),
782 Modified::Yes,
783 ));
784 }
785 },
786 WellKnownObjectKind::Navigator => match prop.as_str() {
787 Some("serviceWorker") => {
788 JsValue::WellKnownObject(WellKnownObjectKind::NavigatorServiceWorker)
789 }
790 _ => {
791 return Ok((
792 JsValue::member(arena.get_or_default(), JsValue::WellKnownObject(kind), prop),
793 Modified::No,
794 ));
795 }
796 },
797 WellKnownObjectKind::NavigatorServiceWorker => match prop.as_str() {
798 Some("register") => {
799 JsValue::WellKnownFunction(WellKnownFunctionKind::ServiceWorkerRegister)
800 }
801 _ => {
802 return Ok((
803 JsValue::member(arena.get_or_default(), JsValue::WellKnownObject(kind), prop),
804 Modified::No,
805 ));
806 }
807 },
808 #[allow(unreachable_patterns)]
809 _ => {
810 return Ok((
811 JsValue::member(arena.get_or_default(), JsValue::WellKnownObject(kind), prop),
812 Modified::No,
813 ));
814 }
815 };
816 Ok((new_value, Modified::Yes))
817}
818
819fn global_object<'a>(arena: &'a Bump, prop: JsValue<'a>) -> JsValue<'a> {
820 match prop.as_str() {
821 Some("assign") => JsValue::WellKnownFunction(WellKnownFunctionKind::ObjectAssign),
822 _ => JsValue::unknown(
823 JsValue::member(
824 arena,
825 JsValue::WellKnownObject(WellKnownObjectKind::GlobalObject),
826 prop,
827 ),
828 true,
829 rcstr!("unsupported property on global Object"),
830 ),
831 }
832}
833
834async fn path_module_member<'a>(
835 arena: &'a ThreadLocal<Bump>,
836 kind: WellKnownObjectKind,
837 prop: JsValue<'a>,
838 compile_time_info: Vc<CompileTimeInfo>,
839) -> Result<JsValue<'a>> {
840 Ok(match (kind, prop.as_str()) {
841 (.., Some("join")) => JsValue::WellKnownFunction(WellKnownFunctionKind::PathJoin),
842 (.., Some("dirname")) => JsValue::WellKnownFunction(WellKnownFunctionKind::PathDirname),
843 (.., Some("resolve")) => {
844 JsValue::WellKnownFunction(WellKnownFunctionKind::PathResolve(
846 arena.get_or_default().alloc(JsValue::from("")),
847 ))
848 }
849 (.., Some("sep")) => compile_time_info
850 .environment()
851 .compile_target()
852 .await?
853 .platform
854 .path_separator()
855 .into(),
856 (WellKnownObjectKind::PathModule, Some("default")) => {
857 JsValue::WellKnownObject(WellKnownObjectKind::PathModuleDefault)
858 }
859 _ => JsValue::unknown(
860 JsValue::member(
861 arena.get_or_default(),
862 JsValue::WellKnownObject(WellKnownObjectKind::PathModule),
863 prop,
864 ),
865 true,
866 rcstr!("unsupported property on Node.js path module"),
867 ),
868 })
869}
870
871fn fs_module_member<'a>(
872 arena: &'a Bump,
873 kind: WellKnownObjectKind,
874 prop: JsValue<'a>,
875) -> JsValue<'a> {
876 if let Some(word) = prop.as_str() {
877 match (kind, word) {
878 (
879 ..,
880 "realpath" | "realpathSync" | "stat" | "statSync" | "existsSync"
881 | "createReadStream" | "exists" | "open" | "openSync" | "readFile" | "readFileSync",
882 ) => {
883 return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadMethod(
884 word.into(),
885 ));
886 }
887 (.., "readdir" | "readdirSync") => {
888 return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadDir);
889 }
890 (WellKnownObjectKind::FsModule | WellKnownObjectKind::FsModuleDefault, "promises") => {
891 return JsValue::WellKnownObject(WellKnownObjectKind::FsModulePromises);
892 }
893 (WellKnownObjectKind::FsModule, "default") => {
894 return JsValue::WellKnownObject(WellKnownObjectKind::FsModuleDefault);
895 }
896 _ => {}
897 }
898 }
899 JsValue::unknown(
900 JsValue::member(
901 arena,
902 JsValue::WellKnownObject(WellKnownObjectKind::FsModule),
903 prop,
904 ),
905 true,
906 rcstr!("unsupported property on Node.js fs module"),
907 )
908}
909
910fn fs_extra_module_member<'a>(
911 arena: &'a Bump,
912 kind: WellKnownObjectKind,
913 prop: JsValue<'a>,
914) -> JsValue<'a> {
915 if let Some(word) = prop.as_str() {
916 match (kind, word) {
917 (
919 ..,
920 "realpath" | "realpathSync" | "stat" | "statSync" | "existsSync"
921 | "createReadStream" | "exists" | "open" | "openSync" | "readFile" | "readFileSync",
922 ) => {
923 return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadMethod(
924 word.into(),
925 ));
926 }
927 (.., "readdir" | "readdirSync") => {
928 return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadDir);
929 }
930 (
932 ..,
933 "pathExists" | "pathExistsSync" | "readJson" | "readJSON" | "readJsonSync"
934 | "readJSONSync",
935 ) => {
936 return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadMethod(
937 word.into(),
938 ));
939 }
940 (WellKnownObjectKind::FsExtraModule, "default") => {
941 return JsValue::WellKnownObject(WellKnownObjectKind::FsExtraModuleDefault);
942 }
943 _ => {}
944 }
945 }
946 JsValue::unknown(
947 JsValue::member(
948 arena,
949 JsValue::WellKnownObject(WellKnownObjectKind::FsExtraModule),
950 prop,
951 ),
952 true,
953 rcstr!("unsupported property on fs-extra module"),
954 )
955}
956
957fn module_module_member<'a>(
958 arena: &'a Bump,
959 kind: WellKnownObjectKind,
960 prop: JsValue<'a>,
961) -> JsValue<'a> {
962 match (kind, prop.as_str()) {
963 (.., Some("createRequire")) => {
964 JsValue::WellKnownFunction(WellKnownFunctionKind::CreateRequire)
965 }
966 (WellKnownObjectKind::ModuleModule, Some("default")) => {
967 JsValue::WellKnownObject(WellKnownObjectKind::ModuleModuleDefault)
968 }
969 _ => JsValue::unknown(
970 JsValue::member(
971 arena,
972 JsValue::WellKnownObject(WellKnownObjectKind::ModuleModule),
973 prop,
974 ),
975 true,
976 rcstr!("unsupported property on Node.js `module` module"),
977 ),
978 }
979}
980
981fn url_module_member<'a>(
982 arena: &'a Bump,
983 kind: WellKnownObjectKind,
984 prop: JsValue<'a>,
985) -> JsValue<'a> {
986 match (kind, prop.as_str()) {
987 (.., Some("pathToFileURL")) => {
988 JsValue::WellKnownFunction(WellKnownFunctionKind::PathToFileUrl)
989 }
990 (WellKnownObjectKind::UrlModule, Some("default")) => {
991 JsValue::WellKnownObject(WellKnownObjectKind::UrlModuleDefault)
992 }
993 _ => JsValue::unknown(
994 JsValue::member(
995 arena,
996 JsValue::WellKnownObject(WellKnownObjectKind::UrlModule),
997 prop,
998 ),
999 true,
1000 rcstr!("unsupported property on Node.js url module"),
1001 ),
1002 }
1003}
1004
1005fn worker_threads_module_member<'a>(
1006 arena: &'a Bump,
1007 kind: WellKnownObjectKind,
1008 prop: JsValue<'a>,
1009) -> JsValue<'a> {
1010 match (kind, prop.as_str()) {
1011 (.., Some("Worker")) => {
1012 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeWorkerConstructor)
1013 }
1014 (WellKnownObjectKind::WorkerThreadsModule, Some("default")) => {
1015 JsValue::WellKnownObject(WellKnownObjectKind::WorkerThreadsModuleDefault)
1016 }
1017 _ => JsValue::unknown(
1018 JsValue::member(
1019 arena,
1020 JsValue::WellKnownObject(WellKnownObjectKind::WorkerThreadsModule),
1021 prop,
1022 ),
1023 true,
1024 rcstr!("unsupported property on Node.js worker_threads module"),
1025 ),
1026 }
1027}
1028
1029fn child_process_module_member<'a>(
1030 arena: &'a Bump,
1031 kind: WellKnownObjectKind,
1032 prop: JsValue<'a>,
1033) -> JsValue<'a> {
1034 let prop_str = prop.as_str();
1035 match (kind, prop_str) {
1036 (.., Some("spawn" | "spawnSync" | "execFile" | "execFileSync")) => {
1037 JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessSpawnMethod(
1038 prop_str.unwrap().into(),
1039 ))
1040 }
1041 (.., Some("fork")) => JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessFork),
1042 (WellKnownObjectKind::ChildProcessModule, Some("default")) => {
1043 JsValue::WellKnownObject(WellKnownObjectKind::ChildProcessModuleDefault)
1044 }
1045
1046 _ => JsValue::unknown(
1047 JsValue::member(
1048 arena,
1049 JsValue::WellKnownObject(WellKnownObjectKind::ChildProcessModule),
1050 prop,
1051 ),
1052 true,
1053 rcstr!("unsupported property on Node.js child_process module"),
1054 ),
1055 }
1056}
1057
1058fn os_module_member<'a>(
1059 arena: &'a Bump,
1060 kind: WellKnownObjectKind,
1061 prop: JsValue<'a>,
1062) -> JsValue<'a> {
1063 match (kind, prop.as_str()) {
1064 (.., Some("platform")) => JsValue::WellKnownFunction(WellKnownFunctionKind::OsPlatform),
1065 (.., Some("arch")) => JsValue::WellKnownFunction(WellKnownFunctionKind::OsArch),
1066 (.., Some("endianness")) => JsValue::WellKnownFunction(WellKnownFunctionKind::OsEndianness),
1067 (WellKnownObjectKind::OsModule, Some("default")) => {
1068 JsValue::WellKnownObject(WellKnownObjectKind::OsModuleDefault)
1069 }
1070 _ => JsValue::unknown(
1071 JsValue::member(
1072 arena,
1073 JsValue::WellKnownObject(WellKnownObjectKind::OsModule),
1074 prop,
1075 ),
1076 true,
1077 rcstr!("unsupported property on Node.js os module"),
1078 ),
1079 }
1080}
1081
1082async fn node_process_member<'a>(
1083 arena: &'a ThreadLocal<Bump>,
1084 prop: JsValue<'a>,
1085 compile_time_info: Vc<CompileTimeInfo>,
1086) -> Result<JsValue<'a>> {
1087 Ok(match prop.as_str() {
1088 Some("arch") => compile_time_info
1089 .environment()
1090 .compile_target()
1091 .await?
1092 .arch
1093 .as_str()
1094 .into(),
1095 Some("platform") => compile_time_info
1096 .environment()
1097 .compile_target()
1098 .await?
1099 .platform
1100 .as_str()
1101 .into(),
1102 Some("cwd") => JsValue::WellKnownFunction(WellKnownFunctionKind::ProcessCwd),
1103 Some("argv") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessArgv),
1104 Some("env") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessEnv),
1105 _ => JsValue::unknown(
1106 JsValue::member(
1107 arena.get_or_default(),
1108 JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessModule),
1109 prop,
1110 ),
1111 true,
1112 rcstr!("unsupported property on Node.js process object"),
1113 ),
1114 })
1115}
1116
1117fn node_pre_gyp<'a>(arena: &'a Bump, prop: JsValue<'a>) -> JsValue<'a> {
1118 match prop.as_str() {
1119 Some("find") => JsValue::WellKnownFunction(WellKnownFunctionKind::NodePreGypFind),
1120 _ => JsValue::unknown(
1121 JsValue::member(
1122 arena,
1123 JsValue::WellKnownObject(WellKnownObjectKind::NodePreGyp),
1124 prop,
1125 ),
1126 true,
1127 rcstr!("unsupported property on @mapbox/node-pre-gyp module"),
1128 ),
1129 }
1130}
1131
1132fn express<'a>(arena: &'a Bump, prop: JsValue<'a>) -> JsValue<'a> {
1133 match prop.as_str() {
1134 Some("set") => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeExpressSet),
1135 _ => JsValue::unknown(
1136 JsValue::member(
1137 arena,
1138 JsValue::WellKnownObject(WellKnownObjectKind::NodeExpressApp),
1139 prop,
1140 ),
1141 true,
1142 rcstr!("unsupported property on require('express')() object"),
1143 ),
1144 }
1145}
1146
1147fn protobuf_loader<'a>(arena: &'a Bump, prop: JsValue<'a>) -> JsValue<'a> {
1148 match prop.as_str() {
1149 Some("load") | Some("loadSync") => {
1150 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeProtobufLoad)
1151 }
1152 _ => JsValue::unknown(
1153 JsValue::member(
1154 arena,
1155 JsValue::WellKnownObject(WellKnownObjectKind::NodeProtobufLoader),
1156 prop,
1157 ),
1158 true,
1159 rcstr!("unsupported property on require('@grpc/proto-loader') object"),
1160 ),
1161 }
1162}
1163
1164#[cfg(test)]
1165mod tests {
1166 use bumpalo::Bump;
1167
1168 use super::path_join;
1169 use crate::analyzer::{BumpVec, JsValue};
1170
1171 fn render(value: &JsValue<'_>) -> String {
1186 match value {
1187 JsValue::Concat(_, parts) => parts.iter().map(render).collect(),
1188 JsValue::Alternatives { values, .. } => render(&values[0]),
1189 JsValue::FreeVar(name) => name.to_string(),
1190 other => other
1191 .as_str()
1192 .expect("path_join over constant strings should yield constant strings")
1193 .to_string(),
1194 }
1195 }
1196
1197 fn join(arena: &Bump, segments: &[&str]) -> String {
1200 let args = BumpVec::from_iter_in(arena, segments.iter().map(|s| JsValue::from(*s)));
1201 render(&path_join(arena, args))
1202 }
1203
1204 #[test]
1210 fn matches_node_path_posix_join() {
1211 let arena = Bump::new();
1212
1213 assert_eq!(join(&arena, &[]), ".");
1214 assert_eq!(join(&arena, &["/.", "x/b", "..", "/b/c.js"]), "/x/b/c.js");
1215 assert_eq!(join(&arena, &["foo", "../../../bar"]), "../../bar");
1216 assert_eq!(join(&arena, &["foo/", "../../../bar"]), "../../bar");
1217 assert_eq!(join(&arena, &["foo/x", "../../../bar"]), "../bar");
1218 assert_eq!(join(&arena, &["foo/x", "./bar"]), "foo/x/bar");
1219 assert_eq!(join(&arena, &["foo/x/", "./bar"]), "foo/x/bar");
1220 assert_eq!(join(&arena, &["foo/x/", ".", "bar"]), "foo/x/bar");
1221 assert_eq!(join(&arena, &[".", ".", "."]), ".");
1222 assert_eq!(join(&arena, &[".", "./", "."]), ".");
1223 assert_eq!(join(&arena, &[".", "/./", "."]), ".");
1224 assert_eq!(join(&arena, &[".", "/////./", "."]), ".");
1225 assert_eq!(join(&arena, &["."]), ".");
1226 assert_eq!(join(&arena, &["foo", "/bar"]), "foo/bar");
1227 assert_eq!(join(&arena, &["", "/foo"]), "/foo");
1228 assert_eq!(join(&arena, &["", "", "/foo"]), "/foo");
1229 assert_eq!(join(&arena, &["foo", ""]), "foo");
1230 assert_eq!(join(&arena, &["foo", "", "/bar"]), "foo/bar");
1231 assert_eq!(join(&arena, &[" /foo"]), " /foo");
1232 assert_eq!(join(&arena, &[" ", "foo"]), " /foo");
1233 assert_eq!(join(&arena, &[" ", "."]), " ");
1234 assert_eq!(join(&arena, &[" ", ""]), " ");
1235 assert_eq!(join(&arena, &["/", "foo"]), "/foo");
1236 assert_eq!(join(&arena, &["/", "/foo"]), "/foo");
1237 assert_eq!(join(&arena, &["/", "//foo"]), "/foo");
1238 assert_eq!(join(&arena, &["/", "", "/foo"]), "/foo");
1239 assert_eq!(join(&arena, &["", "/", "foo"]), "/foo");
1240 assert_eq!(join(&arena, &["", "/", "/foo"]), "/foo");
1241 }
1242
1243 #[test]
1245 fn dotdot_pops_from_segments() {
1246 let arena = Bump::new();
1247
1248 assert_eq!(join(&arena, &["foo/bar/baz", "../.."]), "foo");
1249 assert_eq!(join(&arena, &["a/b", ".."]), "a");
1250 assert_eq!(join(&arena, &["a/b/c/d", "../../.."]), "a");
1251 assert_eq!(join(&arena, &["a/b", "../../c"]), "c");
1253 }
1254
1255 #[test]
1259 fn unpoppable_dotdot_is_locked_into_prefix() {
1260 let arena = Bump::new();
1261
1262 assert_eq!(join(&arena, &["../../foo"]), "../../foo");
1263 assert_eq!(join(&arena, &["..", "foo", ".."]), "..");
1266 assert_eq!(join(&arena, &["/foo", "../../bar"]), "/../bar");
1268 }
1269
1270 #[test]
1274 fn leading_dot_is_locked_but_interior_is_dropped() {
1275 let arena = Bump::new();
1276
1277 assert_eq!(join(&arena, &["./foo", ".", "bar"]), "./foo/bar");
1278 assert_eq!(join(&arena, &["foo/x", ".", "bar"]), "foo/x/bar");
1279 assert_eq!(join(&arena, &[".", ".", "."]), ".");
1280 }
1281
1282 #[test]
1296 fn diverges_from_node_path_posix_join() {
1297 }
1322
1323 #[test]
1327 fn dynamic_segment_freezes_preceding_segments() {
1328 let arena = Bump::new();
1329
1330 assert_eq!(join(&arena, &["foo", "x", ".."]), "foo");
1332
1333 let args = BumpVec::from_iter_in(
1336 &arena,
1337 [
1338 JsValue::from("foo"),
1339 JsValue::FreeVar("dynamic".into()),
1340 JsValue::from(".."),
1341 ],
1342 );
1343 assert_eq!(render(&path_join(&arena, args)), "foo/dynamic/..");
1344 }
1345}