mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Add ReactFeatureFlags support to eprh (#35951)
We're currently hardcoding experimental options to `eslint-plugin-react-hooks`. This blocks the release on features that might not be ready. This PR extends the ReactFeatureFlag infra to support flags for `eslint-plugin-react-hooks`. An alternative would be to create a separate flag system for build tools, but for now we have a small number of these and reusing existing infra seems like the simplest approach. I ran a full `yarn build` and checked the output resolved the flag values as expected: _build/oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js_ ```js var eprh_enableUseKeyedStateCompilerLint = false; var eprh_enableVerboseNoSetStateInEffectCompilerLint = false; var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'off'; ``` _build/facebook-www/ESLintPluginReactHooks-dev.classic.js_ ```js var eprh_enableUseKeyedStateCompilerLint = true; var eprh_enableVerboseNoSetStateInEffectCompilerLint = true; var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'extra-only'; ``` --------- Co-authored-by: lauren <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
lauren
parent
5c3f8962b8
commit
e2ed67eeee
@@ -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/ESLintPluginReactHooks-dev.modern.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/ESLintPluginReactHooks-dev.modern.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 ./compiled/facebook-www/ESLintPluginReactHooks-dev.modern.js \
|
||||
./compiled/eslint-plugin-react-hooks/index.js
|
||||
|
||||
# Move unstable_server-external-runtime.js into facebook-www
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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": "../..",
|
||||
|
||||
@@ -252,3 +252,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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -85,5 +85,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);
|
||||
|
||||
@@ -94,5 +94,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);
|
||||
|
||||
@@ -71,5 +71,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);
|
||||
|
||||
@@ -86,5 +86,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);
|
||||
|
||||
@@ -114,5 +114,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);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
+14
-12
@@ -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.
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
Reference in New Issue
Block a user