From 2bd1c756c6fffefb00cdb2986218fa2701ece82e Mon Sep 17 00:00:00 2001 From: David Sancho Date: Wed, 18 Dec 2024 14:08:56 +0100 Subject: [PATCH] Ensure function arity is preserved after build (#31808) Co-authored-by: eps1lon --- packages/react-dom/src/client/ReactDOMRoot.js | 12 +++-- .../react-reconciler/src/ReactFiberHooks.js | 8 +++- .../react/src/__tests__/React-hooks-arity.js | 44 +++++++++++++++++++ scripts/rollup/validate/eslintrc.cjs.js | 11 +++++ scripts/rollup/validate/eslintrc.cjs2015.js | 11 +++++ scripts/rollup/validate/eslintrc.esm.js | 11 +++++ scripts/rollup/validate/eslintrc.fb.js | 11 +++++ scripts/rollup/validate/eslintrc.rn.js | 11 +++++ 8 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 packages/react/src/__tests__/React-hooks-arity.js diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 1c673bf254..5cef2d2843 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -106,17 +106,19 @@ ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = } if (__DEV__) { - if (typeof arguments[1] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[1] === 'function') { console.error( 'does not support the second callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().', ); - } else if (isValidContainer(arguments[1])) { + } else if (isValidContainer(args[1])) { console.error( 'You passed a container to the second argument of root.render(...). ' + "You don't need to pass it again since you already passed it to create the root.", ); - } else if (typeof arguments[1] !== 'undefined') { + } else if (typeof args[1] !== 'undefined') { console.error( 'You passed a second argument to root.render(...) but it only accepts ' + 'one argument.', @@ -131,7 +133,9 @@ ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount = // $FlowFixMe[missing-this-annot] function (): void { if (__DEV__) { - if (typeof arguments[0] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[0] === 'function') { console.error( 'does not support a callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().', diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 12b110b16d..99dbea0446 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -3626,7 +3626,9 @@ function dispatchReducerAction( action: A, ): void { if (__DEV__) { - if (typeof arguments[3] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[3] === 'function') { console.error( "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + @@ -3666,7 +3668,9 @@ function dispatchSetState( action: A, ): void { if (__DEV__) { - if (typeof arguments[3] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[3] === 'function') { console.error( "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + diff --git a/packages/react/src/__tests__/React-hooks-arity.js b/packages/react/src/__tests__/React-hooks-arity.js new file mode 100644 index 0000000000..0ba63428d3 --- /dev/null +++ b/packages/react/src/__tests__/React-hooks-arity.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactNoop; + +describe('arity', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + }); + + it("ensure useState setter's arity is correct", () => { + function Component() { + const [, setState] = React.useState(() => 'Halo!'); + + expect(setState.length).toBe(1); + return null; + } + + ReactNoop.render(); + }); + + it("ensure useReducer setter's arity is correct", () => { + function Component() { + const [, dispatch] = React.useReducer(() => 'Halo!'); + + expect(dispatch.length).toBe(1); + return null; + } + + ReactNoop.render(); + }); +}); diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index c91c298edf..b974ecee0d 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -86,6 +86,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 01f387e1d7..6efc8838f0 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -81,6 +81,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index 9f9938f204..20b5341a82 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -83,6 +83,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 9f344c5aac..9606d00b35 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -71,6 +71,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index d18516f802..941b1d1872 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -73,6 +73,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment