Warning system refactoring (part 1) (#16799)

* Rename lowPriorityWarning to lowPriorityWarningWithoutStack

This maintains parity with the other warning-like functions.

* Duplicate the toWarnDev tests to test toLowPriorityWarnDev

* Make a lowPriorityWarning version of warning.js

* Extract both variants in print-warning

Avoids parsing lowPriorityWarning.js itself as the way it forwards the
call to lowPriorityWarningWithoutStack is not analyzable.
This commit is contained in:
Jessica Franco
2019-09-24 21:45:38 +09:00
committed by Dan Abramov
parent 8b580a89d6
commit 18d2e0c03e
13 changed files with 291 additions and 50 deletions

View File

@@ -61,7 +61,7 @@ import ReactVersion from 'shared/ReactVersion';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import warningWithoutStack from 'shared/warningWithoutStack';
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
@@ -542,7 +542,7 @@ function legacyCreateRootFromDOMContainer(
if (__DEV__) {
if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
warnedAboutHydrateAPI = true;
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
@@ -801,7 +801,7 @@ const ReactDOM: Object = {
unstable_createPortal(...args) {
if (!didWarnAboutUnstableCreatePortal) {
didWarnAboutUnstableCreatePortal = true;
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'The ReactDOM.unstable_createPortal() alias has been deprecated, ' +
'and will be removed in React 17+. Update your code to use ' +

View File

@@ -15,7 +15,7 @@ import type {ReactProvider, ReactContext} from 'shared/ReactTypes';
import React from 'react';
import invariant from 'shared/invariant';
import getComponentName from 'shared/getComponentName';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';
import describeComponentFrame from 'shared/describeComponentFrame';
@@ -588,7 +588,7 @@ function resolve(
const componentName = getComponentName(Component) || 'Unknown';
if (!didWarnAboutDeprecatedWillMount[componentName]) {
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
// keep this warning in sync with ReactStrictModeWarning.js
'componentWillMount has been renamed, and is not recommended for use. ' +

View File

@@ -17,7 +17,7 @@ import {
} from 'shared/ReactWorkTags';
import SyntheticEvent from 'legacy-events/SyntheticEvent';
import invariant from 'shared/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import {ELEMENT_NODE} from '../shared/HTMLNodeType';
import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes';
import {PLUGIN_EVENT_SYSTEM} from 'legacy-events/EventSystemFlags';
@@ -361,7 +361,7 @@ const ReactTestUtils = {
mockComponent: function(module, mockTagName) {
if (!hasWarnedAboutDeprecatedMockComponent) {
hasWarnedAboutDeprecatedMockComponent = true;
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'ReactTestUtils.mockComponent() is deprecated. ' +
'Use shallow rendering or jest.mock() instead.\n\n' +

View File

@@ -25,7 +25,7 @@ import {
REACT_SUSPENSE_TYPE,
} from 'shared/ReactSymbols';
import isValidElementType from 'shared/isValidElementType';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
export function typeOf(object: any) {
if (typeof object === 'object' && object !== null) {
@@ -88,7 +88,7 @@ export function isAsyncMode(object: any) {
if (__DEV__) {
if (!hasWarnedAboutDeprecatedIsAsyncMode) {
hasWarnedAboutDeprecatedIsAsyncMode = true;
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'The ReactIs.isAsyncMode() alias has been deprecated, ' +
'and will be removed in React 17+. Update your code to use ' +

View File

@@ -13,7 +13,7 @@ import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
import getComponentName from 'shared/getComponentName';
import {StrictMode} from './ReactTypeOfMode';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import warningWithoutStack from 'shared/warningWithoutStack';
type FiberArray = Array<Fiber>;
@@ -237,7 +237,7 @@ if (__DEV__) {
if (componentWillMountUniqueNames.size > 0) {
const sortedNames = setToSortedString(componentWillMountUniqueNames);
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'componentWillMount has been renamed, and is not recommended for use. ' +
'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
@@ -256,7 +256,7 @@ if (__DEV__) {
componentWillReceivePropsUniqueNames,
);
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
@@ -276,7 +276,7 @@ if (__DEV__) {
if (componentWillUpdateUniqueNames.size > 0) {
const sortedNames = setToSortedString(componentWillUpdateUniqueNames);
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'componentWillUpdate has been renamed, and is not recommended for use. ' +
'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +

View File

@@ -6,7 +6,7 @@
*/
import invariant from 'shared/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
@@ -105,7 +105,7 @@ if (__DEV__) {
const defineDeprecationWarning = function(methodName, info) {
Object.defineProperty(Component.prototype, methodName, {
get: function() {
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'%s(...) is deprecated in plain JavaScript React classes. %s',
info[0],

View File

@@ -12,7 +12,7 @@
* that support it.
*/
import lowPriorityWarning from 'shared/lowPriorityWarning';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import isValidElementType from 'shared/isValidElementType';
import getComponentName from 'shared/getComponentName';
import {
@@ -477,7 +477,7 @@ export function createFactoryWithValidation(type) {
Object.defineProperty(validatedFactory, 'type', {
enumerable: false,
get: function() {
lowPriorityWarning(
lowPriorityWarningWithoutStack(
false,
'Factory.type is deprecated. Access the class directly ' +
'before passing it to createFactory.',

View File

@@ -5,4 +5,5 @@
* LICENSE file in the root directory of this source tree.
*/
// This "lowPriorityWarning" is an external module
export default require('lowPriorityWarning');

View File

@@ -5,47 +5,27 @@
* LICENSE file in the root directory of this source tree.
*/
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import ReactSharedInternals from 'shared/ReactSharedInternals';
/**
* Forked from fbjs/warning:
* https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/__forks__/warning.js
*
* Only change is we use console.warn instead of console.error,
* and do nothing when 'console' is not supported.
* This really simplifies the code.
* ---
* Similar to invariant but only logs a warning if the condition is not met.
* This can be used to log issues in development environments in critical
* paths. Removing the logging code for production environments will keep the
* same logic and follow the same code paths.
*/
let lowPriorityWarning = function() {};
let lowPriorityWarning = lowPriorityWarningWithoutStack;
if (__DEV__) {
const printWarning = function(format, ...args) {
let argIndex = 0;
const message = 'Warning: ' + format.replace(/%s/g, () => args[argIndex++]);
if (typeof console !== 'undefined') {
console.warn(message);
}
try {
// --- Welcome to debugging React ---
// This error was thrown as a convenience so that you can use this stack
// to find the callsite that caused this warning to fire.
throw new Error(message);
} catch (x) {}
};
lowPriorityWarning = function(condition, format, ...args) {
if (format === undefined) {
throw new Error(
'`lowPriorityWarning(condition, format, ...args)` requires a warning ' +
'message argument',
);
}
if (!condition) {
printWarning(format, ...args);
if (condition) {
return;
}
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
const stack = ReactDebugCurrentFrame.getStackAddendum();
// eslint-disable-next-line react-internal/warning-and-invariant-args
lowPriorityWarningWithoutStack(false, format + '%s', ...args, stack);
};
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Forked from fbjs/warning:
* https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/__forks__/warning.js
*
* Only change is we use console.warn instead of console.error,
* and do nothing when 'console' is not supported.
* This really simplifies the code.
* ---
* Similar to invariant but only logs a warning if the condition is not met.
* This can be used to log issues in development environments in critical
* paths. Removing the logging code for production environments will keep the
* same logic and follow the same code paths.
*/
let lowPriorityWarningWithoutStack = function() {};
if (__DEV__) {
const printWarning = function(format, ...args) {
let argIndex = 0;
const message = 'Warning: ' + format.replace(/%s/g, () => args[argIndex++]);
if (typeof console !== 'undefined') {
console.warn(message);
}
try {
// --- Welcome to debugging React ---
// This error was thrown as a convenience so that you can use this stack
// to find the callsite that caused this warning to fire.
throw new Error(message);
} catch (x) {}
};
lowPriorityWarningWithoutStack = function(condition, format, ...args) {
if (format === undefined) {
throw new Error(
'`lowPriorityWarningWithoutStack(condition, format, ...args)` requires a warning ' +
'message argument',
);
}
if (!condition) {
printWarning(format, ...args);
}
};
}
export default lowPriorityWarningWithoutStack;

View File

@@ -210,3 +210,209 @@ describe('toWarnDev', () => {
});
}
});
describe('toLowPriorityWarnDev', () => {
it('does not fail if a warning contains a stack', () => {
expect(() => {
if (__DEV__) {
console.warn('Hello\n in div');
}
}).toLowPriorityWarnDev('Hello');
});
it('does not fail if all warnings contain a stack', () => {
expect(() => {
if (__DEV__) {
console.warn('Hello\n in div');
console.warn('Good day\n in div');
console.warn('Bye\n in div');
}
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye']);
});
it('does not fail if warnings without stack explicitly opt out', () => {
expect(() => {
if (__DEV__) {
console.warn('Hello');
}
}).toLowPriorityWarnDev('Hello', {withoutStack: true});
expect(() => {
if (__DEV__) {
console.warn('Hello');
console.warn('Good day');
console.warn('Bye');
}
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye'], {withoutStack: true});
});
it('does not fail when expected stack-less warning number matches the actual one', () => {
expect(() => {
if (__DEV__) {
console.warn('Hello\n in div');
console.warn('Good day');
console.warn('Bye\n in div');
}
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye'], {withoutStack: 1});
});
if (__DEV__) {
// Helper methods avoids invalid toWarn().toThrow() nesting
// See no-to-warn-dev-within-to-throw
const expectToWarnAndToThrow = (expectBlock, expectedErrorMessage) => {
let caughtError;
try {
expectBlock();
} catch (error) {
caughtError = error;
}
expect(caughtError).toBeDefined();
expect(caughtError.message).toContain(expectedErrorMessage);
};
it('fails if a warning does not contain a stack', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello');
}).toLowPriorityWarnDev('Hello');
}, 'Received warning unexpectedly does not include a component stack');
});
it('fails if some warnings do not contain a stack', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello\n in div');
console.warn('Good day\n in div');
console.warn('Bye');
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye']);
}, 'Received warning unexpectedly does not include a component stack');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello');
console.warn('Good day\n in div');
console.warn('Bye\n in div');
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye']);
}, 'Received warning unexpectedly does not include a component stack');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello\n in div');
console.warn('Good day');
console.warn('Bye\n in div');
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye']);
}, 'Received warning unexpectedly does not include a component stack');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello');
console.warn('Good day');
console.warn('Bye');
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye']);
}, 'Received warning unexpectedly does not include a component stack');
});
it('fails if warning is expected to not have a stack, but does', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello\n in div');
}).toLowPriorityWarnDev('Hello', {withoutStack: true});
}, 'Received warning unexpectedly includes a component stack');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello\n in div');
console.warn('Good day');
console.warn('Bye\n in div');
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye'], {
withoutStack: true,
});
}, 'Received warning unexpectedly includes a component stack');
});
it('fails if expected stack-less warning number does not match the actual one', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hello\n in div');
console.warn('Good day');
console.warn('Bye\n in div');
}).toLowPriorityWarnDev(['Hello', 'Good day', 'Bye'], {
withoutStack: 4,
});
}, 'Expected 4 warnings without a component stack but received 1');
});
it('fails if withoutStack is invalid', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi');
}).toLowPriorityWarnDev('Hi', {withoutStack: null});
}, 'Instead received object');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi');
}).toLowPriorityWarnDev('Hi', {withoutStack: {}});
}, 'Instead received object');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi');
}).toLowPriorityWarnDev('Hi', {withoutStack: 'haha'});
}, 'Instead received string');
});
it('fails if the argument number does not match', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi %s', 'Sara', 'extra');
}).toLowPriorityWarnDev('Hi', {withoutStack: true});
}, 'Received 2 arguments for a message with 1 placeholders');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi %s');
}).toLowPriorityWarnDev('Hi', {withoutStack: true});
}, 'Received 0 arguments for a message with 1 placeholders');
});
it('fails if stack is passed twice', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi %s%s', '\n in div', '\n in div');
}).toLowPriorityWarnDev('Hi');
}, 'Received more than one component stack for a warning');
});
it('fails if multiple strings are passed without an array wrapper', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi \n in div');
}).toLowPriorityWarnDev('Hi', 'Bye');
}, 'toWarnDev() second argument, when present, should be an object');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi \n in div');
console.warn('Bye \n in div');
}).toLowPriorityWarnDev('Hi', 'Bye');
}, 'toWarnDev() second argument, when present, should be an object');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi \n in div');
console.warn('Wow \n in div');
console.warn('Bye \n in div');
}).toLowPriorityWarnDev('Hi', 'Bye');
}, 'toWarnDev() second argument, when present, should be an object');
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi \n in div');
console.warn('Wow \n in div');
console.warn('Bye \n in div');
}).toLowPriorityWarnDev('Hi', 'Wow', 'Bye');
}, 'toWarnDev() second argument, when present, should be an object');
});
it('fails on more than two arguments', () => {
expectToWarnAndToThrow(() => {
expect(() => {
console.warn('Hi \n in div');
console.warn('Wow \n in div');
console.warn('Bye \n in div');
}).toLowPriorityWarnDev('Hi', undefined, 'Bye');
}, 'toWarnDev() received more than two arguments.');
});
}
});

View File

@@ -53,7 +53,8 @@ function transform(file, enc, cb) {
if (
callee.isIdentifier({name: 'warning'}) ||
callee.isIdentifier({name: 'warningWithoutStack'}) ||
callee.isIdentifier({name: 'lowPriorityWarning'})
callee.isIdentifier({name: 'lowPriorityWarning'}) ||
callee.isIdentifier({name: 'lowPriorityWarningWithoutStack'})
) {
const node = astPath.node;
@@ -82,6 +83,7 @@ function transform(file, enc, cb) {
gs([
'packages/**/*.js',
'!packages/shared/warning.js',
'!packages/shared/lowPriorityWarning.js',
'!packages/react-devtools*/**/*.js',
'!**/__tests__/**/*.js',
'!**/__mocks__/**/*.js',

View File

@@ -181,12 +181,12 @@ const forks = Object.freeze({
},
// This logic is forked on www to ignore some warnings.
'shared/lowPriorityWarning': (bundleType, entry) => {
'shared/lowPriorityWarningWithoutStack': (bundleType, entry) => {
switch (bundleType) {
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
return 'shared/forks/lowPriorityWarning.www.js';
return 'shared/forks/lowPriorityWarningWithoutStack.www.js';
default:
return null;
}