Skip to content

Lint rule to detect unminified errors #22429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,41 @@ module.exports = {
},

overrides: [
{
// By default, anything error message that appears the packages directory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// By default, anything error message that appears the packages directory
// By default, any 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.
Expand Down
12 changes: 6 additions & 6 deletions packages/jest-react/src/JestReact.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down
10 changes: 5 additions & 5 deletions packages/react-art/src/ReactARTHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions packages/react-cache/src/ReactCacheOld.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 " +
Expand Down
1 change: 1 addition & 0 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ export function resolveError(
message: string,
stack: string,
): void {
// eslint-disable-next-line react-internal/prod-error-codes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe include a reason why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto elsewhere

const error = new Error(message);
error.stack = stack;
const chunks = response._chunks;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ export type StringDecoder = void;
export const supportsBinaryStreams = false;

export function createStringDecoder(): void {
// eslint-disable-next-line react-internal/prod-error-codes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about defining something like an NotImplementedError or UnminifiedError which is automatically ignored by the lint rule and parser?

throw new Error('Should never be called');
}

export function readPartialStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}

export function readFinalStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}
1 change: 1 addition & 0 deletions packages/react-fetch/src/ReactFetchBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ function preloadRecord(url: string, options: mixed): Record {
if (options.method || options.body || options.signal) {
// TODO: wire up our own cancellation mechanism.
// TODO: figure out what to do with POST.
// eslint-disable-next-line react-internal/prod-error-codes
throw Error('Unsupported option');
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-fetch/src/ReactFetchNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ function preloadRecord(url: string, options: mixed): Record<FetchResponse> {
if (options.method || options.body || options.signal) {
// TODO: wire up our own cancellation mechanism.
// TODO: figure out what to do with POST.
// eslint-disable-next-line react-internal/prod-error-codes
throw Error('Unsupported option');
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,7 @@ function updateClassComponent(
case true: {
workInProgress.flags |= DidCapture;
workInProgress.flags |= ShouldCapture;
// eslint-disable-next-line react-internal/prod-error-codes
const error = new Error('Simulated error coming from DevTools');
const lane = pickArbitraryLane(renderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
Expand Down Expand Up @@ -3317,6 +3318,7 @@ function remountFiber(
if (__DEV__) {
const returnFiber = oldWorkInProgress.return;
if (returnFiber === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Cannot swap the root fiber.');
}

Expand All @@ -3337,11 +3339,13 @@ function remountFiber(
} else {
let prevSibling = returnFiber.child;
if (prevSibling === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Expected parent to have a child.');
}
while (prevSibling.sibling !== oldWorkInProgress) {
prevSibling = prevSibling.sibling;
if (prevSibling === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Expected to find the previous sibling.');
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,7 @@ function updateClassComponent(
case true: {
workInProgress.flags |= DidCapture;
workInProgress.flags |= ShouldCapture;
// eslint-disable-next-line react-internal/prod-error-codes
const error = new Error('Simulated error coming from DevTools');
const lane = pickArbitraryLane(renderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
Expand Down Expand Up @@ -3317,6 +3318,7 @@ function remountFiber(
if (__DEV__) {
const returnFiber = oldWorkInProgress.return;
if (returnFiber === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Cannot swap the root fiber.');
}

Expand All @@ -3337,11 +3339,13 @@ function remountFiber(
} else {
let prevSibling = returnFiber.child;
if (prevSibling === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Expected parent to have a child.');
}
while (prevSibling.sibling !== oldWorkInProgress) {
prevSibling = prevSibling.sibling;
if (prevSibling === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Expected to find the previous sibling.');
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/ReactFiberHooks.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,7 @@ function pushEffect(tag, create, destroy, deps) {
let stackContainsErrorMessage: boolean | null = null;

function getCallerStackFrame(): string {
// eslint-disable-next-line react-internal/prod-error-codes
const stackFrames = new Error('Error message').stack.split('\n');

// Some browsers (e.g. Chrome) include the error message in the stack
Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/ReactFiberHooks.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,7 @@ function pushEffect(tag, create, destroy, deps) {
let stackContainsErrorMessage: boolean | null = null;

function getCallerStackFrame(): string {
// eslint-disable-next-line react-internal/prod-error-codes
const stackFrames = new Error('Error message').stack.split('\n');

// Some browsers (e.g. Chrome) include the error message in the stack
Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/ReactFiberHotReloading.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* @flow
*/

/* eslint-disable react-internal/prod-error-codes */

import type {ReactElement} from 'shared/ReactElementType';
import type {Fiber} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/ReactFiberHotReloading.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* @flow
*/

/* eslint-disable react-internal/prod-error-codes */

import type {ReactElement} from 'shared/ReactElementType';
import type {Fiber} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiberThrow.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,8 @@ function throwException(
return;
} else {
// No boundary was found. Fallthrough to error mode.
// TODO: Use invariant so the message is stripped in prod?
// TODO: We should never call getComponentNameFromFiber in production.
// Log a warning or something to prevent us from accidentally bundling it.
value = new Error(
(getComponentNameFromFiber(sourceFiber) || 'A React component') +
' suspended while rendering, but no fallback UI was specified.\n' +
Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiberThrow.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,8 @@ function throwException(
return;
} else {
// No boundary was found. Fallthrough to error mode.
// TODO: Use invariant so the message is stripped in prod?
// TODO: We should never call getComponentNameFromFiber in production.
// Log a warning or something to prevent us from accidentally bundling it.
value = new Error(
(getComponentNameFromFiber(sourceFiber) || 'A React component') +
' suspended while rendering, but no fallback UI was specified.\n' +
Expand Down
1 change: 1 addition & 0 deletions packages/react/unstable-shared-subset.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @flow
*/

// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'This entry point is not yet supported outside of experimental channels',
);
1 change: 1 addition & 0 deletions packages/scheduler/src/forks/SchedulerMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

/* eslint-disable no-var */
/* eslint-disable react-internal/prod-error-codes */

import {
enableSchedulerDebugging,
Expand Down
1 change: 1 addition & 0 deletions packages/shared/checkPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function checkPropTypes(
// This is intentionally an invariant that gets caught. It's the same
// behavior as without this statement except with a better message.
if (typeof typeSpecs[typeSpecName] !== 'function') {
// eslint-disable-next-line react-internal/prod-error-codes
const err = Error(
(componentName || 'React class') +
': ' +
Expand Down
1 change: 1 addition & 0 deletions packages/shared/invariant.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

export default function invariant(condition, format, a, b, c, d, e, f) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'Internal React error: invariant() is meant to be replaced at compile ' +
'time. There is no runtime version.',
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/invokeGuardedCallbackImpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ if (__DEV__) {
if (didCall && didError) {
if (!didSetError) {
// The callback errored, but the error event never fired.
// eslint-disable-next-line react-internal/prod-error-codes
error = new Error(
'An error was thrown inside one of your components, but React ' +
"doesn't know what it was. This is likely due to browser " +
Expand All @@ -212,6 +213,7 @@ if (__DEV__) {
'actually an issue with React, please file an issue.',
);
} else if (isCrossOriginError) {
// eslint-disable-next-line react-internal/prod-error-codes
error = new Error(
"A cross-origin error was thrown. React doesn't have access to " +
'the actual error object in development. ' +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`error transform handles escaped backticks in template string 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
Error(__DEV__ ? \\"Expected \`\\" + listener + \\"\` listener to be a function, instead got a value of \`\\" + type + \\"\` type.\\" : _formatProdErrorMessage(231, listener, type));"
`;

exports[`error transform should correctly transform invariants that are not in the error codes map 1`] = `
"import invariant from 'shared/invariant';

Expand All @@ -18,6 +23,21 @@ if (!condition) {
}"
`;

exports[`error transform should not touch other calls or new expressions 1`] = `
"new NotAnError();
NotAnError();"
`;

exports[`error transform should replace error constructors (no new) 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
Error(__DEV__ ? 'Do not override existing functions.' : _formatProdErrorMessage(16));"
`;

exports[`error transform should replace error constructors 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
Error(__DEV__ ? 'Do not override existing functions.' : _formatProdErrorMessage(16));"
`;

exports[`error transform should replace simple invariant calls 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
import invariant from 'shared/invariant';
Expand All @@ -29,6 +49,21 @@ if (!condition) {
}"
`;

exports[`error transform should support error constructors with concatenated messages 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
Error(__DEV__ ? \\"Expected \\" + foo + \\" target to \\" + (\\"be an array; got \\" + bar) : _formatProdErrorMessage(7, foo, bar));"
`;

exports[`error transform should support interpolating arguments with concatenation 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
Error(__DEV__ ? 'Expected ' + foo + ' target to be an array; got ' + bar : _formatProdErrorMessage(7, foo, bar));"
`;

exports[`error transform should support interpolating arguments with template strings 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
Error(__DEV__ ? \\"Expected \\" + foo + \\" target to be an array; got \\" + bar : _formatProdErrorMessage(7, foo, bar));"
`;

exports[`error transform should support invariant calls with a concatenated template string and args 1`] = `
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
import invariant from 'shared/invariant';
Expand Down
Loading