diff --git a/.eslintrc.js b/.eslintrc.js index 23d5ab76c32bc..d464e83b453aa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -127,6 +127,41 @@ module.exports = { }, overrides: [ + { + // By default, anything error message that appears the packages directory + // must have a corresponding error code. The exceptions are defined + // in the next override entry. + files: ['packages/**/*.js'], + rules: { + 'react-internal/prod-error-codes': ERROR, + }, + }, + { + // These are files where it's OK to have unminified error messages. These + // are environments where bundle size isn't a concern, like tests + // or Node. + files: [ + 'packages/react-dom/src/test-utils/**/*.js', + 'packages/react-devtools-shared/**/*.js', + 'packages/react-noop-renderer/**/*.js', + 'packages/react-pg/**/*.js', + 'packages/react-fs/**/*.js', + 'packages/react-refresh/**/*.js', + 'packages/react-server-dom-webpack/**/*.js', + 'packages/react-test-renderer/**/*.js', + 'packages/react-debug-tools/**/*.js', + 'packages/react-devtools-extensions/**/*.js', + 'packages/react-devtools-scheduling-profiler/**/*.js', + 'packages/react-native-renderer/**/*.js', + 'packages/eslint-plugin-react-hooks/**/*.js', + 'packages/jest-react/**/*.js', + 'packages/**/__tests__/*.js', + 'packages/**/npm/*.js', + ], + rules: { + 'react-internal/prod-error-codes': OFF, + }, + }, { // We apply these settings to files that we ship through npm. // They must be ES5. diff --git a/packages/create-subscription/src/createSubscription.js b/packages/create-subscription/src/createSubscription.js index 30d7b669f65fe..329a238fc238a 100644 --- a/packages/create-subscription/src/createSubscription.js +++ b/packages/create-subscription/src/createSubscription.js @@ -8,7 +8,6 @@ */ import * as React from 'react'; -import invariant from 'shared/invariant'; type Unsubscribe = () => void; @@ -128,10 +127,12 @@ export function createSubscription( // Store the unsubscribe method for later (in case the subscribable prop changes). const unsubscribe = subscribe(source, callback); - invariant( - typeof unsubscribe === 'function', - 'A subscription must return an unsubscribe function.', - ); + + if (typeof unsubscribe !== 'function') { + throw new Error( + 'A subscription must return an unsubscribe function.', + ); + } // It's safe to store unsubscribe on the instance because // We only read or write that property during the "commit" phase. diff --git a/packages/jest-react/src/JestReact.js b/packages/jest-react/src/JestReact.js index b4688f1ff980a..92f7e7cdb192d 100644 --- a/packages/jest-react/src/JestReact.js +++ b/packages/jest-react/src/JestReact.js @@ -7,7 +7,6 @@ import {REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols'; -import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; export {act} from './internalAct'; @@ -31,11 +30,12 @@ function captureAssertion(fn) { function assertYieldsWereCleared(root) { const Scheduler = root._Scheduler; const actualYields = Scheduler.unstable_clearYields(); - invariant( - actualYields.length === 0, - 'Log of yielded values is not empty. ' + - 'Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.', - ); + if (actualYields.length !== 0) { + throw new Error( + 'Log of yielded values is not empty. ' + + 'Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.', + ); + } } export function unstable_toMatchRenderedOutput(root, expectedJSX) { diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js index f9971b5bb0360..ec702bf9a70d4 100644 --- a/packages/react-art/src/ReactARTHostConfig.js +++ b/packages/react-art/src/ReactARTHostConfig.js @@ -7,7 +7,6 @@ import Transform from 'art/core/transform'; import Mode from 'art/modes/current'; -import invariant from 'shared/invariant'; import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals'; @@ -248,8 +247,7 @@ export * from 'react-reconciler/src/ReactFiberHostConfigWithNoMicrotasks'; export function appendInitialChild(parentInstance, child) { if (typeof child === 'string') { // Noop for string children of Text (eg {'foo'}{'bar'}) - invariant(false, 'Text children should already be flattened.'); - return; + throw new Error('Text children should already be flattened.'); } child.inject(parentInstance); @@ -282,7 +280,9 @@ export function createInstance(type, props, internalInstanceHandle) { break; } - invariant(instance, 'ReactART does not support the type "%s"', type); + if (!instance) { + throw new Error(`ReactART does not support the type "${type}"`); + } instance._applyProps(instance, props); @@ -367,18 +367,18 @@ export function appendChildToContainer(parentInstance, child) { } export function insertBefore(parentInstance, child, beforeChild) { - invariant( - child !== beforeChild, - 'ReactART: Can not insert node before itself', - ); + if (child === beforeChild) { + throw new Error('ReactART: Can not insert node before itself'); + } + child.injectBefore(beforeChild); } export function insertInContainerBefore(parentInstance, child, beforeChild) { - invariant( - child !== beforeChild, - 'ReactART: Can not insert node before itself', - ); + if (child === beforeChild) { + throw new Error('ReactART: Can not insert node before itself'); + } + child.injectBefore(beforeChild); } @@ -433,25 +433,25 @@ export function clearContainer(container) { } export function getInstanceFromNode(node) { - throw new Error('Not yet implemented.'); + throw new Error('Not implemented.'); } export function isOpaqueHydratingObject(value: mixed): boolean { - throw new Error('Not yet implemented'); + throw new Error('Not implemented.'); } export function makeOpaqueHydratingObject( attemptToReadValue: () => void, ): OpaqueIDType { - throw new Error('Not yet implemented.'); + throw new Error('Not implemented.'); } export function makeClientId(): OpaqueIDType { - throw new Error('Not yet implemented'); + throw new Error('Not implemented.'); } export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType { - throw new Error('Not yet implemented'); + throw new Error('Not implemented.'); } export function beforeActiveInstanceBlur(internalInstanceHandle: Object) { diff --git a/packages/react-cache/src/ReactCacheOld.js b/packages/react-cache/src/ReactCacheOld.js index 77768dfcf2163..f8b33434b7c76 100644 --- a/packages/react-cache/src/ReactCacheOld.js +++ b/packages/react-cache/src/ReactCacheOld.js @@ -49,6 +49,8 @@ const ReactCurrentDispatcher = function readContext(Context) { const dispatcher = ReactCurrentDispatcher.current; if (dispatcher === null) { + // This wasn't being minified but we're going to retire this package anyway. + // eslint-disable-next-line react-internal/prod-error-codes throw new Error( 'react-cache: read and preload may only be called from within a ' + "component's render. They are not supported in event handlers or " + diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 983e3edc93802..9d5ac3680a8a0 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -397,6 +397,7 @@ export function resolveError( message: string, stack: string, ): void { + // eslint-disable-next-line react-internal/prod-error-codes const error = new Error(message); error.stack = stack; const chunks = response._chunks; diff --git a/packages/react-client/src/ReactFlightClientHostConfig.js b/packages/react-client/src/ReactFlightClientHostConfig.js index 49c9752540528..2829adc569256 100644 --- a/packages/react-client/src/ReactFlightClientHostConfig.js +++ b/packages/react-client/src/ReactFlightClientHostConfig.js @@ -7,9 +7,7 @@ * @flow */ -/* eslint-disable react-internal/invariant-args */ - -import invariant from 'shared/invariant'; +/* eslint-disable react-internal/prod-error-codes */ // We expect that our Rollup, Jest, and Flow configurations // always shim this module with the corresponding host config @@ -19,4 +17,4 @@ import invariant from 'shared/invariant'; // sure that if we *do* accidentally break the configuration, // the failure isn't silent. -invariant(false, 'This module must be shimmed by a specific renderer.'); +throw new Error('This module must be shimmed by a specific renderer.'); diff --git a/packages/react-client/src/ReactFlightClientHostConfigNoStream.js b/packages/react-client/src/ReactFlightClientHostConfigNoStream.js index 17f29a9f26c50..8c453832bd3b2 100644 --- a/packages/react-client/src/ReactFlightClientHostConfigNoStream.js +++ b/packages/react-client/src/ReactFlightClientHostConfigNoStream.js @@ -12,6 +12,7 @@ export type StringDecoder = void; export const supportsBinaryStreams = false; export function createStringDecoder(): void { + // eslint-disable-next-line react-internal/prod-error-codes throw new Error('Should never be called'); } @@ -19,6 +20,7 @@ export function readPartialStringChunk( decoder: StringDecoder, buffer: Uint8Array, ): string { + // eslint-disable-next-line react-internal/prod-error-codes throw new Error('Should never be called'); } @@ -26,5 +28,6 @@ export function readFinalStringChunk( decoder: StringDecoder, buffer: Uint8Array, ): string { + // eslint-disable-next-line react-internal/prod-error-codes throw new Error('Should never be called'); } diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index aa4d2d60fdf63..957838ed58a9c 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -23,7 +23,6 @@ import type {OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig'; import {NoMode} from 'react-reconciler/src/ReactTypeOfMode'; import ErrorStackParser from 'error-stack-parser'; -import invariant from 'shared/invariant'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols'; import { @@ -107,7 +106,7 @@ function nextHook(): null | Hook { } function getCacheForType(resourceType: () => T): T { - invariant(false, 'Not implemented.'); + throw new Error('Not implemented.'); } function readContext(context: ReactContext): T { diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index a1bb39c929d43..8e37995cb7364 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -39,7 +39,6 @@ import { import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal'; import {canUseDOM} from 'shared/ExecutionEnvironment'; import ReactVersion from 'shared/ReactVersion'; -import invariant from 'shared/invariant'; import { warnUnstableRenderSubtreeIntoContainer, enableNewReconciler, @@ -108,10 +107,10 @@ function createPortal( container: Container, key: ?string = null, ): React$Portal { - invariant( - isValidContainer(container), - 'Target container is not a DOM element.', - ); + if (!isValidContainer(container)) { + throw new Error('Target container is not a DOM element.'); + } + // TODO: pass ReactDOM portal implementation as third argument // $FlowFixMe The Flow type is opaque but there's no way to actually create it. return createPortalImpl(children, container, null, key); diff --git a/packages/react-dom/src/client/ReactDOMComponentTree.js b/packages/react-dom/src/client/ReactDOMComponentTree.js index 2afe66035f364..58e5d72acd581 100644 --- a/packages/react-dom/src/client/ReactDOMComponentTree.js +++ b/packages/react-dom/src/client/ReactDOMComponentTree.js @@ -30,7 +30,6 @@ import { import {getParentSuspenseInstance} from './ReactDOMHostConfig'; -import invariant from 'shared/invariant'; import {enableScopeAPI} from 'shared/ReactFeatureFlags'; const randomKey = Math.random() @@ -190,7 +189,7 @@ export function getNodeFromInstance(inst: Fiber): Instance | TextInstance { // Without this first invariant, passing a non-DOM-component triggers the next // invariant for a missing parent, which is super confusing. - invariant(false, 'getNodeFromInstance: Invalid argument.'); + throw new Error('getNodeFromInstance: Invalid argument.'); } export function getFiberCurrentPropsFromNode( diff --git a/packages/react-dom/src/client/ReactDOMEventHandle.js b/packages/react-dom/src/client/ReactDOMEventHandle.js index 80834c32834d2..0fd0cd1599780 100644 --- a/packages/react-dom/src/client/ReactDOMEventHandle.js +++ b/packages/react-dom/src/client/ReactDOMEventHandle.js @@ -28,7 +28,6 @@ import { enableScopeAPI, enableCreateEventHandleAPI, } from 'shared/ReactFeatureFlags'; -import invariant from 'shared/invariant'; type EventHandleOptions = {| capture?: boolean, @@ -73,8 +72,7 @@ function registerReactDOMEvent( eventTarget, ); } else { - invariant( - false, + throw new Error( 'ReactDOM.createEventHandle: setter called on an invalid ' + 'target. Provide a valid EventTarget or an element managed by React.', ); @@ -97,11 +95,11 @@ export function createEventHandle( // Unfortunately, the downside of this invariant is that *removing* a native // event from the list of known events has now become a breaking change for // any code relying on the createEventHandle API. - invariant( - allNativeEvents.has(domEventName), - 'Cannot call unstable_createEventHandle with "%s", as it is not an event known to React.', - domEventName, - ); + if (!allNativeEvents.has(domEventName)) { + throw new Error( + `Cannot call unstable_createEventHandle with "${domEventName}", as it is not an event known to React.`, + ); + } let isCapturePhaseListener = false; if (options != null) { @@ -115,11 +113,13 @@ export function createEventHandle( target: EventTarget | ReactScopeInstance, callback: (SyntheticEvent) => void, ) => { - invariant( - typeof callback === 'function', - 'ReactDOM.createEventHandle: setter called with an invalid ' + - 'callback. The callback must be a function.', - ); + if (typeof callback !== 'function') { + throw new Error( + 'ReactDOM.createEventHandle: setter called with an invalid ' + + 'callback. The callback must be a function.', + ); + } + if (!doesTargetHaveEventHandle(target, eventHandle)) { addEventHandleToTarget(target, eventHandle); registerReactDOMEvent(target, domEventName, isCapturePhaseListener); diff --git a/packages/react-dom/src/client/ReactDOMInput.js b/packages/react-dom/src/client/ReactDOMInput.js index 4fabc1fb9ada9..8f5098405b576 100644 --- a/packages/react-dom/src/client/ReactDOMInput.js +++ b/packages/react-dom/src/client/ReactDOMInput.js @@ -9,7 +9,6 @@ // TODO: direct imports like some-package/src/* are bad. Fix me. import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber'; -import invariant from 'shared/invariant'; import {setValueForProperty} from './DOMPropertyOperations'; import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree'; @@ -383,11 +382,13 @@ function updateNamedCousins(rootNode, props) { // That's probably okay; we don't support it just as we don't support // mixing React radio buttons with non-React ones. const otherProps = getFiberCurrentPropsFromNode(otherNode); - invariant( - otherProps, - 'ReactDOMInput: Mixing React and non-React radio inputs with the ' + - 'same `name` is not supported.', - ); + + if (!otherProps) { + throw new Error( + 'ReactDOMInput: Mixing React and non-React radio inputs with the ' + + 'same `name` is not supported.', + ); + } // We need update the tracked value on the named cousin since the value // was changed but the input saw no event or value set diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js index 70dcddc502763..ac12f18bee509 100644 --- a/packages/react-dom/src/client/ReactDOMLegacy.js +++ b/packages/react-dom/src/client/ReactDOMLegacy.js @@ -36,7 +36,6 @@ import { } from 'react-reconciler/src/ReactFiberReconciler'; import {LegacyRoot} from 'react-reconciler/src/ReactRootTags'; import getComponentNameFromType from 'shared/getComponentNameFromType'; -import invariant from 'shared/invariant'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import {has as hasInstance} from 'shared/ReactInstanceMap'; @@ -238,10 +237,10 @@ export function hydrate( ); } - invariant( - isValidContainerLegacy(container), - 'Target container is not a DOM element.', - ); + if (!isValidContainerLegacy(container)) { + throw new Error('Target container is not a DOM element.'); + } + if (__DEV__) { const isModernRoot = isContainerMarkedAsRoot(container) && @@ -278,10 +277,10 @@ export function render( ); } - invariant( - isValidContainerLegacy(container), - 'Target container is not a DOM element.', - ); + if (!isValidContainerLegacy(container)) { + throw new Error('Target container is not a DOM element.'); + } + if (__DEV__) { const isModernRoot = isContainerMarkedAsRoot(container) && @@ -309,14 +308,14 @@ export function unstable_renderSubtreeIntoContainer( containerNode: Container, callback: ?Function, ) { - invariant( - isValidContainerLegacy(containerNode), - 'Target container is not a DOM element.', - ); - invariant( - parentComponent != null && hasInstance(parentComponent), - 'parentComponent must be a valid React Component', - ); + if (!isValidContainerLegacy(containerNode)) { + throw new Error('Target container is not a DOM element.'); + } + + if (parentComponent == null || !hasInstance(parentComponent)) { + throw new Error('parentComponent must be a valid React Component'); + } + return legacyRenderSubtreeIntoContainer( parentComponent, element, @@ -327,10 +326,11 @@ export function unstable_renderSubtreeIntoContainer( } export function unmountComponentAtNode(container: Container) { - invariant( - isValidContainerLegacy(container), - 'unmountComponentAtNode(...): Target container is not a DOM element.', - ); + if (!isValidContainerLegacy(container)) { + throw new Error( + 'unmountComponentAtNode(...): Target container is not a DOM element.', + ); + } if (__DEV__) { const isModernRoot = diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 1a027d9a1e6a9..38f2100cb5418 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -65,7 +65,6 @@ import { flushSync, isAlreadyRendering, } from 'react-reconciler/src/ReactFiberReconciler'; -import invariant from 'shared/invariant'; import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags'; import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags'; @@ -76,7 +75,7 @@ function ReactDOMRoot(internalRoot: FiberRoot) { ReactDOMRoot.prototype.render = function(children: ReactNodeList): void { const root = this._internalRoot; if (root === null) { - invariant(false, 'Cannot update an unmounted root.'); + throw new Error('Cannot update an unmounted root.'); } if (__DEV__) { @@ -138,10 +137,10 @@ export function createRoot( container: Container, options?: CreateRootOptions, ): RootType { - invariant( - isValidContainerLegacy(container), - 'createRoot(...): Target container is not a DOM element.', - ); + if (!isValidContainerLegacy(container)) { + throw new Error('createRoot(...): Target container is not a DOM element.'); + } + warnIfReactDOMContainerInDEV(container); // TODO: Delete these options @@ -195,10 +194,10 @@ export function hydrateRoot( initialChildren: ReactNodeList, options?: HydrateRootOptions, ): RootType { - invariant( - isValidContainer(container), - 'hydrateRoot(...): Target container is not a DOM element.', - ); + if (!isValidContainer(container)) { + throw new Error('hydrateRoot(...): Target container is not a DOM element.'); + } + warnIfReactDOMContainerInDEV(container); // For now we reuse the whole bag of options since they contain diff --git a/packages/react-dom/src/client/ReactDOMTextarea.js b/packages/react-dom/src/client/ReactDOMTextarea.js index 6e8aed5cc43cd..6834b16e4e97c 100644 --- a/packages/react-dom/src/client/ReactDOMTextarea.js +++ b/packages/react-dom/src/client/ReactDOMTextarea.js @@ -7,7 +7,6 @@ * @flow */ -import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes'; @@ -40,10 +39,12 @@ type TextAreaWithWrapperState = HTMLTextAreaElement & {| export function getHostProps(element: Element, props: Object) { const node = ((element: any): TextAreaWithWrapperState); - invariant( - props.dangerouslySetInnerHTML == null, - '`dangerouslySetInnerHTML` does not make sense on