Skip to content

Commit fd4bb7c

Browse files
committed
[compiler] Run compiler pipeline on 'use no forget'
This PR updates the babel plugin to continue the compilation pipeline as normal on components/hooks that have been opted out using a directive. Instead, we no longer emit the compiled function when the directive is present. Previously, we would skip over the entire pipeline. By continuing to enter the pipeline, we'll be able to detect if there are unused directives. ghstack-source-id: f26aec9 Pull Request resolved: #30720
1 parent fa6eab5 commit fd4bb7c

File tree

3 files changed

+33
-33
lines changed

3 files changed

+33
-33
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ export type LoggerEvent =
165165
fnLoc: t.SourceLocation | null;
166166
detail: Omit<Omit<CompilerErrorDetailOptions, 'severity'>, 'suggestions'>;
167167
}
168+
| {
169+
kind: 'CompileSkip';
170+
fnLoc: t.SourceLocation | null;
171+
reason: string;
172+
loc: t.SourceLocation | null;
173+
}
168174
| {
169175
kind: 'CompileSuccess';
170176
fnLoc: t.SourceLocation | null;

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ export type CompilerPass = {
4343
comments: Array<t.CommentBlock | t.CommentLine>;
4444
code: string | null;
4545
};
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']);
4648

4749
function findDirectiveEnablingMemoization(
4850
directives: Array<t.Directive>,
4951
): t.Directive | null {
5052
for (const directive of directives) {
5153
const directiveValue = directive.value.value;
52-
if (directiveValue === 'use forget' || directiveValue === 'use memo') {
54+
if (OPT_IN_DIRECTIVES.has(directiveValue)) {
5355
return directive;
5456
}
5557
}
@@ -62,11 +64,7 @@ function findDirectiveDisablingMemoization(
6264
): t.Directive | null {
6365
for (const directive of directives) {
6466
const directiveValue = directive.value.value;
65-
if (
66-
(directiveValue === 'use no forget' ||
67-
directiveValue === 'use no memo') &&
68-
!options.ignoreUseNoForget
69-
) {
67+
if (OPT_OUT_DIRECTIVES.has(directiveValue) && !options.ignoreUseNoForget) {
7068
return directive;
7169
}
7270
}
@@ -338,6 +336,11 @@ export function compileProgram(
338336
}> = [];
339337
const compiledFns: Array<CompileResult> = [];
340338

339+
const useNoForget = findDirectiveDisablingMemoization(
340+
program.node.directives,
341+
pass.opts,
342+
);
343+
341344
const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
342345
const fnType = getReactFunctionType(fn, pass, environment);
343346
if (fnType === null || ALREADY_COMPILED.has(fn.node)) {
@@ -434,8 +437,20 @@ export function compileProgram(
434437
handleError(err, pass, fn.node.loc ?? null);
435438
return null;
436439
}
437-
438-
if (!pass.opts.noEmit && !hasCriticalError) {
440+
/**
441+
* If 'use no forget/memo' is present, we still run the code through the compiler for validation
442+
* but we don't mutate the babel AST. This allows us to flag if there is an unused
443+
* 'use no forget/memo' directive.
444+
*/
445+
if (useNoForget != null) {
446+
pass.opts.logger?.logEvent(pass.filename, {
447+
kind: 'CompileSkip',
448+
fnLoc: fn.node.body.loc ?? null,
449+
reason: `Skipped due to '${useNoForget.value}' directive.`,
450+
loc: useNoForget.loc ?? null,
451+
});
452+
return null;
453+
} else if (!pass.opts.noEmit && !hasCriticalError) {
439454
return compiledFn;
440455
}
441456
return null;
@@ -596,24 +611,6 @@ function shouldSkipCompilation(
596611
}
597612
}
598613

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-
}
617614
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
618615
if (hasMemoCacheFunctionImport(program, moduleName)) {
619616
return true;
@@ -638,14 +635,10 @@ function getReactFunctionType(
638635
);
639636
if (useNoForget != null) {
640637
pass.opts.logger?.logEvent(pass.filename, {
641-
kind: 'CompileError',
638+
kind: 'CompileSkip',
642639
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-
},
640+
reason: `Skipped due to '${useNoForget.value}' directive.`,
641+
loc: useNoForget.loc ?? null,
649642
});
650643
return null;
651644
}

compiler/packages/babel-plugin-react-compiler/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export {
1818
compileProgram,
1919
parsePluginOptions,
2020
run,
21+
OPT_OUT_DIRECTIVES,
2122
type CompilerPipelineValue,
2223
type PluginOptions,
2324
} from './Entrypoint';

0 commit comments

Comments
 (0)