Replace `wrap-warning-with-env-check` with an eslint plugin (#17540)

* Replace Babel plugin with an ESLint plugin

* Fix ESLint rule violations

* Move shared conditions higher

* Test formatting nits

* Tweak ESLint rule

* Bugfix: inside else branch, 'if' tests are not satisfactory

* Use a stricter check for exactly if (__DEV__)

This makes it easier to see what's going on and matches dominant style in the codebase.

* Fix remaining files after stricter check
This commit is contained in:
Laura buns 2019-12-06 18:25:54 +00:00 committed by Dan Abramov
parent acfe4b21b2
commit b43eec7eaa
35 changed files with 933 additions and 550 deletions

View File

@ -93,6 +93,7 @@ module.exports = {
'react-internal/no-primitive-constructors': ERROR, 'react-internal/no-primitive-constructors': ERROR,
'react-internal/no-to-warn-dev-within-to-throw': ERROR, 'react-internal/no-to-warn-dev-within-to-throw': ERROR,
'react-internal/warning-and-invariant-args': ERROR, 'react-internal/warning-and-invariant-args': ERROR,
'react-internal/no-production-logging': ERROR,
}, },
overrides: [ overrides: [

View File

@ -36,6 +36,7 @@ export function createSubscription<Property, Value>(
}> { }> {
const {getCurrentValue, subscribe} = config; const {getCurrentValue, subscribe} = config;
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
typeof getCurrentValue === 'function', typeof getCurrentValue === 'function',
'Subscription must specify a getCurrentValue function', 'Subscription must specify a getCurrentValue function',
@ -44,6 +45,7 @@ export function createSubscription<Property, Value>(
typeof subscribe === 'function', typeof subscribe === 'function',
'Subscription must specify a subscribe function', 'Subscription must specify a subscribe function',
); );
}
type Props = { type Props = {
children: (value: Value) => React$Element<any>, children: (value: Value) => React$Element<any>,

View File

@ -283,9 +283,9 @@ function getPooledWarningPropertyDefinition(propName, getVal) {
} }
function warn(action, result) { function warn(action, result) {
const warningCondition = false; if (__DEV__) {
warningWithoutStack( warningWithoutStack(
warningCondition, false,
"This synthetic event is reused for performance reasons. If you're seeing this, " + "This synthetic event is reused for performance reasons. If you're seeing this, " +
"you're %s `%s` on a released/nullified synthetic event. %s. " + "you're %s `%s` on a released/nullified synthetic event. %s. " +
'If you must keep the original synthetic event around, use event.persist(). ' + 'If you must keep the original synthetic event around, use event.persist(). ' +
@ -296,6 +296,7 @@ function getPooledWarningPropertyDefinition(propName, getVal) {
); );
} }
} }
}
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) { function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
const EventConstructor = this; const EventConstructor = this;

View File

@ -142,6 +142,7 @@ const ReactDOM: Object = {
// Temporary alias since we already shipped React 16 RC with it. // Temporary alias since we already shipped React 16 RC with it.
// TODO: remove in React 17. // TODO: remove in React 17.
unstable_createPortal(...args) { unstable_createPortal(...args) {
if (__DEV__) {
if (!didWarnAboutUnstableCreatePortal) { if (!didWarnAboutUnstableCreatePortal) {
didWarnAboutUnstableCreatePortal = true; didWarnAboutUnstableCreatePortal = true;
lowPriorityWarningWithoutStack( lowPriorityWarningWithoutStack(
@ -152,6 +153,7 @@ const ReactDOM: Object = {
'but without the "unstable_" prefix.', 'but without the "unstable_" prefix.',
); );
} }
}
return createPortal(...args); return createPortal(...args);
}, },

View File

@ -40,6 +40,7 @@ const valuePropNames = ['value', 'defaultValue'];
* Validation function for `value` and `defaultValue`. * Validation function for `value` and `defaultValue`.
*/ */
function checkSelectPropTypes(props) { function checkSelectPropTypes(props) {
if (__DEV__) {
ReactControlledValuePropTypes.checkPropTypes('select', props); ReactControlledValuePropTypes.checkPropTypes('select', props);
for (let i = 0; i < valuePropNames.length; i++) { for (let i = 0; i < valuePropNames.length; i++) {
@ -67,6 +68,7 @@ function checkSelectPropTypes(props) {
} }
} }
} }
}
function updateOptions( function updateOptions(
node: HTMLSelectElement, node: HTMLSelectElement,

View File

@ -687,6 +687,7 @@ function resolve(
); );
} }
} else { } else {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'%s.getChildContext(): childContextTypes must be defined in order to ' + '%s.getChildContext(): childContextTypes must be defined in order to ' +
@ -695,6 +696,7 @@ function resolve(
); );
} }
} }
}
if (childContext) { if (childContext) {
context = Object.assign({}, context, childContext); context = Object.assign({}, context, childContext);
} }

View File

@ -392,7 +392,6 @@ export function useLayoutEffect(
) { ) {
if (__DEV__) { if (__DEV__) {
currentHookNameInDev = 'useLayoutEffect'; currentHookNameInDev = 'useLayoutEffect';
}
warning( warning(
false, false,
'useLayoutEffect does nothing on the server, because its effect cannot ' + 'useLayoutEffect does nothing on the server, because its effect cannot ' +
@ -403,6 +402,7 @@ export function useLayoutEffect(
'See https://fb.me/react-uselayouteffect-ssr for common fixes.', 'See https://fb.me/react-uselayouteffect-ssr for common fixes.',
); );
} }
}
function dispatchAction<A>( function dispatchAction<A>(
componentIdentity: Object, componentIdentity: Object,

View File

@ -128,6 +128,7 @@ export function validateShorthandPropertyCollisionInDev(
styleUpdates, styleUpdates,
nextStyles, nextStyles,
) { ) {
if (__DEV__) {
if (!warnAboutShorthandPropertyCollision) { if (!warnAboutShorthandPropertyCollision) {
return; return;
} }
@ -162,3 +163,4 @@ export function validateShorthandPropertyCollisionInDev(
} }
} }
} }
}

View File

@ -18,6 +18,7 @@ const rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$');
const hasOwnProperty = Object.prototype.hasOwnProperty; const hasOwnProperty = Object.prototype.hasOwnProperty;
function validateProperty(tagName, name) { function validateProperty(tagName, name) {
if (__DEV__) {
if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) { if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) {
return true; return true;
} }
@ -76,11 +77,13 @@ function validateProperty(tagName, name) {
return true; return true;
} }
} }
}
return true; return true;
} }
function warnInvalidARIAProps(type, props) { function warnInvalidARIAProps(type, props) {
if (__DEV__) {
const invalidProps = []; const invalidProps = [];
for (const key in props) { for (const key in props) {
@ -112,6 +115,7 @@ function warnInvalidARIAProps(type, props) {
); );
} }
} }
}
export function validateProperties(type, props) { export function validateProperties(type, props) {
if (isCustomComponent(type, props)) { if (isCustomComponent(type, props)) {

View File

@ -10,6 +10,7 @@ import warning from 'shared/warning';
let didWarnValueNull = false; let didWarnValueNull = false;
export function validateProperties(type, props) { export function validateProperties(type, props) {
if (__DEV__) {
if (type !== 'input' && type !== 'textarea' && type !== 'select') { if (type !== 'input' && type !== 'textarea' && type !== 'select') {
return; return;
} }
@ -35,3 +36,4 @@ export function validateProperties(type, props) {
} }
} }
} }
}

View File

@ -255,9 +255,15 @@ if (__DEV__) {
} }
const warnUnknownProperties = function(type, props, canUseEventSystem) { const warnUnknownProperties = function(type, props, canUseEventSystem) {
if (__DEV__) {
const unknownProps = []; const unknownProps = [];
for (const key in props) { for (const key in props) {
const isValid = validateProperty(type, key, props[key], canUseEventSystem); const isValid = validateProperty(
type,
key,
props[key],
canUseEventSystem,
);
if (!isValid) { if (!isValid) {
unknownProps.push(key); unknownProps.push(key);
} }
@ -285,6 +291,7 @@ const warnUnknownProperties = function(type, props, canUseEventSystem) {
type, type,
); );
} }
}
}; };
export function validateProperties(type, props, canUseEventSystem) { export function validateProperties(type, props, canUseEventSystem) {

View File

@ -38,7 +38,8 @@ function sanitizeURL(url: string) {
'React has blocked a javascript: URL as a security precaution.%s', 'React has blocked a javascript: URL as a security precaution.%s',
__DEV__ ? ReactDebugCurrentFrame.getStackAddendum() : '', __DEV__ ? ReactDebugCurrentFrame.getStackAddendum() : '',
); );
} else if (__DEV__ && !didWarn && isJavaScriptProtocol.test(url)) { } else if (__DEV__) {
if (!didWarn && isJavaScriptProtocol.test(url)) {
didWarn = true; didWarn = true;
warning( warning(
false, false,
@ -49,5 +50,6 @@ function sanitizeURL(url: string) {
); );
} }
} }
}
export default sanitizeURL; export default sanitizeURL;

View File

@ -359,6 +359,7 @@ const ReactTestUtils = {
* @return {object} the ReactTestUtils object (for chaining) * @return {object} the ReactTestUtils object (for chaining)
*/ */
mockComponent: function(module, mockTagName) { mockComponent: function(module, mockTagName) {
if (__DEV__) {
if (!hasWarnedAboutDeprecatedMockComponent) { if (!hasWarnedAboutDeprecatedMockComponent) {
hasWarnedAboutDeprecatedMockComponent = true; hasWarnedAboutDeprecatedMockComponent = true;
lowPriorityWarningWithoutStack( lowPriorityWarningWithoutStack(
@ -368,6 +369,7 @@ const ReactTestUtils = {
'See https://fb.me/test-utils-mock-component for more information.', 'See https://fb.me/test-utils-mock-component for more information.',
); );
} }
}
mockTagName = mockTagName || module.mockTagName || 'div'; mockTagName = mockTagName || module.mockTagName || 'div';

View File

@ -179,12 +179,14 @@ export default function(
} }
if (maybeInstance.canonical) { if (maybeInstance.canonical) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: measureLayout on components using NativeMethodsMixin ' + 'Warning: measureLayout on components using NativeMethodsMixin ' +
'or ReactNative.NativeComponent is not currently supported in Fabric. ' + 'or ReactNative.NativeComponent is not currently supported in Fabric. ' +
'measureLayout must be called on a native ref. Consider using forwardRef.', 'measureLayout must be called on a native ref. Consider using forwardRef.',
); );
}
return; return;
} else { } else {
let relativeNode; let relativeNode;
@ -197,10 +199,12 @@ export default function(
} }
if (relativeNode == null) { if (relativeNode == null) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.', 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
); );
}
return; return;
} }
@ -243,10 +247,12 @@ export default function(
} }
if (maybeInstance.canonical) { if (maybeInstance.canonical) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: setNativeProps is not currently supported in Fabric', 'Warning: setNativeProps is not currently supported in Fabric',
); );
}
return; return;
} }

View File

@ -165,11 +165,13 @@ const ReactFabric: ReactFabricType = {
handle._nativeTag == null || handle._internalInstanceHandle == null; handle._nativeTag == null || handle._internalInstanceHandle == null;
if (invalid) { if (invalid) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
!invalid, !invalid,
"dispatchCommand was called with a ref that isn't a " + "dispatchCommand was called with a ref that isn't a " +
'native component. Use React.forwardRef to get access to the underlying native component', 'native component. Use React.forwardRef to get access to the underlying native component',
); );
}
return; return;
} }

View File

@ -159,10 +159,12 @@ class ReactFabricHostComponent {
typeof relativeToNativeNode === 'number' || typeof relativeToNativeNode === 'number' ||
!(relativeToNativeNode instanceof ReactFabricHostComponent) !(relativeToNativeNode instanceof ReactFabricHostComponent)
) { ) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: ref.measureLayout must be called with a ref to a native component.', 'Warning: ref.measureLayout must be called with a ref to a native component.',
); );
}
return; return;
} }
@ -176,10 +178,12 @@ class ReactFabricHostComponent {
} }
setNativeProps(nativeProps: Object) { setNativeProps(nativeProps: Object) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: setNativeProps is not currently supported in Fabric', 'Warning: setNativeProps is not currently supported in Fabric',
); );
}
return; return;
} }

View File

@ -190,12 +190,14 @@ export default function(
} }
if (maybeInstance.canonical) { if (maybeInstance.canonical) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: measureLayout on components using NativeMethodsMixin ' + 'Warning: measureLayout on components using NativeMethodsMixin ' +
'or ReactNative.NativeComponent is not currently supported in Fabric. ' + 'or ReactNative.NativeComponent is not currently supported in Fabric. ' +
'measureLayout must be called on a native ref. Consider using forwardRef.', 'measureLayout must be called on a native ref. Consider using forwardRef.',
); );
}
return; return;
} else { } else {
let relativeNode; let relativeNode;
@ -208,10 +210,12 @@ export default function(
} }
if (relativeNode == null) { if (relativeNode == null) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.', 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
); );
}
return; return;
} }
@ -254,10 +258,12 @@ export default function(
} }
if (maybeInstance.canonical) { if (maybeInstance.canonical) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: setNativeProps is not currently supported in Fabric', 'Warning: setNativeProps is not currently supported in Fabric',
); );
}
return; return;
} }

View File

@ -85,10 +85,12 @@ class ReactNativeFiberHostComponent {
} }
if (relativeNode == null) { if (relativeNode == null) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
false, false,
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.', 'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
); );
}
return; return;
} }

View File

@ -172,11 +172,13 @@ const ReactNativeRenderer: ReactNativeType = {
dispatchCommand(handle: any, command: string, args: Array<any>) { dispatchCommand(handle: any, command: string, args: Array<any>) {
if (handle._nativeTag == null) { if (handle._nativeTag == null) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
handle._nativeTag != null, handle._nativeTag != null,
"dispatchCommand was called with a ref that isn't a " + "dispatchCommand was called with a ref that isn't a " +
'native component. Use React.forwardRef to get access to the underlying native component', 'native component. Use React.forwardRef to get access to the underlying native component',
); );
}
return; return;
} }

View File

@ -232,6 +232,7 @@ function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
} }
function warnOnFunctionType() { function warnOnFunctionType() {
if (__DEV__) {
const currentComponentErrorInfo = const currentComponentErrorInfo =
'Functions are not valid as a React child. This may happen if ' + 'Functions are not valid as a React child. This may happen if ' +
'you return a Component instead of <Component /> from render. ' + 'you return a Component instead of <Component /> from render. ' +
@ -250,6 +251,7 @@ function warnOnFunctionType() {
'Or maybe you meant to call this function rather than return it.', 'Or maybe you meant to call this function rather than return it.',
); );
} }
}
// This wrapper function exists because I expect to clone the code in each path // This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs // to be able to optimize each path individually by branching early. This needs

View File

@ -1397,6 +1397,7 @@ function mountIndeterminateComponent(
} }
function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) { function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
if (__DEV__) {
if (Component) { if (Component) {
warningWithoutStack( warningWithoutStack(
!Component.childContextTypes, !Component.childContextTypes,
@ -1474,6 +1475,7 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
} }
} }
} }
}
const SUSPENDED_MARKER: SuspenseState = { const SUSPENDED_MARKER: SuspenseState = {
dehydrated: null, dehydrated: null,

View File

@ -89,7 +89,8 @@ export function injectInternals(internals: Object): boolean {
hook.onCommitFiberRoot(rendererID, root, undefined, didError); hook.onCommitFiberRoot(rendererID, root, undefined, didError);
} }
} catch (err) { } catch (err) {
if (__DEV__ && !hasLoggedError) { if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true; hasLoggedError = true;
warningWithoutStack( warningWithoutStack(
false, false,
@ -98,12 +99,14 @@ export function injectInternals(internals: Object): boolean {
); );
} }
} }
}
}; };
onCommitFiberUnmount = fiber => { onCommitFiberUnmount = fiber => {
try { try {
hook.onCommitFiberUnmount(rendererID, fiber); hook.onCommitFiberUnmount(rendererID, fiber);
} catch (err) { } catch (err) {
if (__DEV__ && !hasLoggedError) { if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true; hasLoggedError = true;
warningWithoutStack( warningWithoutStack(
false, false,
@ -112,6 +115,7 @@ export function injectInternals(internals: Object): boolean {
); );
} }
} }
}
}; };
} catch (err) { } catch (err) {
// Catch all errors because it is unsafe to throw during initialization. // Catch all errors because it is unsafe to throw during initialization.

View File

@ -280,12 +280,14 @@ export function updateContainer(
callback = callback === undefined ? null : callback; callback = callback === undefined ? null : callback;
if (callback !== null) { if (callback !== null) {
if (__DEV__) {
warningWithoutStack( warningWithoutStack(
typeof callback === 'function', typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' + 'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.', 'function. Instead received: %s.',
callback, callback,
); );
}
update.callback = callback; update.callback = callback;
} }

View File

@ -1089,13 +1089,15 @@ export function flushDiscreteUpdates() {
(executionContext & (BatchedContext | RenderContext | CommitContext)) !== (executionContext & (BatchedContext | RenderContext | CommitContext)) !==
NoContext NoContext
) { ) {
if (__DEV__ && (executionContext & RenderContext) !== NoContext) { if (__DEV__) {
if ((executionContext & RenderContext) !== NoContext) {
warning( warning(
false, false,
'unstable_flushDiscreteUpdates: Cannot flush updates when React is ' + 'unstable_flushDiscreteUpdates: Cannot flush updates when React is ' +
'already rendering.', 'already rendering.',
); );
} }
}
// We're already rendering, so we can't synchronously flush pending work. // We're already rendering, so we can't synchronously flush pending work.
// This is probably a nested event dispatch triggered by a lifecycle/effect, // This is probably a nested event dispatch triggered by a lifecycle/effect,
// like `el.focus()`. Exit. // like `el.focus()`. Exit.

View File

@ -61,6 +61,7 @@ function areHookInputsEqual(
prevDeps: Array<mixed> | null, prevDeps: Array<mixed> | null,
) { ) {
if (prevDeps === null) { if (prevDeps === null) {
if (__DEV__) {
warning( warning(
false, false,
'%s received a final argument during this render, but not during ' + '%s received a final argument during this render, but not during ' +
@ -68,9 +69,11 @@ function areHookInputsEqual(
'its type cannot change between renders.', 'its type cannot change between renders.',
currentHookNameInDev, currentHookNameInDev,
); );
}
return false; return false;
} }
if (__DEV__) {
// Don't bother comparing lengths in prod because these arrays should be // Don't bother comparing lengths in prod because these arrays should be
// passed inline. // passed inline.
if (nextDeps.length !== prevDeps.length) { if (nextDeps.length !== prevDeps.length) {
@ -85,6 +88,7 @@ function areHookInputsEqual(
`[${prevDeps.join(', ')}]`, `[${prevDeps.join(', ')}]`,
); );
} }
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) { if (is(nextDeps[i], prevDeps[i])) {
continue; continue;

View File

@ -214,7 +214,8 @@ export function createTextInstance(
hostContext: Object, hostContext: Object,
internalInstanceHandle: Object, internalInstanceHandle: Object,
): TextInstance { ): TextInstance {
if (__DEV__ && enableFlareAPI) { if (__DEV__) {
if (enableFlareAPI) {
warning( warning(
hostContext !== EVENT_COMPONENT_CONTEXT, hostContext !== EVENT_COMPONENT_CONTEXT,
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' + 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
@ -222,6 +223,7 @@ export function createTextInstance(
text, text,
); );
} }
}
return { return {
text, text,
isHidden: false, isHidden: false,

View File

@ -48,6 +48,7 @@ function hasValidKey(config) {
function defineKeyPropWarningGetter(props, displayName) { function defineKeyPropWarningGetter(props, displayName) {
const warnAboutAccessingKey = function() { const warnAboutAccessingKey = function() {
if (__DEV__) {
if (!specialPropKeyWarningShown) { if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true; specialPropKeyWarningShown = true;
warningWithoutStack( warningWithoutStack(
@ -59,6 +60,7 @@ function defineKeyPropWarningGetter(props, displayName) {
displayName, displayName,
); );
} }
}
}; };
warnAboutAccessingKey.isReactWarning = true; warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, 'key', { Object.defineProperty(props, 'key', {
@ -69,6 +71,7 @@ function defineKeyPropWarningGetter(props, displayName) {
function defineRefPropWarningGetter(props, displayName) { function defineRefPropWarningGetter(props, displayName) {
const warnAboutAccessingRef = function() { const warnAboutAccessingRef = function() {
if (__DEV__) {
if (!specialPropRefWarningShown) { if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true; specialPropRefWarningShown = true;
warningWithoutStack( warningWithoutStack(
@ -80,6 +83,7 @@ function defineRefPropWarningGetter(props, displayName) {
displayName, displayName,
); );
} }
}
}; };
warnAboutAccessingRef.isReactWarning = true; warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', { Object.defineProperty(props, 'ref', {

View File

@ -194,6 +194,7 @@ function validateChildKeys(node, parentType) {
* @param {ReactElement} element * @param {ReactElement} element
*/ */
function validatePropTypes(element) { function validatePropTypes(element) {
if (__DEV__) {
const type = element.type; const type = element.type;
if (type === null || type === undefined || typeof type === 'string') { if (type === null || type === undefined || typeof type === 'string') {
return; return;
@ -239,12 +240,14 @@ function validatePropTypes(element) {
); );
} }
} }
}
/** /**
* Given a fragment, validate that it can only be provided with fragment props * Given a fragment, validate that it can only be provided with fragment props
* @param {ReactElement} fragment * @param {ReactElement} fragment
*/ */
function validateFragmentProps(fragment) { function validateFragmentProps(fragment) {
if (__DEV__) {
setCurrentlyValidatingElement(fragment); setCurrentlyValidatingElement(fragment);
const keys = Object.keys(fragment.props); const keys = Object.keys(fragment.props);
@ -267,6 +270,7 @@ function validateFragmentProps(fragment) {
setCurrentlyValidatingElement(null); setCurrentlyValidatingElement(null);
} }
}
export function jsxWithValidation( export function jsxWithValidation(
type, type,
@ -313,6 +317,7 @@ export function jsxWithValidation(
typeString = typeof type; typeString = typeof type;
} }
if (__DEV__) {
warning( warning(
false, false,
'React.jsx: type is invalid -- expected a string (for ' + 'React.jsx: type is invalid -- expected a string (for ' +
@ -322,6 +327,7 @@ export function jsxWithValidation(
info, info,
); );
} }
}
const element = jsxDEV(type, props, key, source, self); const element = jsxDEV(type, props, key, source, self);
@ -350,6 +356,7 @@ export function jsxWithValidation(
Object.freeze(children); Object.freeze(children);
} }
} else { } else {
if (__DEV__) {
warning( warning(
false, false,
'React.jsx: Static children should always be an array. ' + 'React.jsx: Static children should always be an array. ' +
@ -357,6 +364,7 @@ export function jsxWithValidation(
'Use the Babel transform instead.', 'Use the Babel transform instead.',
); );
} }
}
} else { } else {
validateChildKeys(children, type); validateChildKeys(children, type);
} }
@ -364,6 +372,7 @@ export function jsxWithValidation(
} }
if (hasOwnProperty.call(props, 'key')) { if (hasOwnProperty.call(props, 'key')) {
if (__DEV__) {
warning( warning(
false, false,
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' + 'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
@ -371,6 +380,7 @@ export function jsxWithValidation(
'E.g. <ComponentName {...props} key={key} />', 'E.g. <ComponentName {...props} key={key} />',
); );
} }
}
if (type === REACT_FRAGMENT_TYPE) { if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element); validateFragmentProps(element);
@ -431,6 +441,7 @@ export function createElementWithValidation(type, props, children) {
typeString = typeof type; typeString = typeof type;
} }
if (__DEV__) {
warning( warning(
false, false,
'React.createElement: type is invalid -- expected a string (for ' + 'React.createElement: type is invalid -- expected a string (for ' +
@ -440,6 +451,7 @@ export function createElementWithValidation(type, props, children) {
info, info,
); );
} }
}
const element = createElement.apply(this, arguments); const element = createElement.apply(this, arguments);

View File

@ -8,7 +8,7 @@
'use strict'; 'use strict';
let babel = require('@babel/core'); let babel = require('@babel/core');
let wrapWarningWithEnvCheck = require('../wrap-warning-with-env-check'); let wrapWarningWithEnvCheck = require('../lift-warning-conditional-argument');
function transform(input) { function transform(input) {
return babel.transform(input, { return babel.transform(input, {
@ -23,7 +23,7 @@ function compare(input, output) {
let oldEnv; let oldEnv;
describe('wrap-warning-with-env-check', () => { describe('lift-warning-conditional-argument', () => {
beforeEach(() => { beforeEach(() => {
oldEnv = process.env.NODE_ENV; oldEnv = process.env.NODE_ENV;
process.env.NODE_ENV = ''; process.env.NODE_ENV = '';
@ -36,14 +36,14 @@ describe('wrap-warning-with-env-check', () => {
it('should wrap warning calls', () => { it('should wrap warning calls', () => {
compare( compare(
"warning(condition, 'a %s b', 'c');", "warning(condition, 'a %s b', 'c');",
"__DEV__ ? !condition ? warning(false, 'a %s b', 'c') : void 0 : void 0;" "!condition ? warning(false, 'a %s b', 'c') : void 0;"
); );
}); });
it('should wrap warningWithoutStack calls', () => { it('should wrap warningWithoutStack calls', () => {
compare( compare(
"warningWithoutStack(condition, 'a %s b', 'c');", "warningWithoutStack(condition, 'a %s b', 'c');",
"__DEV__ ? !condition ? warningWithoutStack(false, 'a %s b', 'c') : void 0 : void 0;" "!condition ? warningWithoutStack(false, 'a %s b', 'c') : void 0;"
); );
}); });

View File

@ -9,8 +9,6 @@
module.exports = function(babel, options) { module.exports = function(babel, options) {
const t = babel.types; const t = babel.types;
const DEV_EXPRESSION = t.identifier('__DEV__');
const SEEN_SYMBOL = Symbol('expression.seen'); const SEEN_SYMBOL = Symbol('expression.seen');
return { return {
@ -34,11 +32,9 @@ module.exports = function(babel, options) {
// //
// into this: // into this:
// //
// if (__DEV__) {
// if (!condition) { // if (!condition) {
// warning(false, argument, argument); // warning(false, argument, argument);
// } // }
// }
// //
// The goal is to strip out warning calls entirely in production // The goal is to strip out warning calls entirely in production
// and to avoid evaluating the arguments in development. // and to avoid evaluating the arguments in development.
@ -49,14 +45,9 @@ module.exports = function(babel, options) {
); );
newNode[SEEN_SYMBOL] = true; newNode[SEEN_SYMBOL] = true;
path.replaceWith( path.replaceWith(
t.ifStatement(
DEV_EXPRESSION,
t.blockStatement([
t.ifStatement( t.ifStatement(
t.unaryExpression('!', condition), t.unaryExpression('!', condition),
t.expressionStatement(newNode) t.expressionStatement(newNode)
),
])
) )
); );
} }

View File

@ -0,0 +1,234 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
const rule = require('../no-production-logging');
const RuleTester = require('eslint').RuleTester;
const ruleTester = new RuleTester();
ruleTester.run('no-production-logging', rule, {
valid: [
{
code: `
if (__DEV__) {
warning(test, 'Oh no');
}
`,
},
{
code: `
if (__DEV__) {
warningWithoutStack(test, 'Oh no');
}
`,
},
{
code: `
if (__DEV__) {
lowPriorityWarning(test, 'Oh no');
}
`,
},
{
code: `
if (__DEV__) {
lowPriorityWarningWithoutStack(test, 'Oh no');
}
`,
},
// This is OK too because it's wrapped outside:
{
code: `
if (__DEV__) {
if (potato) {
while (true) {
warning(test, 'Oh no');
}
}
}`,
},
{
code: `
var f;
if (__DEV__) {
f = function() {
if (potato) {
while (true) {
warning(test, 'Oh no');
}
}
};
}`,
},
// Don't do anything with these:
{
code: 'normalFunctionCall(test);',
},
{
code: 'invariant(test);',
},
{
code: `
if (__DEV__) {
normalFunctionCall(test);
}
`,
},
// This is OK because of the outer if.
{
code: `
if (__DEV__) {
if (foo) {
if (__DEV__) {
} else {
warning(test, 'Oh no');
}
}
}`,
},
],
invalid: [
{
code: 'warning(test);',
errors: [
{
message: `Wrap warning() in an "if (__DEV__) {}" check`,
},
],
},
{
code: 'warningWithoutStack(test)',
errors: [
{
message: `Wrap warningWithoutStack() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (potato) {
warningWithoutStack(test);
}
`,
errors: [
{
message: `Wrap warningWithoutStack() in an "if (__DEV__) {}" check`,
},
],
},
{
code: 'lowPriorityWarning(test);',
errors: [
{
message: `Wrap lowPriorityWarning() in an "if (__DEV__) {}" check`,
},
],
},
{
code: 'lowPriorityWarningWithoutStack(test)',
errors: [
{
message: `Wrap lowPriorityWarningWithoutStack() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (potato) {
lowPriorityWarningWithoutStack(test);
}
`,
errors: [
{
message: `Wrap lowPriorityWarningWithoutStack() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (__DEV__ || potato && true) {
warning(test);
}
`,
errors: [
{
message: `Wrap warning() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (banana && __DEV__ && potato && kitten) {
warning(test);
}
`,
// Technically this code is valid but we prefer
// explicit standalone __DEV__ blocks that stand out.
errors: [
{
message: `Wrap warning() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (!__DEV__) {
warning(test);
}
`,
errors: [
{
message: `Wrap warning() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (foo || x && __DEV__) {
warning(test);
}
`,
errors: [
{
message: `Wrap warning() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (__DEV__) {
} else {
warning(test);
}
`,
errors: [
{
message: `Wrap warning() in an "if (__DEV__) {}" check`,
},
],
},
{
code: `
if (__DEV__) {
} else {
if (__DEV__) {
} else {
warning(test);
}
}
`,
errors: [
{
message: `Wrap warning() in an "if (__DEV__) {}" check`,
},
],
},
],
});

View File

@ -5,5 +5,6 @@ module.exports = {
'no-primitive-constructors': require('./no-primitive-constructors'), 'no-primitive-constructors': require('./no-primitive-constructors'),
'no-to-warn-dev-within-to-throw': require('./no-to-warn-dev-within-to-throw'), 'no-to-warn-dev-within-to-throw': require('./no-to-warn-dev-within-to-throw'),
'warning-and-invariant-args': require('./warning-and-invariant-args'), 'warning-and-invariant-args': require('./warning-and-invariant-args'),
'no-production-logging': require('./no-production-logging'),
}, },
}; };

View File

@ -0,0 +1,72 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
const LOGGER_FN_NAMES = [
'warning',
'warningWithoutStack',
'lowPriorityWarning',
'lowPriorityWarningWithoutStack',
];
module.exports = function(context) {
function isInDEVBlock(node) {
let done = false;
while (!done) {
let parent = node.parent;
if (!parent) {
return false;
}
if (
parent.type === 'IfStatement' &&
node === parent.consequent &&
parent.test.type === 'Identifier' &&
// This is intentionally strict so we can
// see blocks of DEV-only code at once.
parent.test.name === '__DEV__'
) {
return true;
}
node = parent;
}
}
function report(node) {
context.report({
node: node,
message: `Wrap {{identifier}}() in an "if (__DEV__) {}" check`,
data: {
identifier: node.callee.name,
},
fix: function(fixer) {
return [
fixer.insertTextBefore(node.parent, `if (__DEV__) {`),
fixer.insertTextAfter(node.parent, '}'),
];
},
});
}
const isLoggerFunctionName = name => LOGGER_FN_NAMES.includes(name);
return {
meta: {
fixable: 'code',
},
CallExpression: function(node) {
if (!isLoggerFunctionName(node.callee.name)) {
return;
}
if (!isInDEVBlock(node)) {
report(node);
}
},
};
};

View File

@ -17,7 +17,7 @@ const pathToBabelPluginDevWithCode = require.resolve(
'../error-codes/transform-error-messages' '../error-codes/transform-error-messages'
); );
const pathToBabelPluginWrapWarning = require.resolve( const pathToBabelPluginWrapWarning = require.resolve(
'../babel/wrap-warning-with-env-check' '../babel/lift-warning-conditional-argument'
); );
const pathToBabelPluginAsyncToGenerator = require.resolve( const pathToBabelPluginAsyncToGenerator = require.resolve(
'@babel/plugin-transform-async-to-generator' '@babel/plugin-transform-async-to-generator'

View File

@ -125,7 +125,7 @@ function getBabelConfig(updateBabelOptions, bundleType, filename) {
// Minify invariant messages // Minify invariant messages
require('../error-codes/transform-error-messages'), require('../error-codes/transform-error-messages'),
// Wrap warning() calls in a __DEV__ check so they are stripped from production. // Wrap warning() calls in a __DEV__ check so they are stripped from production.
require('../babel/wrap-warning-with-env-check'), require('../babel/lift-warning-conditional-argument'),
]), ]),
}); });
case RN_OSS_DEV: case RN_OSS_DEV:
@ -142,7 +142,7 @@ function getBabelConfig(updateBabelOptions, bundleType, filename) {
{noMinify: true}, {noMinify: true},
], ],
// Wrap warning() calls in a __DEV__ check so they are stripped from production. // Wrap warning() calls in a __DEV__ check so they are stripped from production.
require('../babel/wrap-warning-with-env-check'), require('../babel/lift-warning-conditional-argument'),
]), ]),
}); });
case UMD_DEV: case UMD_DEV:
@ -158,7 +158,7 @@ function getBabelConfig(updateBabelOptions, bundleType, filename) {
// Minify invariant messages // Minify invariant messages
require('../error-codes/transform-error-messages'), require('../error-codes/transform-error-messages'),
// Wrap warning() calls in a __DEV__ check so they are stripped from production. // Wrap warning() calls in a __DEV__ check so they are stripped from production.
require('../babel/wrap-warning-with-env-check'), require('../babel/lift-warning-conditional-argument'),
]), ]),
}); });
default: default: