43
comments: Array<t.CommentBlock | t.CommentLine>;
44
code: string | null;
45
};
46
+const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']);
47
+export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']);
48
49
function findDirectiveEnablingMemoization(
50
directives: Array<t.Directive>,
49
-): t.Directive | null {
50
- for (const directive of directives) {
51
- const directiveValue = directive.value.value;
52
- if (directiveValue === 'use forget' || directiveValue === 'use memo') {
53
- return directive;
54
- }
55
- }
56
- return null;
51
+): Array<t.Directive> {
52
+ return directives.filter(directive =>
53
+ OPT_IN_DIRECTIVES.has(directive.value.value),
54
+ );
55
}
56
57
function findDirectiveDisablingMemoization(
58
directives: Array<t.Directive>,
61
- options: PluginOptions,
62
-): t.Directive | null {
63
- for (const directive of directives) {
64
- const directiveValue = directive.value.value;
65
- if (
66
- (directiveValue === 'use no forget' ||
67
- directiveValue === 'use no memo') &&
68
- !options.ignoreUseNoForget
69
- ) {
70
- return directive;
71
- }
72
- }
73
- return null;
59
+): Array<t.Directive> {
60
+ return directives.filter(directive =>
61
+ OPT_OUT_DIRECTIVES.has(directive.value.value),
62
+ );
63
}
64
65
function isCriticalError(err: unknown): boolean {
91
compiledFn: CodegenFunction;
92
};
93
105
-function handleError(
94
+function logError(
95
err: unknown,
96
pass: CompilerPass,
97
fnLoc: t.SourceLocation | null,
120
});
121
}
122
}
123
+}
124
+function handleError(
125
+ err: unknown,
126
+ pass: CompilerPass,
127
+ fnLoc: t.SourceLocation | null,
128
+): void {
129
+ logError(err, pass, fnLoc);
130
if (
131
pass.opts.panicThreshold === 'all_errors' ||
132
(pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) ||
389
fn: BabelFn,
390
fnType: ReactFunctionType,
391
): null | CodegenFunction => {
392
+ let optInDirectives: Array<t.Directive> = [];
393
+ let optOutDirectives: Array<t.Directive> = [];
394
+ if (fn.node.body.type === 'BlockStatement') {
395
+ optInDirectives = findDirectiveEnablingMemoization(
396
+ fn.node.body.directives,
397
+ );
398
+ optOutDirectives = findDirectiveDisablingMemoization(
399
+ fn.node.body.directives,
400
+ );
401
+ }
402
+
403
if (lintError != null) {
404
/**
405
* Note that Babel does not attach comment nodes to nodes; they are dangling off of the
411
fn,
412
);
413
if (suppressionsInFunction.length > 0) {
407
- handleError(lintError, pass, fn.node.loc ?? null);
414
+ if (optOutDirectives.length > 0) {
415
+ logError(lintError, pass, fn.node.loc ?? null);
416
+ } else {
417
+ handleError(lintError, pass, fn.node.loc ?? null);
418
+ }
419
}
420
}
421
441
prunedMemoValues: compiledFn.prunedMemoValues,
442
});
443
} catch (err) {
444
+ /**
445
+ * If an opt out directive is present, log only instead of throwing and don't mark as
446
+ * containing a critical error.
447
+ */
448
+ if (fn.node.body.type === 'BlockStatement') {
449
+ if (optOutDirectives.length > 0) {
450
+ logError(err, pass, fn.node.loc ?? null);
451
+ return null;
452
+ }
453
+ }
454
hasCriticalError ||= isCriticalError(err);
455
handleError(err, pass, fn.node.loc ?? null);
456
return null;
457
}
458
459
+ /**
460
+ * Always compile functions with opt in directives.
461
+ */
462
+ if (optInDirectives.length > 0) {
463
+ return compiledFn;
464
+ } else if (pass.opts.compilationMode === 'annotation') {
465
+ /**
466
+ * No opt-in directive in annotation mode, so don't insert the compiled function.
467
+ */
468
+ return null;
469
+ }
470
+
471
+ /**
472
+ * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler
473
+ * for validation but we don't mutate the babel AST. This allows us to flag if there is an
474
+ * unused 'use no forget/memo' directive.
475
+ */
476
+ if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) {
477
+ for (const directive of optOutDirectives) {
478
+ pass.opts.logger?.logEvent(pass.filename, {
479
+ kind: 'CompileSkip',
480
+ fnLoc: fn.node.body.loc ?? null,
481
+ reason: `Skipped due to '${directive.value.value}' directive.`,
482
+ loc: directive.loc ?? null,
483
+ });
484
+ }
485
+ return null;
486
+ }
487
+
488
if (!pass.opts.noEmit && !hasCriticalError) {
489
return compiledFn;
490
}
531
});
532
}
533
534
+ /**
535
+ * Do not modify source if there is a module scope level opt out directive.
536
+ */
537
+ const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization(
538
+ program.node.directives,
539
+ );
540
+ if (moduleScopeOptOutDirectives.length > 0) {
541
+ return;
542
+ }
543
+
544
if (pass.opts.gating != null) {
545
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
546
program,
656
}
657
}
658
599
- // Top level "use no forget", skip this file entirely
600
- const useNoForget = findDirectiveDisablingMemoization(
601
- program.node.directives,
602
- pass.opts,
603
- );
604
- if (useNoForget != null) {
605
- pass.opts.logger?.logEvent(pass.filename, {
606
- kind: 'CompileError',
607
- fnLoc: null,
608
- detail: {
609
- severity: ErrorSeverity.Todo,
610
- reason: 'Skipped due to "use no forget" directive.',
611
- loc: useNoForget.loc ?? null,
612
- suggestions: null,
613
- },
614
- });
615
- return true;
616
- }
659
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
660
if (hasMemoCacheFunctionImport(program, moduleName)) {
661
return true;
673
): ReactFunctionType | null {
674
const hookPattern = environment.hookPattern;
675
if (fn.node.body.type === 'BlockStatement') {
634
- // Opt-outs disable compilation regardless of mode
635
- const useNoForget = findDirectiveDisablingMemoization(
636
- fn.node.body.directives,
637
- pass.opts,
638
- );
639
- if (useNoForget != null) {
640
- pass.opts.logger?.logEvent(pass.filename, {
641
- kind: 'CompileError',
642
- fnLoc: fn.node.body.loc ?? null,
643
- detail: {
644
- severity: ErrorSeverity.Todo,
645
- reason: 'Skipped due to "use no forget" directive.',
646
- loc: useNoForget.loc ?? null,
647
- suggestions: null,
648
- },
649
- });
650
- return null;
651
- }
652
- // Otherwise opt-ins enable compilation regardless of mode
653
- if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) {
676
+ if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0)
677
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
655
- }
678
}
679
680
// Component and hook declarations are known components/hooks