11
import { CompilerError } from "../CompilerError";
12
import { Logger } from "../Entrypoint";
13
import { Err, Ok, Result } from "../Utils/Result";
14
-import { log } from "../Utils/logger";
14
import {
15
DEFAULT_GLOBALS,
16
DEFAULT_SHAPES,
319
*/
320
throwUnknownException__testonly: z.boolean().default(false),
321
322
+ enableSharedRuntime__testonly: z.boolean().default(false),
323
+
324
/**
325
* Enables deps of a function epxression to be treated as conditional. This
326
* makes sure we don't load a dep when it's a property (to check if it has
514
}
515
516
getGlobalDeclaration(binding: NonLocalBinding): Global | null {
516
- const name = binding.name;
517
- let resolvedName = name;
518
-
517
if (this.config.hookPattern != null) {
520
- const match = new RegExp(this.config.hookPattern).exec(name);
518
+ const match = new RegExp(this.config.hookPattern).exec(binding.name);
519
if (
520
match != null &&
521
typeof match[1] === "string" &&
522
isHookName(match[1])
523
) {
526
- resolvedName = match[1];
524
+ const resolvedName = match[1];
525
+ return this.#globals.get(resolvedName) ?? this.#getCustomHookType();
526
}
527
}
528
530
- let resolvedGlobal: Global | null = this.#globals.get(resolvedName) ?? null;
531
- if (resolvedGlobal === null) {
532
- // Hack, since we don't track module level declarations and imports
533
- if (isHookName(resolvedName)) {
534
- return this.#getCustomHookType();
535
- } else {
536
- log(() => `Undefined global \`${name}\``);
529
+ switch (binding.kind) {
530
+ case "ModuleLocal": {
531
+ // don't resolve module locals
532
+ return isHookName(binding.name) ? this.#getCustomHookType() : null;
533
+ }
534
+ case "Global": {
535
+ return (
536
+ this.#globals.get(binding.name) ??
537
+ (isHookName(binding.name) ? this.#getCustomHookType() : null)
538
+ );
539
+ }
540
+ case "ImportSpecifier": {
541
+ if (this.#isKnownReactModule(binding.module)) {
542
+ /**
543
+ * For `import {imported as name} from "..."` form, we use the `imported`
544
+ * name rather than the local alias. Because we don't have definitions for
545
+ * every React builtin hook yet, we also check to see if the imported name
546
+ * is hook-like (whereas the fall-through below is checking if the aliased
547
+ * name is hook-like)
548
+ */
549
+ return (
550
+ this.#globals.get(binding.imported) ??
551
+ (isHookName(binding.imported) ? this.#getCustomHookType() : null)
552
+ );
553
+ } else {
554
+ /**
555
+ * For modules we don't own, we look at whether the original name or import alias
556
+ * are hook-like. Both of the following are likely hooks so we would return a hook
557
+ * type for both:
558
+ *
559
+ * `import {useHook as foo} ...`
560
+ * `import {foo as useHook} ...`
561
+ */
562
+ return isHookName(binding.imported) || isHookName(binding.name)
563
+ ? this.#getCustomHookType()
564
+ : null;
565
+ }
566
+ }
567
+ case "ImportDefault":
568
+ case "ImportNamespace": {
569
+ if (this.#isKnownReactModule(binding.module)) {
570
+ // only resolve imports to modules we know about
571
+ return (
572
+ this.#globals.get(binding.name) ??
573
+ (isHookName(binding.name) ? this.#getCustomHookType() : null)
574
+ );
575
+ } else {
576
+ return isHookName(binding.name) ? this.#getCustomHookType() : null;
577
+ }
578
}
579
}
580
+ }
581
540
- return resolvedGlobal;
582
+ #isKnownReactModule(moduleName: string): boolean {
583
+ return (
584
+ moduleName.toLowerCase() === "react" ||
585
+ moduleName.toLowerCase() === "react-dom" ||
586
+ (this.config.enableSharedRuntime__testonly &&
587
+ moduleName === "shared-runtime")
588
+ );
589
}
590
591
getPropertyType(