Skip to main content

turbopack_ecmascript/analyzer/
well_known.rs

1use std::mem::take;
2
3use anyhow::Result;
4use turbo_rcstr::rcstr;
5use turbo_tasks::Vc;
6use turbopack_core::compile_time_info::CompileTimeInfo;
7use url::Url;
8
9use super::{
10    ConstantValue, JsValue, JsValueUrlKind, ModuleValue, WellKnownFunctionKind, WellKnownObjectKind,
11};
12use crate::analyzer::RequireContextValue;
13
14pub async fn replace_well_known(
15    value: JsValue,
16    compile_time_info: Vc<CompileTimeInfo>,
17    allow_project_root_tracing: bool,
18) -> Result<(JsValue, bool)> {
19    Ok(match value {
20        JsValue::Call(_, box JsValue::WellKnownFunction(kind), args) => (
21            well_known_function_call(
22                kind,
23                JsValue::unknown_empty(false, "this is not analyzed yet"),
24                args,
25                compile_time_info,
26                allow_project_root_tracing,
27            )
28            .await?,
29            true,
30        ),
31        JsValue::Call(usize, callee, args) => {
32            // var fs = require('fs'), fs = __importStar(fs);
33            // TODO(WEB-552) this is not correct and has many false positives!
34            if args.len() == 1
35                && let JsValue::WellKnownObject(_) = &args[0]
36            {
37                return Ok((args[0].clone(), true));
38            }
39            (JsValue::Call(usize, callee, args), false)
40        }
41        JsValue::Member(_, box JsValue::WellKnownObject(kind), box prop) => {
42            well_known_object_member(kind, prop, compile_time_info).await?
43        }
44        JsValue::Member(_, box JsValue::WellKnownFunction(kind), box prop) => {
45            well_known_function_member(kind, prop)
46        }
47        JsValue::Member(_, box JsValue::Array { .. }, box ref prop) => match prop.as_str() {
48            Some("filter") => (
49                JsValue::WellKnownFunction(WellKnownFunctionKind::ArrayFilter),
50                true,
51            ),
52            Some("forEach") => (
53                JsValue::WellKnownFunction(WellKnownFunctionKind::ArrayForEach),
54                true,
55            ),
56            Some("map") => (
57                JsValue::WellKnownFunction(WellKnownFunctionKind::ArrayMap),
58                true,
59            ),
60            _ => (value, false),
61        },
62        // module.hot → WellKnownObject(ModuleHot) (only when HMR is enabled)
63        JsValue::Member(_, box JsValue::FreeVar(ref name), box ref prop)
64            if &**name == "module"
65                && prop.as_str() == Some("hot")
66                && compile_time_info.await?.hot_module_replacement_enabled =>
67        {
68            (
69                JsValue::WellKnownObject(WellKnownObjectKind::ModuleHot),
70                true,
71            )
72        }
73        _ => (value, false),
74    })
75}
76
77pub async fn well_known_function_call(
78    kind: WellKnownFunctionKind,
79    _this: JsValue,
80    args: Vec<JsValue>,
81    compile_time_info: Vc<CompileTimeInfo>,
82    allow_project_root_tracing: bool,
83) -> Result<JsValue> {
84    Ok(match kind {
85        WellKnownFunctionKind::ObjectAssign => object_assign(args),
86        WellKnownFunctionKind::PathJoin => path_join(args),
87        WellKnownFunctionKind::PathDirname => path_dirname(args),
88        WellKnownFunctionKind::PathResolve(cwd) => path_resolve(*cwd, args),
89        WellKnownFunctionKind::Import => import(args),
90        WellKnownFunctionKind::Require => require(args),
91        WellKnownFunctionKind::RequireContextRequire(value) => {
92            require_context_require(value, args)?
93        }
94        WellKnownFunctionKind::RequireContextRequireKeys(value) => {
95            require_context_require_keys(value, args)?
96        }
97        WellKnownFunctionKind::RequireContextRequireResolve(value) => {
98            require_context_require_resolve(value, args)?
99        }
100        WellKnownFunctionKind::PathToFileUrl => path_to_file_url(args),
101        WellKnownFunctionKind::OsArch => compile_time_info
102            .environment()
103            .compile_target()
104            .await?
105            .arch
106            .as_str()
107            .into(),
108        WellKnownFunctionKind::OsPlatform => compile_time_info
109            .environment()
110            .compile_target()
111            .await?
112            .platform
113            .as_str()
114            .into(),
115        WellKnownFunctionKind::ProcessCwd => {
116            if allow_project_root_tracing
117                && let Some(cwd) = &*compile_time_info.environment().cwd().await?
118            {
119                format!("/ROOT/{}", cwd.path).into()
120            } else {
121                JsValue::unknown(
122                    JsValue::call(Box::new(JsValue::WellKnownFunction(kind)), args),
123                    true,
124                    "process.cwd is not specified in the environment",
125                )
126            }
127        }
128        WellKnownFunctionKind::OsEndianness => compile_time_info
129            .environment()
130            .compile_target()
131            .await?
132            .endianness
133            .as_str()
134            .into(),
135        WellKnownFunctionKind::NodeExpress => {
136            JsValue::WellKnownObject(WellKnownObjectKind::NodeExpressApp)
137        }
138        // bypass
139        WellKnownFunctionKind::NodeResolveFrom => {
140            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom)
141        }
142
143        _ => JsValue::unknown(
144            JsValue::call(Box::new(JsValue::WellKnownFunction(kind)), args),
145            true,
146            "unsupported function",
147        ),
148    })
149}
150
151fn object_assign(args: Vec<JsValue>) -> JsValue {
152    if args.iter().all(|arg| matches!(arg, JsValue::Object { .. })) {
153        if let Some(mut merged_object) = args.into_iter().reduce(|mut acc, cur| {
154            if let JsValue::Object { parts, mutable, .. } = &mut acc
155                && let JsValue::Object {
156                    parts: next_parts,
157                    mutable: next_mutable,
158                    ..
159                } = &cur
160            {
161                parts.extend_from_slice(next_parts);
162                *mutable |= *next_mutable;
163            }
164            acc
165        }) {
166            merged_object.update_total_nodes();
167            merged_object
168        } else {
169            JsValue::unknown(
170                JsValue::call(
171                    Box::new(JsValue::WellKnownFunction(
172                        WellKnownFunctionKind::ObjectAssign,
173                    )),
174                    vec![],
175                ),
176                true,
177                "empty arguments for Object.assign",
178            )
179        }
180    } else {
181        JsValue::unknown(
182            JsValue::call(
183                Box::new(JsValue::WellKnownFunction(
184                    WellKnownFunctionKind::ObjectAssign,
185                )),
186                args,
187            ),
188            true,
189            "only const object assign is supported",
190        )
191    }
192}
193
194fn path_join(args: Vec<JsValue>) -> JsValue {
195    if args.is_empty() {
196        return rcstr!(".").into();
197    }
198    let mut parts = Vec::new();
199    for item in args {
200        if let Some(str) = item.as_str() {
201            let split = str.split('/');
202            parts.extend(split.map(|s| s.into()));
203        } else {
204            parts.push(item);
205        }
206    }
207    let mut results_final = Vec::new();
208    let mut results: Vec<JsValue> = Vec::new();
209    for item in parts {
210        if let Some(str) = item.as_str() {
211            match str {
212                "" | "." => {
213                    if results_final.is_empty() && results.is_empty() {
214                        results_final.push(item);
215                    }
216                }
217                ".." => {
218                    if results.pop().is_none() {
219                        results_final.push(item);
220                    }
221                }
222                _ => results.push(item),
223            }
224        } else {
225            results_final.append(&mut results);
226            results_final.push(item);
227        }
228    }
229    results_final.append(&mut results);
230    let mut iter = results_final.into_iter();
231    let first = iter.next().unwrap();
232    let mut last_is_str = first.as_str().is_some();
233    results.push(first);
234    for part in iter {
235        let is_str = part.as_str().is_some();
236        if last_is_str && is_str {
237            results.push(rcstr!("/").into());
238        } else {
239            results.push(JsValue::alternatives(vec![
240                rcstr!("/").into(),
241                rcstr!("").into(),
242            ]));
243        }
244        results.push(part);
245        last_is_str = is_str;
246    }
247    JsValue::concat(results)
248}
249
250fn path_resolve(cwd: JsValue, mut args: Vec<JsValue>) -> JsValue {
251    // If no path segments are passed, `path.resolve()` will return the absolute
252    // path of the current working directory.
253    if args.is_empty() {
254        return JsValue::unknown_empty(false, "cwd is not static analyzable");
255    }
256    if args.len() == 1 {
257        return args.into_iter().next().unwrap();
258    }
259
260    // path.resolve stops at the string starting with `/`
261    for (idx, arg) in args.iter().enumerate().rev() {
262        if idx != 0
263            && let Some(str) = arg.as_str()
264            && str.starts_with('/')
265        {
266            return path_resolve(cwd, args.drain(idx..).collect());
267        }
268    }
269
270    let mut results_final = Vec::new();
271    let mut results: Vec<JsValue> = Vec::new();
272    for item in args {
273        if let Some(str) = item.as_str() {
274            for str in str.split('/') {
275                match str {
276                    "" | "." => {
277                        if results_final.is_empty() && results.is_empty() {
278                            results_final.push(str.into());
279                        }
280                    }
281                    ".." => {
282                        if results.pop().is_none() {
283                            results_final.push(rcstr!("..").into());
284                        }
285                    }
286                    _ => results.push(str.into()),
287                }
288            }
289        } else {
290            results_final.append(&mut results);
291            results_final.push(item);
292        }
293    }
294    results_final.append(&mut results);
295    let mut iter = results_final.into_iter();
296    let first = iter.next().unwrap();
297
298    let is_already_absolute =
299        first.is_empty_string() == Some(true) || first.starts_with("/") == Some(true);
300
301    let mut last_was_str = first.as_str().is_some();
302
303    if !is_already_absolute {
304        results.push(cwd);
305    }
306
307    results.push(first);
308    for part in iter {
309        let is_str = part.as_str().is_some();
310        if last_was_str && is_str {
311            results.push(rcstr!("/").into());
312        } else {
313            results.push(JsValue::alternatives(vec![
314                rcstr!("/").into(),
315                rcstr!("").into(),
316            ]));
317        }
318        results.push(part);
319        last_was_str = is_str;
320    }
321
322    JsValue::concat(results)
323}
324
325fn path_dirname(mut args: Vec<JsValue>) -> JsValue {
326    if let Some(arg) = args.iter_mut().next() {
327        if let Some(str) = arg.as_str() {
328            if let Some(i) = str.rfind('/') {
329                return JsValue::Constant(ConstantValue::Str(str[..i].to_string().into()));
330            } else {
331                return JsValue::Constant(ConstantValue::Str(rcstr!("").into()));
332            }
333        } else if let JsValue::Concat(_, items) = arg
334            && let Some(last) = items.last_mut()
335            && let Some(str) = last.as_str()
336            && let Some(i) = str.rfind('/')
337        {
338            *last = JsValue::Constant(ConstantValue::Str(str[..i].to_string().into()));
339            return take(arg);
340        }
341    }
342    JsValue::unknown(
343        JsValue::call(
344            Box::new(JsValue::WellKnownFunction(
345                WellKnownFunctionKind::PathDirname,
346            )),
347            args,
348        ),
349        true,
350        "path.dirname with unsupported arguments",
351    )
352}
353
354/// Resolve the contents of an import call, throwing errors
355/// if we come across any unsupported syntax.
356pub fn import(args: Vec<JsValue>) -> JsValue {
357    match &args[..] {
358        [JsValue::Constant(ConstantValue::Str(v))] => {
359            JsValue::promise(JsValue::Module(ModuleValue {
360                module: v.as_atom().into_owned().into(),
361                annotations: None,
362            }))
363        }
364        _ => JsValue::unknown(
365            JsValue::call(
366                Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::Import)),
367                args,
368            ),
369            true,
370            "only a single constant argument is supported",
371        ),
372    }
373}
374
375/// Resolve the contents of a require call, throwing errors
376/// if we come across any unsupported syntax.
377fn require(args: Vec<JsValue>) -> JsValue {
378    if args.len() == 1 {
379        if let Some(s) = args[0].as_str() {
380            JsValue::Module(ModuleValue {
381                module: s.into(),
382                annotations: None,
383            })
384        } else {
385            JsValue::unknown(
386                JsValue::call(
387                    Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::Require)),
388                    args,
389                ),
390                true,
391                "only constant argument is supported",
392            )
393        }
394    } else {
395        JsValue::unknown(
396            JsValue::call(
397                Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::Require)),
398                args,
399            ),
400            true,
401            "only a single argument is supported",
402        )
403    }
404}
405
406/// (try to) statically evaluate `require.context(...)()`
407fn require_context_require(val: RequireContextValue, args: Vec<JsValue>) -> Result<JsValue> {
408    if args.is_empty() {
409        return Ok(JsValue::unknown(
410            JsValue::call(
411                Box::new(JsValue::WellKnownFunction(
412                    WellKnownFunctionKind::RequireContextRequire(val),
413                )),
414                args,
415            ),
416            true,
417            "require.context(...).require() requires an argument specifying the module path",
418        ));
419    }
420
421    let Some(s) = args[0].as_str() else {
422        return Ok(JsValue::unknown(
423            JsValue::call(
424                Box::new(JsValue::WellKnownFunction(
425                    WellKnownFunctionKind::RequireContextRequire(val),
426                )),
427                args,
428            ),
429            true,
430            "require.context(...).require() only accepts a single, constant string argument",
431        ));
432    };
433
434    let Some(m) = val.0.get(s) else {
435        return Ok(JsValue::unknown(
436            JsValue::call(
437                Box::new(JsValue::WellKnownFunction(
438                    WellKnownFunctionKind::RequireContextRequire(val),
439                )),
440                args,
441            ),
442            true,
443            "require.context(...).require() can only be called with an argument that's in the \
444             context",
445        ));
446    };
447
448    Ok(JsValue::Module(ModuleValue {
449        module: m.to_string().into(),
450        annotations: None,
451    }))
452}
453
454/// (try to) statically evaluate `require.context(...).keys()`
455fn require_context_require_keys(val: RequireContextValue, args: Vec<JsValue>) -> Result<JsValue> {
456    Ok(if args.is_empty() {
457        JsValue::array(val.0.keys().cloned().map(|k| k.into()).collect())
458    } else {
459        JsValue::unknown(
460            JsValue::call(
461                Box::new(JsValue::WellKnownFunction(
462                    WellKnownFunctionKind::RequireContextRequireKeys(val),
463                )),
464                args,
465            ),
466            true,
467            "require.context(...).keys() does not accept arguments",
468        )
469    })
470}
471
472/// (try to) statically evaluate `require.context(...).resolve()`
473fn require_context_require_resolve(
474    val: RequireContextValue,
475    args: Vec<JsValue>,
476) -> Result<JsValue> {
477    if args.len() != 1 {
478        return Ok(JsValue::unknown(
479            JsValue::call(
480                Box::new(JsValue::WellKnownFunction(
481                    WellKnownFunctionKind::RequireContextRequireResolve(val),
482                )),
483                args,
484            ),
485            true,
486            "require.context(...).resolve() only accepts a single, constant string argument",
487        ));
488    }
489
490    let Some(s) = args[0].as_str() else {
491        return Ok(JsValue::unknown(
492            JsValue::call(
493                Box::new(JsValue::WellKnownFunction(
494                    WellKnownFunctionKind::RequireContextRequireResolve(val),
495                )),
496                args,
497            ),
498            true,
499            "require.context(...).resolve() only accepts a single, constant string argument",
500        ));
501    };
502
503    let Some(m) = val.0.get(s) else {
504        return Ok(JsValue::unknown(
505            JsValue::call(
506                Box::new(JsValue::WellKnownFunction(
507                    WellKnownFunctionKind::RequireContextRequireResolve(val),
508                )),
509                args,
510            ),
511            true,
512            "require.context(...).resolve() can only be called with an argument that's in the \
513             context",
514        ));
515    };
516
517    Ok(m.as_str().into())
518}
519
520fn path_to_file_url(args: Vec<JsValue>) -> JsValue {
521    if args.len() == 1 {
522        if let Some(path) = args[0].as_str() {
523            Url::from_file_path(path)
524                .map(|url| JsValue::Url(String::from(url).into(), JsValueUrlKind::Absolute))
525                .unwrap_or_else(|_| {
526                    JsValue::unknown(
527                        JsValue::call(
528                            Box::new(JsValue::WellKnownFunction(
529                                WellKnownFunctionKind::PathToFileUrl,
530                            )),
531                            args,
532                        ),
533                        true,
534                        "url not parseable: path is relative or has an invalid prefix",
535                    )
536                })
537        } else {
538            JsValue::unknown(
539                JsValue::call(
540                    Box::new(JsValue::WellKnownFunction(
541                        WellKnownFunctionKind::PathToFileUrl,
542                    )),
543                    args,
544                ),
545                true,
546                "only constant argument is supported",
547            )
548        }
549    } else {
550        JsValue::unknown(
551            JsValue::call(
552                Box::new(JsValue::WellKnownFunction(
553                    WellKnownFunctionKind::PathToFileUrl,
554                )),
555                args,
556            ),
557            true,
558            "only a single argument is supported",
559        )
560    }
561}
562
563fn well_known_function_member(kind: WellKnownFunctionKind, prop: JsValue) -> (JsValue, bool) {
564    let new_value = match (kind, prop.as_str()) {
565        (WellKnownFunctionKind::Require, Some("resolve")) => {
566            JsValue::WellKnownFunction(WellKnownFunctionKind::RequireResolve)
567        }
568        (WellKnownFunctionKind::Require, Some("cache")) => {
569            JsValue::WellKnownObject(WellKnownObjectKind::RequireCache)
570        }
571        (WellKnownFunctionKind::Require, Some("context")) => {
572            JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContext)
573        }
574        (WellKnownFunctionKind::RequireContextRequire(val), Some("resolve")) => {
575            JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireResolve(val))
576        }
577        (WellKnownFunctionKind::RequireContextRequire(val), Some("keys")) => {
578            JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequireKeys(val))
579        }
580        (WellKnownFunctionKind::NodeStrongGlobalize, Some("SetRootDir")) => {
581            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir)
582        }
583        (WellKnownFunctionKind::NodeResolveFrom, Some("silent")) => {
584            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom)
585        }
586        (WellKnownFunctionKind::Import, Some("meta")) => {
587            JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta)
588        }
589        #[allow(unreachable_patterns)]
590        (kind, _) => {
591            return (
592                JsValue::member(Box::new(JsValue::WellKnownFunction(kind)), Box::new(prop)),
593                false,
594            );
595        }
596    };
597    (new_value, true)
598}
599
600async fn well_known_object_member(
601    kind: WellKnownObjectKind,
602    prop: JsValue,
603    compile_time_info: Vc<CompileTimeInfo>,
604) -> Result<(JsValue, bool)> {
605    let new_value = match kind {
606        WellKnownObjectKind::GlobalObject => global_object(prop),
607        WellKnownObjectKind::PathModule | WellKnownObjectKind::PathModuleDefault => {
608            path_module_member(kind, prop)
609        }
610        WellKnownObjectKind::FsModule
611        | WellKnownObjectKind::FsModuleDefault
612        | WellKnownObjectKind::FsModulePromises => fs_module_member(kind, prop),
613        WellKnownObjectKind::FsExtraModule | WellKnownObjectKind::FsExtraModuleDefault => {
614            fs_extra_module_member(kind, prop)
615        }
616        WellKnownObjectKind::ModuleModule | WellKnownObjectKind::ModuleModuleDefault => {
617            module_module_member(kind, prop)
618        }
619        WellKnownObjectKind::UrlModule | WellKnownObjectKind::UrlModuleDefault => {
620            url_module_member(kind, prop)
621        }
622        WellKnownObjectKind::WorkerThreadsModule
623        | WellKnownObjectKind::WorkerThreadsModuleDefault => {
624            worker_threads_module_member(kind, prop)
625        }
626        WellKnownObjectKind::ChildProcessModule
627        | WellKnownObjectKind::ChildProcessModuleDefault => child_process_module_member(kind, prop),
628        WellKnownObjectKind::OsModule | WellKnownObjectKind::OsModuleDefault => {
629            os_module_member(kind, prop)
630        }
631        WellKnownObjectKind::NodeProcessModule => {
632            node_process_member(prop, compile_time_info).await?
633        }
634        WellKnownObjectKind::NodePreGyp => node_pre_gyp(prop),
635        WellKnownObjectKind::NodeExpressApp => express(prop),
636        WellKnownObjectKind::NodeProtobufLoader => protobuf_loader(prop),
637        WellKnownObjectKind::ImportMeta => match prop.as_str() {
638            // import.meta.turbopackHot is the ESM equivalent of module.hot for HMR
639            Some("turbopackHot") if compile_time_info.await?.hot_module_replacement_enabled => {
640                JsValue::WellKnownObject(WellKnownObjectKind::ModuleHot)
641            }
642            _ => {
643                return Ok((
644                    JsValue::member(Box::new(JsValue::WellKnownObject(kind)), Box::new(prop)),
645                    false,
646                ));
647            }
648        },
649        WellKnownObjectKind::ModuleHot => match prop.as_str() {
650            Some("accept") => JsValue::WellKnownFunction(WellKnownFunctionKind::ModuleHotAccept),
651            Some("decline") => JsValue::WellKnownFunction(WellKnownFunctionKind::ModuleHotDecline),
652            _ => {
653                return Ok((
654                    JsValue::unknown(
655                        JsValue::member(Box::new(JsValue::WellKnownObject(kind)), Box::new(prop)),
656                        true,
657                        "unsupported property on module.hot",
658                    ),
659                    true,
660                ));
661            }
662        },
663        #[allow(unreachable_patterns)]
664        _ => {
665            return Ok((
666                JsValue::member(Box::new(JsValue::WellKnownObject(kind)), Box::new(prop)),
667                false,
668            ));
669        }
670    };
671    Ok((new_value, true))
672}
673
674fn global_object(prop: JsValue) -> JsValue {
675    match prop.as_str() {
676        Some("assign") => JsValue::WellKnownFunction(WellKnownFunctionKind::ObjectAssign),
677        _ => JsValue::unknown(
678            JsValue::member(
679                Box::new(JsValue::WellKnownObject(WellKnownObjectKind::GlobalObject)),
680                Box::new(prop),
681            ),
682            true,
683            "unsupported property on global Object",
684        ),
685    }
686}
687
688fn path_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
689    match (kind, prop.as_str()) {
690        (.., Some("join")) => JsValue::WellKnownFunction(WellKnownFunctionKind::PathJoin),
691        (.., Some("dirname")) => JsValue::WellKnownFunction(WellKnownFunctionKind::PathDirname),
692        (.., Some("resolve")) => {
693            // cwd is added while resolving in references.rs
694            JsValue::WellKnownFunction(WellKnownFunctionKind::PathResolve(Box::new(JsValue::from(
695                "",
696            ))))
697        }
698        (WellKnownObjectKind::PathModule, Some("default")) => {
699            JsValue::WellKnownObject(WellKnownObjectKind::PathModuleDefault)
700        }
701        _ => JsValue::unknown(
702            JsValue::member(
703                Box::new(JsValue::WellKnownObject(WellKnownObjectKind::PathModule)),
704                Box::new(prop),
705            ),
706            true,
707            "unsupported property on Node.js path module",
708        ),
709    }
710}
711
712fn fs_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
713    if let Some(word) = prop.as_str() {
714        match (kind, word) {
715            (
716                ..,
717                "realpath" | "realpathSync" | "stat" | "statSync" | "existsSync"
718                | "createReadStream" | "exists" | "open" | "openSync" | "readFile" | "readFileSync",
719            ) => {
720                return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadMethod(
721                    word.into(),
722                ));
723            }
724            (WellKnownObjectKind::FsModule | WellKnownObjectKind::FsModuleDefault, "promises") => {
725                return JsValue::WellKnownObject(WellKnownObjectKind::FsModulePromises);
726            }
727            (WellKnownObjectKind::FsModule, "default") => {
728                return JsValue::WellKnownObject(WellKnownObjectKind::FsModuleDefault);
729            }
730            _ => {}
731        }
732    }
733    JsValue::unknown(
734        JsValue::member(
735            Box::new(JsValue::WellKnownObject(WellKnownObjectKind::FsModule)),
736            Box::new(prop),
737        ),
738        true,
739        "unsupported property on Node.js fs module",
740    )
741}
742
743fn fs_extra_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
744    if let Some(word) = prop.as_str() {
745        match (kind, word) {
746            // regular fs methods
747            (
748                ..,
749                "realpath" | "realpathSync" | "stat" | "statSync" | "existsSync"
750                | "createReadStream" | "exists" | "open" | "openSync" | "readFile" | "readFileSync",
751            ) => {
752                return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadMethod(
753                    word.into(),
754                ));
755            }
756            // fs-extra specific
757            (
758                ..,
759                "pathExists" | "pathExistsSync" | "readJson" | "readJSON" | "readJsonSync"
760                | "readJSONSync",
761            ) => {
762                return JsValue::WellKnownFunction(WellKnownFunctionKind::FsReadMethod(
763                    word.into(),
764                ));
765            }
766            (WellKnownObjectKind::FsExtraModule, "default") => {
767                return JsValue::WellKnownObject(WellKnownObjectKind::FsExtraModuleDefault);
768            }
769            _ => {}
770        }
771    }
772    JsValue::unknown(
773        JsValue::member(
774            Box::new(JsValue::WellKnownObject(WellKnownObjectKind::FsExtraModule)),
775            Box::new(prop),
776        ),
777        true,
778        "unsupported property on fs-extra module",
779    )
780}
781
782fn module_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
783    match (kind, prop.as_str()) {
784        (.., Some("createRequire")) => {
785            JsValue::WellKnownFunction(WellKnownFunctionKind::CreateRequire)
786        }
787        (WellKnownObjectKind::ModuleModule, Some("default")) => {
788            JsValue::WellKnownObject(WellKnownObjectKind::ModuleModuleDefault)
789        }
790        _ => JsValue::unknown(
791            JsValue::member(
792                Box::new(JsValue::WellKnownObject(WellKnownObjectKind::ModuleModule)),
793                Box::new(prop),
794            ),
795            true,
796            "unsupported property on Node.js `module` module",
797        ),
798    }
799}
800
801fn url_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
802    match (kind, prop.as_str()) {
803        (.., Some("pathToFileURL")) => {
804            JsValue::WellKnownFunction(WellKnownFunctionKind::PathToFileUrl)
805        }
806        (WellKnownObjectKind::UrlModule, Some("default")) => {
807            JsValue::WellKnownObject(WellKnownObjectKind::UrlModuleDefault)
808        }
809        _ => JsValue::unknown(
810            JsValue::member(
811                Box::new(JsValue::WellKnownObject(WellKnownObjectKind::UrlModule)),
812                Box::new(prop),
813            ),
814            true,
815            "unsupported property on Node.js url module",
816        ),
817    }
818}
819
820fn worker_threads_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
821    match (kind, prop.as_str()) {
822        (.., Some("Worker")) => {
823            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeWorkerConstructor)
824        }
825        (WellKnownObjectKind::WorkerThreadsModule, Some("default")) => {
826            JsValue::WellKnownObject(WellKnownObjectKind::WorkerThreadsModuleDefault)
827        }
828        _ => JsValue::unknown(
829            JsValue::member(
830                Box::new(JsValue::WellKnownObject(
831                    WellKnownObjectKind::WorkerThreadsModule,
832                )),
833                Box::new(prop),
834            ),
835            true,
836            "unsupported property on Node.js worker_threads module",
837        ),
838    }
839}
840
841fn child_process_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
842    let prop_str = prop.as_str();
843    match (kind, prop_str) {
844        (.., Some("spawn" | "spawnSync" | "execFile" | "execFileSync")) => {
845            JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessSpawnMethod(
846                prop_str.unwrap().into(),
847            ))
848        }
849        (.., Some("fork")) => JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessFork),
850        (WellKnownObjectKind::ChildProcessModule, Some("default")) => {
851            JsValue::WellKnownObject(WellKnownObjectKind::ChildProcessModuleDefault)
852        }
853
854        _ => JsValue::unknown(
855            JsValue::member(
856                Box::new(JsValue::WellKnownObject(
857                    WellKnownObjectKind::ChildProcessModule,
858                )),
859                Box::new(prop),
860            ),
861            true,
862            "unsupported property on Node.js child_process module",
863        ),
864    }
865}
866
867fn os_module_member(kind: WellKnownObjectKind, prop: JsValue) -> JsValue {
868    match (kind, prop.as_str()) {
869        (.., Some("platform")) => JsValue::WellKnownFunction(WellKnownFunctionKind::OsPlatform),
870        (.., Some("arch")) => JsValue::WellKnownFunction(WellKnownFunctionKind::OsArch),
871        (.., Some("endianness")) => JsValue::WellKnownFunction(WellKnownFunctionKind::OsEndianness),
872        (WellKnownObjectKind::OsModule, Some("default")) => {
873            JsValue::WellKnownObject(WellKnownObjectKind::OsModuleDefault)
874        }
875        _ => JsValue::unknown(
876            JsValue::member(
877                Box::new(JsValue::WellKnownObject(WellKnownObjectKind::OsModule)),
878                Box::new(prop),
879            ),
880            true,
881            "unsupported property on Node.js os module",
882        ),
883    }
884}
885
886async fn node_process_member(
887    prop: JsValue,
888    compile_time_info: Vc<CompileTimeInfo>,
889) -> Result<JsValue> {
890    Ok(match prop.as_str() {
891        Some("arch") => compile_time_info
892            .environment()
893            .compile_target()
894            .await?
895            .arch
896            .as_str()
897            .into(),
898        Some("platform") => compile_time_info
899            .environment()
900            .compile_target()
901            .await?
902            .platform
903            .as_str()
904            .into(),
905        Some("cwd") => JsValue::WellKnownFunction(WellKnownFunctionKind::ProcessCwd),
906        Some("argv") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessArgv),
907        Some("env") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessEnv),
908        _ => JsValue::unknown(
909            JsValue::member(
910                Box::new(JsValue::WellKnownObject(
911                    WellKnownObjectKind::NodeProcessModule,
912                )),
913                Box::new(prop),
914            ),
915            true,
916            "unsupported property on Node.js process object",
917        ),
918    })
919}
920
921fn node_pre_gyp(prop: JsValue) -> JsValue {
922    match prop.as_str() {
923        Some("find") => JsValue::WellKnownFunction(WellKnownFunctionKind::NodePreGypFind),
924        _ => JsValue::unknown(
925            JsValue::member(
926                Box::new(JsValue::WellKnownObject(WellKnownObjectKind::NodePreGyp)),
927                Box::new(prop),
928            ),
929            true,
930            "unsupported property on @mapbox/node-pre-gyp module",
931        ),
932    }
933}
934
935fn express(prop: JsValue) -> JsValue {
936    match prop.as_str() {
937        Some("set") => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeExpressSet),
938        _ => JsValue::unknown(
939            JsValue::member(
940                Box::new(JsValue::WellKnownObject(
941                    WellKnownObjectKind::NodeExpressApp,
942                )),
943                Box::new(prop),
944            ),
945            true,
946            "unsupported property on require('express')() object",
947        ),
948    }
949}
950
951fn protobuf_loader(prop: JsValue) -> JsValue {
952    match prop.as_str() {
953        Some("load") | Some("loadSync") => {
954            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeProtobufLoad)
955        }
956        _ => JsValue::unknown(
957            JsValue::member(
958                Box::new(JsValue::WellKnownObject(
959                    WellKnownObjectKind::NodeProtobufLoader,
960                )),
961                Box::new(prop),
962            ),
963            true,
964            "unsupported property on require('@grpc/proto-loader') object",
965        ),
966    }
967}