@@ -43,34 +43,23 @@ export type CompilerPass = {
43
43
comments : Array < t . CommentBlock | t . CommentLine > ;
44
44
code : string | null ;
45
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' ] ) ;
46
48
47
49
function findDirectiveEnablingMemoization (
48
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
+ ) ;
57
55
}
58
56
59
57
function findDirectiveDisablingMemoization (
60
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
+ ) ;
74
63
}
75
64
76
65
function isCriticalError ( err : unknown ) : boolean {
@@ -102,7 +91,7 @@ export type CompileResult = {
102
91
compiledFn : CodegenFunction ;
103
92
} ;
104
93
105
- function handleError (
94
+ function logError (
106
95
err : unknown ,
107
96
pass : CompilerPass ,
108
97
fnLoc : t . SourceLocation | null ,
@@ -131,6 +120,13 @@ function handleError(
131
120
} ) ;
132
121
}
133
122
}
123
+ }
124
+ function handleError (
125
+ err : unknown ,
126
+ pass : CompilerPass ,
127
+ fnLoc : t . SourceLocation | null ,
128
+ ) : void {
129
+ logError ( err , pass , fnLoc ) ;
134
130
if (
135
131
pass . opts . panicThreshold === 'all_errors' ||
136
132
( pass . opts . panicThreshold === 'critical_errors' && isCriticalError ( err ) ) ||
@@ -393,6 +389,17 @@ export function compileProgram(
393
389
fn : BabelFn ,
394
390
fnType : ReactFunctionType ,
395
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
+
396
403
if ( lintError != null ) {
397
404
/**
398
405
* Note that Babel does not attach comment nodes to nodes; they are dangling off of the
@@ -404,7 +411,11 @@ export function compileProgram(
404
411
fn ,
405
412
) ;
406
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
+ }
408
419
}
409
420
}
410
421
@@ -430,11 +441,50 @@ export function compileProgram(
430
441
prunedMemoValues : compiledFn . prunedMemoValues ,
431
442
} ) ;
432
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
+ }
433
454
hasCriticalError ||= isCriticalError ( err ) ;
434
455
handleError ( err , pass , fn . node . loc ?? null ) ;
435
456
return null ;
436
457
}
437
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
+
438
488
if ( ! pass . opts . noEmit && ! hasCriticalError ) {
439
489
return compiledFn ;
440
490
}
@@ -481,6 +531,16 @@ export function compileProgram(
481
531
} ) ;
482
532
}
483
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
+
484
544
if ( pass . opts . gating != null ) {
485
545
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel (
486
546
program ,
@@ -596,24 +656,6 @@ function shouldSkipCompilation(
596
656
}
597
657
}
598
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
- }
617
659
const moduleName = pass . opts . runtimeModule ?? 'react/compiler-runtime' ;
618
660
if ( hasMemoCacheFunctionImport ( program , moduleName ) ) {
619
661
return true ;
@@ -631,28 +673,8 @@ function getReactFunctionType(
631
673
) : ReactFunctionType | null {
632
674
const hookPattern = environment . hookPattern ;
633
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 )
654
677
return getComponentOrHookLike ( fn , hookPattern ) ?? 'Other' ;
655
- }
656
678
}
657
679
658
680
// Component and hook declarations are known components/hooks
0 commit comments