From fb7e49439feecd401029f24bf401f22b235c3947 Mon Sep 17 00:00:00 2001 From: Jirat Ki Date: Mon, 9 Jan 2017 22:26:01 +0700 Subject: [PATCH] Add component stack to invalid element type warning (#8495) * Show Source Error Addemden if __source available * Add Parent Stack on invalid element type * refactor to use normalizeCodeLocInfo * Remove ( ) from addendum --- .../classic/element/ReactElementValidator.js | 25 +++++++++- .../__tests__/ReactElementValidator-test.js | 9 ++-- .../ReactJSXElementValidator-test.js | 48 +++++++++---------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index ef024c7bf9811..f3910590dca5f 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -39,6 +39,20 @@ function getDeclarationErrorAddendum() { return ''; } +function getSourceInfoErrorAddendum(elementProps) { + if ( + elementProps !== null && + elementProps !== undefined && + elementProps.__source !== undefined + ) { + var source = elementProps.__source; + var fileName = source.fileName.replace(/^.*[\\\/]/, ''); + var lineNumber = source.lineNumber; + return ' Check your code at ' + fileName + ':' + lineNumber + '.'; + } + return ''; +} + /** * Warn if there's no key explicitly set on dynamic arrays of children or * object keys are not valid. This allows us to keep track of children between @@ -206,7 +220,16 @@ var ReactElementValidator = { ' You likely forgot to export your component from the file ' + 'it\'s defined in.'; } - info += getDeclarationErrorAddendum(); + + var sourceInfo = getSourceInfoErrorAddendum(props); + if (sourceInfo) { + info += sourceInfo; + } else { + info += getDeclarationErrorAddendum(); + } + + info += ReactComponentTreeHook.getCurrentStackAddendum(); + warning( false, 'React.createElement: type is invalid -- expected a string (for ' + diff --git a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js index ff36bec706c82..4347b8b78b0a1 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js @@ -20,7 +20,7 @@ var ReactTestUtils; describe('ReactElementValidator', () => { function normalizeCodeLocInfo(str) { - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); + return str && str.replace(/at .+?:\d+/g, 'at **'); } var ComponentClass; @@ -349,7 +349,8 @@ describe('ReactElementValidator', () => { expectDev(console.error.calls.argsFor(0)[0]).toBe( 'Warning: React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + - 'components) but got: null. Check the render method of `ParentComp`.' + 'components) but got: null. Check the render method of `ParentComp`.' + + '\n in ParentComp' ); }); @@ -546,11 +547,11 @@ describe('ReactElementValidator', () => { var Foo = undefined; void {[
]}; expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: undefined. You likely forgot to export your ' + - 'component from the file it\'s defined in.' + 'component from the file it\'s defined in. Check your code at **.' ); }); diff --git a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js index f0c47c9179729..5819b2fa9562c 100644 --- a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js +++ b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js @@ -19,6 +19,10 @@ var ReactDOM; var ReactTestUtils; describe('ReactJSXElementValidator', () => { + function normalizeCodeLocInfo(str) { + return str && str.replace(/at .+?:\d+/g, 'at **'); + } + var Component; var RequiredPropComponent; @@ -194,9 +198,7 @@ describe('ReactJSXElementValidator', () => { } } ReactTestUtils.renderIntoDocument(); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)') - ).toBe( + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Failed prop type: ' + 'Invalid prop `color` of type `number` supplied to `MyComp`, ' + 'expected `string`.\n' + @@ -232,9 +234,7 @@ describe('ReactJSXElementValidator', () => { expect(console.error.calls.count()).toBe(1); // The warning should have the full stack with line numbers. // If it doesn't, it means we're using information from the old element. - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)') - ).toBe( + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Failed prop type: ' + 'Invalid prop `color` of type `number` supplied to `MyComp`, ' + 'expected `string`.\n' + @@ -256,26 +256,30 @@ describe('ReactJSXElementValidator', () => { void ; void ; expectDev(console.error.calls.count()).toBe(4); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + 'components) but got: undefined. You likely forgot to export your ' + - 'component from the file it\'s defined in.' + 'component from the file it\'s defined in. ' + + 'Check your code at **.' ); - expectDev(console.error.calls.argsFor(1)[0]).toBe( + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( 'Warning: React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + - 'components) but got: null.' + 'components) but got: null. ' + + 'Check your code at **.' ); - expectDev(console.error.calls.argsFor(2)[0]).toBe( + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe( 'Warning: React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + - 'components) but got: boolean.' + 'components) but got: boolean. ' + + 'Check your code at **.' ); - expectDev(console.error.calls.argsFor(3)[0]).toBe( + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(3)[0])).toBe( 'Warning: React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function (for composite ' + - 'components) but got: number.' + 'components) but got: number. ' + + 'Check your code at **.' ); void
; expectDev(console.error.calls.count()).toBe(4); @@ -289,9 +293,7 @@ describe('ReactJSXElementValidator', () => { ReactTestUtils.renderIntoDocument(); expectDev(console.error.calls.count()).toBe(1); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)') - ).toBe( + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Failed prop type: The prop `prop` is marked as required in ' + '`RequiredPropComponent`, but its value is `null`.\n' + ' in RequiredPropComponent (at **)' @@ -304,9 +306,7 @@ describe('ReactJSXElementValidator', () => { ReactTestUtils.renderIntoDocument(); expectDev(console.error.calls.count()).toBe(1); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)') - ).toBe( + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Failed prop type: The prop `prop` is marked as required in ' + '`RequiredPropComponent`, but its value is `null`.\n' + ' in RequiredPropComponent (at **)' @@ -320,18 +320,14 @@ describe('ReactJSXElementValidator', () => { ReactTestUtils.renderIntoDocument(); expectDev(console.error.calls.count()).toBe(2); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)') - ).toBe( + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Failed prop type: ' + 'The prop `prop` is marked as required in `RequiredPropComponent`, but ' + 'its value is `undefined`.\n' + ' in RequiredPropComponent (at **)' ); - expect( - console.error.calls.argsFor(1)[0].replace(/\(at .+?:\d+\)/g, '(at **)') - ).toBe( + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( 'Warning: Failed prop type: ' + 'Invalid prop `prop` of type `number` supplied to ' + '`RequiredPropComponent`, expected `string`.\n' +