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