@samitouri / QOS-React / commits / 4632e36a4e

refactor(eslint-plugin-react-hooks): change array type and improve conditionals (#32400)

- [build(eslint-plugin-react-hooks): add ts-linting](https://github.com/facebook/react/commit/4c0fbe73d9abc2681445f62b9450737f3df12ee2) This change adds configuration to the eslint config governing `eslint-plugin-react-hooks` to use the typescript-eslint plugin and parser. It adds the typescript-recommended config, and configures the team's preferred `array-type` convention. - [refactor(eslint-plugin-react-hooks): improve conditionals](https://github.com/facebook/react/commit/540d0d95bc5172ef95ccc2ad70b4b202b6eeedd2) This change addresses several feedback items from https://github.com/facebook/react/pull/32240 - [ci (eslint-e2e): exclude nested node_modules from cache](https://github.com/facebook/react/pull/32400/commits/a3279f46a85cfb4ddea5a863a6f7c71344280d36) This change removes the nested fixture `node_modules` from being cached, so that the symbolic link can be made after the build happens.

michael faith committed Feb 16, 2025 at 19:28 UTC 4632e36a4ef16a1af24987c56e42b664f6403e64
6 files changed +97 -26
.eslintrc.js
+21 -4
@@ -446,10 +446,7 @@ module.exports = {
446 },
447 },
448 {
449 - files: [
450 - 'scripts/eslint-rules/*.js',
451 - 'packages/eslint-plugin-react-hooks/src/*.js',
452 - ],
449 + files: ['scripts/eslint-rules/*.js'],
450 plugins: ['eslint-plugin'],
451 rules: {
452 'eslint-plugin/prefer-object-rule': ERROR,
@@ -517,6 +514,26 @@ module.exports = {
514 __IS_INTERNAL_VERSION__: 'readonly',
515 },
516 },
517 + {
518 + files: ['packages/eslint-plugin-react-hooks/src/**/*'],
519 + extends: ['plugin:@typescript-eslint/recommended'],
520 + parser: '@typescript-eslint/parser',
521 + plugins: ['@typescript-eslint', 'eslint-plugin'],
522 + rules: {
523 + '@typescript-eslint/no-explicit-any': OFF,
524 + '@typescript-eslint/no-non-null-assertion': OFF,
525 + '@typescript-eslint/array-type': [ERROR, {default: 'generic'}],
526 +
527 + 'es/no-optional-chaining': OFF,
528 +
529 + 'eslint-plugin/prefer-object-rule': ERROR,
530 + 'eslint-plugin/require-meta-fixable': [
531 + ERROR,
532 + {catchNoFixerButFixableProperty: true},
533 + ],
534 + 'eslint-plugin/require-meta-has-suggestions': ERROR,
535 + },
536 + },
537 ],
538
539 env: {
.github/workflows/runtime_eslint_plugin_e2e.yml
+1 -1
@@ -40,7 +40,7 @@ jobs:
40 uses: actions/cache@v4
41 id: node_modules
42 with:
43 - path: "**/node_modules"
43 + path: "node_modules"
44 key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
45 - name: Ensure clean build directory
46 run: rm -rf build
package.json
+2
@@ -42,6 +42,8 @@
42 "@rollup/plugin-node-resolve": "^15.0.1",
43 "@rollup/plugin-replace": "^5.0.2",
44 "@rollup/plugin-typescript": "^12.1.2",
45 + "@typescript-eslint/eslint-plugin": "^6.21.0",
46 + "@typescript-eslint/parser": "^6.21.0",
47 "abortcontroller-polyfill": "^1.7.5",
48 "art": "0.10.1",
49 "babel-plugin-syntax-trailing-function-commas": "^6.5.0",
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts
+17 -15
@@ -28,7 +28,7 @@ type DeclaredDependency = {
28
29 type Dependency = {
30 isStable: boolean;
31 - references: Scope.Reference[];
31 + references: Array<Scope.Reference>;
32 };
33
34 type DependencyTreeNode = {
@@ -184,7 +184,9 @@ const rule = {
184 // Get the current scope.
185 const scope = scopeManager.acquire(node);
186 if (!scope) {
187 - return;
187 + throw new Error(
188 + 'Unable to acquire scope for the current node. This is a bug in eslint-plugin-react-hooks, please file an issue.',
189 + );
190 }
191
192 // Find all our "pure scopes". On every re-render of a component these
@@ -255,7 +257,7 @@ const rule = {
257 // Detect primitive constants
258 // const foo = 42
259 let declaration = defNode.parent;
258 - if (declaration == null && componentScope) {
260 + if (declaration == null && componentScope != null) {
261 // This might happen if variable is declared after the callback.
262 // In that case ESLint won't set up .parent refs.
263 // So we'll set them up manually.
@@ -266,7 +268,7 @@ const rule = {
268 }
269 }
270 if (
269 - declaration &&
271 + declaration != null &&
272 'kind' in declaration &&
273 declaration.kind === 'const' &&
274 init.type === 'Literal' &&
@@ -454,7 +456,7 @@ const rule = {
456 function isInsideEffectCleanup(reference: Scope.Reference): boolean {
457 let curScope: Scope.Scope | null = reference.from;
458 let isInReturnedFunction = false;
457 - while (curScope && curScope.block !== node) {
459 + while (curScope != null && curScope.block !== node) {
460 if (curScope.type === 'function') {
461 isInReturnedFunction =
462 curScope.block.parent != null &&
@@ -529,7 +531,7 @@ const rule = {
531 continue;
532 }
533 // Ignore references to the function itself as it's not defined yet.
532 - if (def.node && def.node.init === node.parent) {
534 + if (def.node != null && def.node.init === node.parent) {
535 continue;
536 }
537 // Ignore Flow type parameters
@@ -566,8 +568,8 @@ const rule = {
568 // Is React managing this ref or us?
569 // Let's see if we can find a .current assignment.
570 let foundCurrentAssignment = false;
569 - for (const reference of references) {
570 - const {identifier} = reference;
571 + for (const ref of references) {
572 + const {identifier} = ref;
573 const {parent} = identifier;
574 if (
575 parent != null &&
@@ -660,7 +662,7 @@ const rule = {
662 }
663
664 let fnScope: Scope.Scope | null = reference.from;
663 - while (fnScope && fnScope.type !== 'function') {
665 + while (fnScope != null && fnScope.type !== 'function') {
666 fnScope = fnScope.upper;
667 }
668 const isDirectlyInsideEffect = fnScope?.block === node;
@@ -704,7 +706,7 @@ const rule = {
706 return;
707 }
708
707 - const declaredDependencies: DeclaredDependency[] = [];
709 + const declaredDependencies: Array<DeclaredDependency> = [];
710 const externalDependencies = new Set<string>();
711 const isArrayExpression =
712 declaredDependenciesNode.type === 'ArrayExpression';
@@ -1469,7 +1471,7 @@ function collectRecommendations({
1471 isEffect,
1472 }: {
1473 dependencies: Map<string, Dependency>;
1472 - declaredDependencies: DeclaredDependency[];
1474 + declaredDependencies: Array<DeclaredDependency>;
1475 stableDependencies: Set<string>;
1476 externalDependencies: Set<string>;
1477 isEffect: boolean;
@@ -1592,7 +1594,7 @@ function collectRecommendations({
1594 }
1595
1596 // Collect suggestions in the order they were originally specified.
1595 - const suggestedDependencies: string[] = [];
1597 + const suggestedDependencies: Array<string> = [];
1598 const unnecessaryDependencies = new Set<string>();
1599 const duplicateDependencies = new Set<string>();
1600 declaredDependencies.forEach(({key}) => {
@@ -1699,7 +1701,7 @@ function scanForConstructions({
1701 componentScope,
1702 scope,
1703 }: {
1702 - declaredDependencies: DeclaredDependency[];
1704 + declaredDependencies: Array<DeclaredDependency>;
1705 declaredDependenciesNode: Node;
1706 componentScope: Scope.Scope;
1707 scope: Scope.Scope;
@@ -1747,7 +1749,7 @@ function scanForConstructions({
1749 }
1750 return null;
1751 })
1750 - .filter(Boolean) as [Scope.Variable, string][];
1752 + .filter(Boolean) as Array<[Scope.Variable, string]>;
1753
1754 function isUsedOutsideOfHook(ref: Scope.Variable): boolean {
1755 let foundWriteExpr = false;
@@ -2007,7 +2009,7 @@ function fastFindReferenceWithParent(start: Node, target: Node): Node | null {
2009 return null;
2010 }
2011
2010 -function joinEnglish(arr: string[]): string {
2012 +function joinEnglish(arr: Array<string>): string {
2013 let s = '';
2014 for (let i = 0; i < arr.length; i++) {
2015 s += arr[i];
packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts
+7 -5
@@ -130,8 +130,10 @@ const rule = {
130 },
131 create(context: Rule.RuleContext) {
132 let lastEffect: CallExpression | null = null;
133 - const codePathReactHooksMapStack: Map<Rule.CodePathSegment, Node[]>[] = [];
134 - const codePathSegmentStack: Rule.CodePathSegment[] = [];
133 + const codePathReactHooksMapStack: Array<
134 + Map<Rule.CodePathSegment, Array<Node>>
135 + > = [];
136 + const codePathSegmentStack: Array<Rule.CodePathSegment> = [];
137 const useEffectEventFunctions = new WeakSet();
138
139 // For a given scope, iterate through the references and add all useEffectEvent definitions. We can
@@ -190,7 +192,7 @@ const rule = {
192 // Maintain code path stack as we traverse.
193 onCodePathStart: () =>
194 codePathReactHooksMapStack.push(
193 - new Map<Rule.CodePathSegment, Node[]>(),
195 + new Map<Rule.CodePathSegment, Array<Node>>(),
196 ),
197
198 // Process our code path.
@@ -561,7 +563,7 @@ const rule = {
563 context.report({node: hook, message});
564 }
565 } else if (
564 - codePathNode.parent &&
566 + codePathNode.parent != null &&
567 (codePathNode.parent.type === 'MethodDefinition' ||
568 // @ts-expect-error `ClassProperty` was removed from typescript-estree in https://github.com/typescript-eslint/typescript-eslint/pull/3806
569 codePathNode.parent.type === 'ClassProperty' ||
@@ -758,7 +760,7 @@ function getFunctionName(node: Node) {
760 /**
761 * Convenience function for peeking the last item in a stack.
762 */
761 -function last<T>(array: T[]): T {
763 +function last<T>(array: Array<T>): T {
764 return array[array.length - 1] as T;
765 }
766
yarn.lock
+49 -1
@@ -2410,6 +2410,11 @@
2410 dependencies:
2411 eslint-visitor-keys "^3.3.0"
2412
2413 +"@eslint-community/regexpp@^4.5.1":
2414 + version "4.12.1"
2415 + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
2416 + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
2417 +
2418 "@eslint-community/regexpp@^4.6.1":
2419 version "4.10.0"
2420 resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63"
@@ -3718,6 +3723,23 @@
3723 dependencies:
3724 "@types/node" "*"
3725
3726 +"@typescript-eslint/eslint-plugin@^6.21.0":
3727 + version "6.21.0"
3728 + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3"
3729 + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==
3730 + dependencies:
3731 + "@eslint-community/regexpp" "^4.5.1"
3732 + "@typescript-eslint/scope-manager" "6.21.0"
3733 + "@typescript-eslint/type-utils" "6.21.0"
3734 + "@typescript-eslint/utils" "6.21.0"
3735 + "@typescript-eslint/visitor-keys" "6.21.0"
3736 + debug "^4.3.4"
3737 + graphemer "^1.4.0"
3738 + ignore "^5.2.4"
3739 + natural-compare "^1.4.0"
3740 + semver "^7.5.4"
3741 + ts-api-utils "^1.0.1"
3742 +
3743 "@typescript-eslint/experimental-utils@2.34.0":
3744 version "2.34.0"
3745 resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f"
@@ -3780,6 +3802,17 @@
3802 "@typescript-eslint/typescript-estree" "5.62.0"
3803 debug "^4.3.4"
3804
3805 +"@typescript-eslint/parser@^6.21.0":
3806 + version "6.21.0"
3807 + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b"
3808 + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==
3809 + dependencies:
3810 + "@typescript-eslint/scope-manager" "6.21.0"
3811 + "@typescript-eslint/types" "6.21.0"
3812 + "@typescript-eslint/typescript-estree" "6.21.0"
3813 + "@typescript-eslint/visitor-keys" "6.21.0"
3814 + debug "^4.3.4"
3815 +
3816 "@typescript-eslint/scope-manager@4.1.0":
3817 version "4.1.0"
3818 resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.1.0.tgz#9e389745ee9cfe12252ed1e9958808abd6b3a683"
@@ -3804,6 +3837,16 @@
3837 "@typescript-eslint/types" "6.21.0"
3838 "@typescript-eslint/visitor-keys" "6.21.0"
3839
3840 +"@typescript-eslint/type-utils@6.21.0":
3841 + version "6.21.0"
3842 + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e"
3843 + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==
3844 + dependencies:
3845 + "@typescript-eslint/typescript-estree" "6.21.0"
3846 + "@typescript-eslint/utils" "6.21.0"
3847 + debug "^4.3.4"
3848 + ts-api-utils "^1.0.1"
3849 +
3850 "@typescript-eslint/types@3.10.1":
3851 version "3.10.1"
3852 resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
@@ -3892,7 +3935,7 @@
3935 semver "^7.5.4"
3936 ts-api-utils "^1.0.1"
3937
3895 -"@typescript-eslint/utils@^6.0.0":
3938 +"@typescript-eslint/utils@6.21.0", "@typescript-eslint/utils@^6.0.0":
3939 version "6.21.0"
3940 resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134"
3941 integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==
@@ -9799,6 +9842,11 @@ ignore@^5.2.0:
9842 resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
9843 integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
9844
9845 +ignore@^5.2.4:
9846 + version "5.3.2"
9847 + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
9848 + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
9849 +
9850 image-size@1.1.1:
9851 version "1.1.1"
9852 resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.1.1.tgz#ddd67d4dc340e52ac29ce5f546a09f4e29e840ac"