From d221c0f5659e1f4902e4e3b8e04e42aa996ce242 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 9 Jul 2025 22:22:08 -0700 Subject: [PATCH 1/2] [compiler] More precise errors for invalid import/export/namespace statements import, export, and TS namespace statements can only be used at the top-level of a module, which is enforced by parsers already. Here we add a backup validation of that. As of this PR, we now have only major statement type (class declarations) listed as a todo. --- .../src/HIR/BuildHIR.ts | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 5bcf27b3332b3..996f92dd9bc41 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -1397,6 +1397,41 @@ function lowerStatement( }); return; } + case 'ExportAllDeclaration': + case 'ExportDefaultDeclaration': + case 'ExportNamedDeclaration': + case 'ImportDeclaration': + case 'TSExportAssignment': + case 'TSImportEqualsDeclaration': { + builder.errors.push({ + reason: + 'JavaScript `import` and `export` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + loc: stmtPath.node.loc ?? null, + suggestions: null, + }); + lowerValueToTemporary(builder, { + kind: 'UnsupportedNode', + loc: stmtPath.node.loc ?? GeneratedSource, + node: stmtPath.node, + }); + return; + } + case 'TSNamespaceExportDeclaration': { + builder.errors.push({ + reason: + 'TypeScript `namespace` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + loc: stmtPath.node.loc ?? null, + suggestions: null, + }); + lowerValueToTemporary(builder, { + kind: 'UnsupportedNode', + loc: stmtPath.node.loc ?? GeneratedSource, + node: stmtPath.node, + }); + return; + } case 'DeclareClass': case 'DeclareExportAllDeclaration': case 'DeclareExportDeclaration': @@ -1411,32 +1446,12 @@ function lowerStatement( case 'OpaqueType': case 'TSDeclareFunction': case 'TSInterfaceDeclaration': + case 'TSModuleDeclaration': case 'TSTypeAliasDeclaration': case 'TypeAlias': { // We do not preserve type annotations/syntax through transformation return; } - case 'ExportAllDeclaration': - case 'ExportDefaultDeclaration': - case 'ExportNamedDeclaration': - case 'ImportDeclaration': - case 'TSExportAssignment': - case 'TSImportEqualsDeclaration': - case 'TSModuleDeclaration': - case 'TSNamespaceExportDeclaration': { - builder.errors.push({ - reason: `(BuildHIR::lowerStatement) Handle ${stmtPath.type} statements`, - severity: ErrorSeverity.Todo, - loc: stmtPath.node.loc ?? null, - suggestions: null, - }); - lowerValueToTemporary(builder, { - kind: 'UnsupportedNode', - loc: stmtPath.node.loc ?? GeneratedSource, - node: stmtPath.node, - }); - return; - } default: { return assertExhaustive( stmtNode, From 21be5d2812e38087ef2ca2f7e684b2ede594da11 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 9 Jul 2025 22:22:08 -0700 Subject: [PATCH 2/2] [compiler] Add CompilerError.UnsupportedJS variant We use this variant for syntax we intentionally don't support: with statements, eval, and inline class declarations. --- .../src/CompilerError.ts | 13 +++++++++++-- .../src/HIR/BuildHIR.ts | 16 +++++++++------- .../error.invalid-eval-unsupported.expect.md | 2 +- .../compiler/error.todo-kitchensink.expect.md | 2 +- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index 7285140de0a62..75e01abaefa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -15,6 +15,11 @@ export enum ErrorSeverity { * misunderstanding on the user’s part. */ InvalidJS = 'InvalidJS', + /** + * JS syntax that is not supported and which we do not plan to support. Developers should + * rewrite to use supported forms. + */ + UnsupportedJS = 'UnsupportedJS', /** * Code that breaks the rules of React. */ @@ -241,12 +246,16 @@ export class CompilerError extends Error { case ErrorSeverity.InvalidJS: case ErrorSeverity.InvalidReact: case ErrorSeverity.InvalidConfig: + case ErrorSeverity.UnsupportedJS: { return true; + } case ErrorSeverity.CannotPreserveMemoization: - case ErrorSeverity.Todo: + case ErrorSeverity.Todo: { return false; - default: + } + default: { assertExhaustive(detail.severity, 'Unhandled error severity'); + } } }); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 996f92dd9bc41..d0335fb3a435d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -1359,7 +1359,7 @@ function lowerStatement( builder.errors.push({ reason: `JavaScript 'with' syntax is not supported`, description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, - severity: ErrorSeverity.InvalidJS, + severity: ErrorSeverity.UnsupportedJS, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1371,13 +1371,15 @@ function lowerStatement( return; } case 'ClassDeclaration': { - /* - * We can in theory support nested classes, similarly to functions where we track values - * captured by the class and consider mutations of the instances to mutate the class itself + /** + * In theory we could support inline class declarations, but this is rare enough in practice + * and complex enough to support that we don't anticipate supporting anytime soon. Developers + * are encouraged to lift classes out of component/hook declarations. */ builder.errors.push({ - reason: `Support nested class declarations`, - severity: ErrorSeverity.Todo, + reason: 'Inline `class` declarations are not supported', + description: `Move class declarations outside of components/hooks`, + severity: ErrorSeverity.UnsupportedJS, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -3560,7 +3562,7 @@ function lowerIdentifier( reason: `The 'eval' function is not supported`, description: 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', - severity: ErrorSeverity.InvalidJS, + severity: ErrorSeverity.UnsupportedJS, loc: exprPath.node.loc ?? null, suggestions: null, }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-eval-unsupported.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-eval-unsupported.expect.md index 409fa2b85fb17..73eddd5dcf44c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-eval-unsupported.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-eval-unsupported.expect.md @@ -15,7 +15,7 @@ function Component(props) { ``` 1 | function Component(props) { > 2 | eval('props.x = true'); - | ^^^^ InvalidJS: The 'eval' function is not supported. Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler (2:2) + | ^^^^ UnsupportedJS: The 'eval' function is not supported. Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler (2:2) 3 | return
; 4 | } 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md index 7c415e467f838..7da1b677bfab8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md @@ -84,7 +84,7 @@ let moduleLocal = false; > 3 | var x = []; | ^^^^^^^^^^^ Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration (3:3) -Todo: Support nested class declarations (5:10) +UnsupportedJS: Inline `class` declarations are not supported. Move class declarations outside of components/hooks (5:10) Todo: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement (20:22)