From 2d2cc042d7812499baf992804fbf83c20caa7436 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 8 Aug 2024 15:47:40 -0700 Subject: [PATCH] [compiler][ez] Option to bail out on blocklisted imports ghstack-source-id: 540d154b25e49a83683a05fc9326dbd0ad59a6bd Pull Request resolved: https://github.com/facebook/react/pull/30643 --- .../src/Entrypoint/Imports.ts | 26 ++++++++++++++++- .../src/Entrypoint/Program.ts | 7 ++++- .../src/HIR/Environment.ts | 1 + ...ror.validate-blocklisted-imports.expect.md | 28 +++++++++++++++++++ .../error.validate-blocklisted-imports.ts | 8 ++++++ compiler/packages/snap/src/compiler.ts | 15 ++++++++++ 6 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 59409e3055..d62c656944 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -8,9 +8,33 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; import {CompilerError} from '../CompilerError'; -import {ExternalFunction, GeneratedSource} from '../HIR'; +import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR'; import {getOrInsertDefault} from '../Utils/utils'; +export function validateRestrictedImports( + path: NodePath, + {validateBlocklistedImports}: EnvironmentConfig, +): void { + if ( + validateBlocklistedImports == null || + validateBlocklistedImports.length === 0 + ) { + return; + } + const restrictedImports = new Set(validateBlocklistedImports); + path.traverse({ + ImportDeclaration(importDeclPath) { + if (restrictedImports.has(importDeclPath.node.source.value)) { + CompilerError.throwTodo({ + reason: 'Bailing out due to blocklisted import', + description: `Import from module ${importDeclPath.node.source.value}`, + loc: importDeclPath.node.loc ?? null, + }); + } + }, + }); +} + export function addImportsToProgram( path: NodePath, importList: Array, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 7d6a554266..128a38f671 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -24,7 +24,11 @@ import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; import {assertExhaustive} from '../Utils/utils'; import {insertGatedFunctionDeclaration} from './Gating'; -import {addImportsToProgram, updateMemoCacheFunctionImport} from './Imports'; +import { + addImportsToProgram, + updateMemoCacheFunctionImport, + validateRestrictedImports, +} from './Imports'; import {PluginOptions} from './Options'; import {compileFn} from './Pipeline'; import { @@ -296,6 +300,7 @@ export function compileProgram( }); } const environment = environmentResult.unwrap(); + validateRestrictedImports(program, environment); const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c'); const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 6aeae34ad5..73645a3c95 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -238,6 +238,7 @@ const EnvironmentConfigSchema = z.object({ * this option to the empty array. */ validateNoCapitalizedCalls: z.nullable(z.array(z.string())).default(null), + validateBlocklistedImports: z.nullable(z.array(z.string())).default(null), /* * When enabled, the compiler assumes that hooks follow the Rules of React: diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md new file mode 100644 index 0000000000..a4a885d94b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md @@ -0,0 +1,28 @@ + +## Input + +```javascript +// @validateBlocklistedImports(DangerousImport) +import {foo} from 'DangerousImport'; +import {useIdentity} from 'shared-runtime'; + +function useHook() { + useIdentity(foo); + return; +} + +``` + + +## Error + +``` + 1 | // @validateBlocklistedImports(DangerousImport) +> 2 | import {foo} from 'DangerousImport'; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Bailing out due to blocklisted import. Import from module DangerousImport (2:2) + 3 | import {useIdentity} from 'shared-runtime'; + 4 | + 5 | function useHook() { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts new file mode 100644 index 0000000000..fcde4a1922 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts @@ -0,0 +1,8 @@ +// @validateBlocklistedImports(DangerousImport) +import {foo} from 'DangerousImport'; +import {useIdentity} from 'shared-runtime'; + +function useHook() { + useIdentity(foo); + return; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index e2a75bb9b0..a9e705ea3b 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -47,6 +47,7 @@ function makePluginOptions( let validatePreserveExistingMemoizationGuarantees = false; let enableChangeDetectionForDebugging = null; let customMacros = null; + let validateBlocklistedImports = null; if (firstLine.indexOf('@compilationMode(annotation)') !== -1) { assert( @@ -155,6 +156,19 @@ function makePluginOptions( .filter(s => s.length > 0); } + const validateBlocklistedImportsMatch = + /@validateBlocklistedImports\(([^)]+)\)/.exec(firstLine); + if ( + validateBlocklistedImportsMatch && + validateBlocklistedImportsMatch.length > 1 && + validateBlocklistedImportsMatch[1].trim().length > 0 + ) { + validateBlocklistedImports = validateBlocklistedImportsMatch[1] + .split(' ') + .map(s => s.trim()) + .filter(s => s.length > 0); + } + let lowerContextAccess = null; if (firstLine.includes('@lowerContextAccess')) { lowerContextAccess = { @@ -216,6 +230,7 @@ function makePluginOptions( validatePreserveExistingMemoizationGuarantees, enableChangeDetectionForDebugging, lowerContextAccess, + validateBlocklistedImports, }, compilationMode, logger,