diff --git a/.github/workflows/runtime_commit_artifacts.yml b/.github/workflows/runtime_commit_artifacts.yml index 1b98673cd4dd..2827c3258238 100644 --- a/.github/workflows/runtime_commit_artifacts.yml +++ b/.github/workflows/runtime_commit_artifacts.yml @@ -116,11 +116,13 @@ jobs: run: | sed -i -e 's/ @license React*//' \ build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \ + build/facebook-www/eslint-plugin-react-hooks.development.js \ build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js - name: Insert @headers into eslint plugin and react-refresh run: | sed -i -e 's/ LICENSE file in the root directory of this source tree./ LICENSE file in the root directory of this source tree.\n *\n * @noformat\n * @nolint\n * @lightSyntaxTransform\n * @preventMunge\n * @oncall react_core/' \ build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \ + build/facebook-www/eslint-plugin-react-hooks.development.js \ build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js - name: Move relevant files for React in www into compiled run: | @@ -132,9 +134,9 @@ jobs: mkdir ./compiled/facebook-www/__test_utils__ mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js - # Copy eslint-plugin-react-hooks + # Copy eslint-plugin-react-hooks (www build with feature flags) mkdir ./compiled/eslint-plugin-react-hooks - cp build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \ + cp build/facebook-www/eslint-plugin-react-hooks.development.js \ ./compiled/eslint-plugin-react-hooks/index.js # Move unstable_server-external-runtime.js into facebook-www diff --git a/packages/eslint-plugin-react-hooks/src/shared/ReactFeatureFlags.d.ts b/packages/eslint-plugin-react-hooks/src/shared/ReactFeatureFlags.d.ts new file mode 100644 index 000000000000..6d135b41bde2 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/src/shared/ReactFeatureFlags.d.ts @@ -0,0 +1,15 @@ +/** + * Type declarations for shared/ReactFeatureFlags + * + * This allows importing from the Flow-typed ReactFeatureFlags.js file + * without TypeScript errors. + */ +declare module 'shared/ReactFeatureFlags' { + export const eprh_enableUseKeyedStateCompilerLint: boolean; + export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean; + export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only'; +} diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts index 9aaddb07e656..34151fe8c27e 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -21,6 +21,11 @@ import type * as ESTree from 'estree'; import * as HermesParser from 'hermes-parser'; import {isDeepStrictEqual} from 'util'; import type {ParseResult} from '@babel/parser'; +import { + eprh_enableUseKeyedStateCompilerLint, + eprh_enableVerboseNoSetStateInEffectCompilerLint, + eprh_enableExhaustiveEffectDependenciesCompilerLint, +} from 'shared/ReactFeatureFlags'; // Pattern for component names: starts with uppercase letter const COMPONENT_NAME_PATTERN = /^[A-Z]/; @@ -81,10 +86,7 @@ function checkTopLevelNode(node: ESTree.Node): boolean { // Also handles Flow component/hook syntax transformed to FunctionDeclaration with flags if (node.type === 'FunctionDeclaration') { // Check for Hermes-added flags indicating Flow component/hook syntax - if ( - '__componentDeclaration' in node || - '__hookDeclaration' in node - ) { + if ('__componentDeclaration' in node || '__hookDeclaration' in node) { return true; } const id = (node as ESTree.FunctionDeclaration).id; @@ -107,7 +109,10 @@ function checkTopLevelNode(node: ESTree.Node): boolean { init.type === 'FunctionExpression') ) { const name = decl.id.name; - if (COMPONENT_NAME_PATTERN.test(name) || HOOK_NAME_PATTERN.test(name)) { + if ( + COMPONENT_NAME_PATTERN.test(name) || + HOOK_NAME_PATTERN.test(name) + ) { return true; } } @@ -136,10 +141,13 @@ const COMPILER_OPTIONS: PluginOptions = { validateNoCapitalizedCalls: [], validateHooksUsage: true, validateNoDerivedComputationsInEffects: true, - // Temporarily enabled for internal testing - enableUseKeyedState: true, - enableVerboseNoSetStateInEffect: true, - validateExhaustiveEffectDependencies: 'extra-only', + + // Experimental options controlled by ReactFeatureFlags + enableUseKeyedState: eprh_enableUseKeyedStateCompilerLint, + enableVerboseNoSetStateInEffect: + eprh_enableVerboseNoSetStateInEffectCompilerLint, + validateExhaustiveEffectDependencies: + eprh_enableExhaustiveEffectDependenciesCompilerLint, }, }; diff --git a/packages/eslint-plugin-react-hooks/tsconfig.json b/packages/eslint-plugin-react-hooks/tsconfig.json index c5d8847f1ec4..22c36e50eb6e 100644 --- a/packages/eslint-plugin-react-hooks/tsconfig.json +++ b/packages/eslint-plugin-react-hooks/tsconfig.json @@ -9,7 +9,8 @@ "types": ["estree-jsx", "node"], "downlevelIteration": true, "paths": { - "babel-plugin-react-compiler": ["../../compiler/packages/babel-plugin-react-compiler/src"] + "babel-plugin-react-compiler": ["../../compiler/packages/babel-plugin-react-compiler/src"], + "shared/*": ["../shared/*"] }, "jsx": "react-jsxdev", "rootDir": "../..", diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index ee5f22ab9588..3e9a0e92b2e4 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -250,3 +250,14 @@ export const enableAsyncDebugInfo: boolean = true; export const enableUpdaterTracking = __PROFILE__; export const ownerStackLimit = 1e4; + +// ----------------------------------------------------------------------------- +// eslint-plugin-react-hooks +// ----------------------------------------------------------------------------- +export const eprh_enableUseKeyedStateCompilerLint: boolean = false; +export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean = false; +export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only' = 'off'; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index bbb13a6eb174..85dc349beec4 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -84,5 +84,13 @@ export const enableInternalInstanceMap: boolean = false; export const enableOptimisticKey: boolean = false; export const enableParallelTransitions: boolean = false; +export const eprh_enableUseKeyedStateCompilerLint: boolean = false; +export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean = false; +export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only' = 'off'; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 6b0d93447966..78cc2fbfe05d 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -84,5 +84,13 @@ export const enableProfilerNestedUpdatePhase: boolean = __PROFILE__; export const enableUpdaterTracking: boolean = __PROFILE__; export const enableParallelTransitions: boolean = false; +export const eprh_enableUseKeyedStateCompilerLint: boolean = false; +export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean = false; +export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only' = 'off'; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 954d9d88eaf5..7c564788be2e 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -93,5 +93,13 @@ export const enableObjectFiber: boolean = false; export const enableOptimisticKey: boolean = false; export const enableParallelTransitions: boolean = false; +export const eprh_enableUseKeyedStateCompilerLint: boolean = false; +export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean = false; +export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only' = 'off'; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 6bc80d2b8e30..751e575d5036 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -70,5 +70,13 @@ export const ownerStackLimit = 1e4; export const enableOptimisticKey = false; export const enableParallelTransitions = false; +export const eprh_enableUseKeyedStateCompilerLint: boolean = false; +export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean = false; +export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only' = 'off'; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 91dc33b28f35..e3f668c7298b 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -85,5 +85,13 @@ export const enableInternalInstanceMap: boolean = false; export const enableOptimisticKey: boolean = false; export const enableParallelTransitions: boolean = false; +export const eprh_enableUseKeyedStateCompilerLint: boolean = false; +export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean = false; +export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only' = 'off'; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 76e3909ccafc..936999ba37a6 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -112,5 +112,13 @@ export const enableFragmentRefsInstanceHandles: boolean = true; export const enableOptimisticKey: boolean = false; +export const eprh_enableUseKeyedStateCompilerLint: boolean = true; +export const eprh_enableVerboseNoSetStateInEffectCompilerLint: boolean = true; +export const eprh_enableExhaustiveEffectDependenciesCompilerLint: + | 'off' + | 'all' + | 'extra-only' + | 'missing-only' = 'extra-only'; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/scripts/flags/flags.js b/scripts/flags/flags.js index a02b4d84b341..7a5c9730f22a 100644 --- a/scripts/flags/flags.js +++ b/scripts/flags/flags.js @@ -190,7 +190,7 @@ function getNextMajorFlagValue(flag) { return '๐Ÿ“Š'; } else if (value === 'dev') { return '๐Ÿ’ป'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error(`Unexpected OSS Stable value ${value} for flag ${flag}`); @@ -212,7 +212,7 @@ function getOSSCanaryFlagValue(flag) { return '๐Ÿ“Š'; } else if (value === 'dev') { return '๐Ÿ’ป'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error(`Unexpected OSS Canary value ${value} for flag ${flag}`); @@ -229,7 +229,7 @@ function getOSSExperimentalFlagValue(flag) { return '๐Ÿ“Š'; } else if (value === 'dev') { return '๐Ÿ’ป'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error( @@ -250,7 +250,7 @@ function getWWWModernFlagValue(flag) { return '๐Ÿ’ป'; } else if (value === 'gk') { return '๐Ÿงช'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error(`Unexpected WWW Modern value ${value} for flag ${flag}`); @@ -274,7 +274,7 @@ function getWWWClassicFlagValue(flag) { return '๐Ÿ’ป'; } else if (value === 'gk') { return '๐Ÿงช'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error(`Unexpected WWW Classic value ${value} for flag ${flag}`); @@ -295,7 +295,7 @@ function getRNNextMajorFlagValue(flag) { return '๐Ÿ’ป'; } else if (value === 'gk') { return '๐Ÿงช'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error(`Unexpected RN OSS value ${value} for flag ${flag}`); @@ -320,7 +320,7 @@ function getRNOSSFlagValue(flag) { return '๐Ÿ’ป'; } else if (value === 'gk') { return '๐Ÿงช'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error(`Unexpected RN OSS value ${value} for flag ${flag}`); @@ -344,7 +344,7 @@ function getRNFBFlagValue(flag) { return '๐Ÿ’ป'; } else if (value === 'gk') { return '๐Ÿงช'; - } else if (typeof value === 'number') { + } else if (typeof value === 'number' || typeof value === 'string') { return value; } else { throw new Error(`Unexpected RN FB value ${value} for flag ${flag}`); diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 630391822803..66b74b436349 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -380,18 +380,20 @@ function getPlugins( return [ // Keep dynamic imports as externals dynamicImports(), - bundle.tsconfig != null - ? typescript({tsconfig: bundle.tsconfig}) - : { - name: 'rollup-plugin-flow-remove-types', - transform(code) { - const transformed = flowRemoveTypes(code); - return { - code: transformed.toString(), - map: null, - }; - }, - }, + bundle.tsconfig != null ? typescript({tsconfig: bundle.tsconfig}) : false, + { + name: 'rollup-plugin-flow-remove-types', + transform(code, id) { + if (bundle.tsconfig != null && !id.endsWith('.js')) { + return null; + } + const transformed = flowRemoveTypes(code); + return { + code: transformed.toString(), + map: null, + }; + }, + }, // See https://github.com/rollup/plugins/issues/1425 bundle.tsconfig != null ? commonjs({strictRequires: true}) : false, // Shim any modules that need forking in this environment. diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 8a551d2a1549..dbf6160a847e 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -1235,7 +1235,7 @@ const bundles = [ // currently required in order for the package to be copied over correctly. // So, it would be worth improving that flow. name: 'eslint-plugin-react-hooks', - bundleTypes: [NODE_DEV, NODE_PROD, CJS_DTS], + bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD, CJS_DTS], moduleType: ISOMORPHIC, entry: 'eslint-plugin-react-hooks/src/index.ts', global: 'ESLintPluginReactHooks', diff --git a/scripts/rollup/validate/index.js b/scripts/rollup/validate/index.js index 08d356190aa8..429a9bbc6b3e 100644 --- a/scripts/rollup/validate/index.js +++ b/scripts/rollup/validate/index.js @@ -17,6 +17,11 @@ function getFormat(filepath) { // TODO: Should we lint them? return null; } + if (filepath.includes('ESLintPluginReactHooks')) { + // The ESLint plugin bundles compiler code with modern syntax that + // doesn't need to conform to the ES5 www lint rules. + return null; + } return 'fb'; } if (filepath.includes('react-native')) {