@samitouri / QOS-React-2 / commits / 75955bf1d7

Pass prod error messages directly to constructor (#17063)

* Remove "Invariant Violation" from dev errors When I made the change to compile `invariant` to throw expressions, I left a small runtime to set the error's `name` property to "Invariant Violation" to maintain the existing behavior. I think we can remove it. The argument for keeping it is to preserve continuity in error logs, but this only affects development errors, anyway: production error messages are replaced with error codes. * Pass prod error messages directly to constructor Updates the `invariant` transform to pass an error message string directly to the Error constructor, instead of mutating the message property. Turns this code: ```js invariant(condition, 'A %s message that contains %s', adj, noun); ``` into this: ```js if (!condition) { throw Error( __DEV__ ? `A ${adj} message that contains ${noun}` : formatProdErrorMessage(ERR_CODE, adj, noun) ); } ```

Andrew Clark committed Oct 11, 2019 at 09:10 UTC 75955bf1d7ff6c2c1f4052f4a84dd2ce6944c62e
7 files changed +81 -131
packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
+1 -1
@@ -40,7 +40,7 @@ describe('ReactNativeError', () => {
40 throw new Error(e.toString());
41 }
42 }).toThrow(
43 - 'Invariant Violation: View config getter callback for component `View` must be a function (received `null`)',
43 + 'View config getter callback for component `View` must be a function (received `null`)',
44 );
45 });
46
packages/shared/ReactError.js deleted
-18
@@ -1,18 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - */
8 -
9 -// Do not require this module directly! Use normal `invariant` calls with
10 -// template literal strings. The messages will be converted to ReactError during
11 -// build, and in production they will be minified.
12 -
13 -function ReactError(error) {
14 - error.name = 'Invariant Violation';
15 - return error;
16 -}
17 -
18 -export default ReactError;
packages/shared/__tests__/ReactError-test.internal.js
+1 -9
@@ -37,15 +37,7 @@ describe('ReactError', () => {
37 });
38
39 if (__DEV__) {
40 - it('should throw errors whose name is "Invariant Violation"', () => {
41 - let error;
42 - try {
43 - React.useState();
44 - } catch (e) {
45 - error = e;
46 - }
47 - expect(error.name).toEqual('Invariant Violation');
48 - });
40 + it("empty test so Jest doesn't complain", () => {});
41 } else {
42 it('should error with minified error code', () => {
43 expect(() => ReactDOM.render('Hi', null)).toThrowError(
packages/shared/__tests__/ReactErrorProd-test.internal.js
+5 -11
@@ -8,7 +8,7 @@
8 */
9 'use strict';
10
11 -let ReactErrorProd;
11 +let formatProdErrorMessage;
12
13 describe('ReactErrorProd', () => {
14 let globalErrorMock;
@@ -25,7 +25,7 @@ describe('ReactErrorProd', () => {
25 expect(typeof global.Error).toBe('function');
26 }
27 jest.resetModules();
28 - ReactErrorProd = require('shared/ReactErrorProd').default;
28 + formatProdErrorMessage = require('shared/formatProdErrorMessage').default;
29 });
30
31 afterEach(() => {
@@ -35,27 +35,21 @@ describe('ReactErrorProd', () => {
35 });
36
37 it('should throw with the correct number of `%s`s in the URL', () => {
38 - expect(function() {
39 - throw ReactErrorProd(Error(124), 'foo', 'bar');
40 - }).toThrowError(
38 + expect(formatProdErrorMessage(124, 'foo', 'bar')).toEqual(
39 'Minified React error #124; visit ' +
40 'https://reactjs.org/docs/error-decoder.html?invariant=124&args[]=foo&args[]=bar' +
41 ' for the full message or use the non-minified dev environment' +
42 ' for full errors and additional helpful warnings.',
43 );
44
47 - expect(function() {
48 - throw ReactErrorProd(Error(20));
49 - }).toThrowError(
45 + expect(formatProdErrorMessage(20)).toEqual(
46 'Minified React error #20; visit ' +
47 'https://reactjs.org/docs/error-decoder.html?invariant=20' +
48 ' for the full message or use the non-minified dev environment' +
49 ' for full errors and additional helpful warnings.',
50 );
51
56 - expect(function() {
57 - throw ReactErrorProd(Error(77), '<div>', '&?bar');
58 - }).toThrowError(
52 + expect(formatProdErrorMessage(77, '<div>', '&?bar')).toEqual(
53 'Minified React error #77; visit ' +
54 'https://reactjs.org/docs/error-decoder.html?invariant=77&args[]=%3Cdiv%3E&args[]=%26%3Fbar' +
55 ' for the full message or use the non-minified dev environment' +
packages/shared/formatProdErrorMessage.js renamed
+7 -8
@@ -7,20 +7,19 @@
7 */
8
9 // Do not require this module directly! Use normal `invariant` calls with
10 -// template literal strings. The messages will be converted to ReactError during
11 -// build, and in production they will be minified.
10 +// template literal strings. The messages will be replaced with error codes
11 +// during build.
12
13 -function ReactErrorProd(error) {
14 - const code = error.message;
13 +function formatProdErrorMessage(code) {
14 let url = 'https://reactjs.org/docs/error-decoder.html?invariant=' + code;
15 for (let i = 1; i < arguments.length; i++) {
16 url += '&args[]=' + encodeURIComponent(arguments[i]);
17 }
19 - error.message =
18 + return (
19 `Minified React error #${code}; visit ${url} for the full message or ` +
20 'use the non-minified dev environment for full errors and additional ' +
22 - 'helpful warnings. ';
23 - return error;
21 + 'helpful warnings.'
22 + );
23 }
24
26 -export default ReactErrorProd;
25 +export default formatProdErrorMessage;
scripts/error-codes/__tests__/__snapshots__/transform-error-messages.js.snap
+19 -35
@@ -1,76 +1,60 @@
1 // Jest Snapshot v1, https://goo.gl/fbAQLP
2
3 exports[`error transform should correctly transform invariants that are not in the error codes map 1`] = `
4 -"import _ReactError from \\"shared/ReactError\\";
5 -import invariant from 'shared/invariant';
4 +"import invariant from 'shared/invariant';
5
6 /*FIXME (minify-errors-in-prod): Unminified error message in production build!*/
8 -(function () {
9 - if (!condition) {
10 - throw _ReactError(Error(\\"This is not a real error message.\\"));
11 - }
12 -})();"
7 +if (!condition) {
8 + throw Error(\\"This is not a real error message.\\");
9 +}"
10 `;
11
12 exports[`error transform should handle escaped characters 1`] = `
16 -"import _ReactError from \\"shared/ReactError\\";
17 -import invariant from 'shared/invariant';
13 +"import invariant from 'shared/invariant';
14
15 /*FIXME (minify-errors-in-prod): Unminified error message in production build!*/
20 -(function () {
21 - if (!condition) {
22 - throw _ReactError(Error(\\"What's up?\\"));
23 - }
24 -})();"
16 +if (!condition) {
17 + throw Error(\\"What's up?\\");
18 +}"
19 `;
20
21 exports[`error transform should replace simple invariant calls 1`] = `
28 -"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
29 -import _ReactError from \\"shared/ReactError\\";
22 +"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
23 import invariant from 'shared/invariant';
24
25 if (!condition) {
33 - if (__DEV__) {
34 - throw _ReactError(Error(\\"Do not override existing functions.\\"));
35 - } else {
36 - throw _ReactErrorProd(Error(16));
26 + {
27 + throw Error(__DEV__ ? \\"Do not override existing functions.\\" : _formatProdErrorMessage(16));
28 }
29 }"
30 `;
31
32 exports[`error transform should support invariant calls with a concatenated template string and args 1`] = `
42 -"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
43 -import _ReactError from \\"shared/ReactError\\";
33 +"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
34 import invariant from 'shared/invariant';
35
36 if (!condition) {
47 - if (__DEV__) {
48 - throw _ReactError(Error(\\"Expected a component class, got \\" + Foo + \\".\\" + Bar));
49 - } else {
50 - throw _ReactErrorProd(Error(18), Foo, Bar);
37 + {
38 + throw Error(__DEV__ ? \\"Expected a component class, got \\" + Foo + \\".\\" + Bar : _formatProdErrorMessage(18, Foo, Bar));
39 }
40 }"
41 `;
42
43 exports[`error transform should support invariant calls with args 1`] = `
56 -"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
57 -import _ReactError from \\"shared/ReactError\\";
44 +"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
45 import invariant from 'shared/invariant';
46
47 if (!condition) {
61 - if (__DEV__) {
62 - throw _ReactError(Error(\\"Expected \\" + foo + \\" target to be an array; got \\" + bar));
63 - } else {
64 - throw _ReactErrorProd(Error(7), foo, bar);
48 + {
49 + throw Error(__DEV__ ? \\"Expected \\" + foo + \\" target to be an array; got \\" + bar : _formatProdErrorMessage(7, foo, bar));
50 }
51 }"
52 `;
53
54 exports[`error transform should support noMinify option 1`] = `
70 -"import _ReactError from \\"shared/ReactError\\";
71 -import invariant from 'shared/invariant';
55 +"import invariant from 'shared/invariant';
56
57 if (!condition) {
74 - throw _ReactError(Error(\\"Do not override existing functions.\\"));
58 + throw Error(\\"Do not override existing functions.\\");
59 }"
60 `;
scripts/error-codes/transform-error-messages.js
+48 -49
@@ -29,11 +29,11 @@ module.exports = function(babel) {
29 // into this:
30 //
31 // if (!condition) {
32 - // if (__DEV__) {
33 - // throw ReactError(Error(`A ${adj} message that contains ${noun}`));
34 - // } else {
35 - // throw ReactErrorProd(Error(ERR_CODE), adj, noun);
36 - // }
32 + // throw Error(
33 + // __DEV__
34 + // ? `A ${adj} message that contains ${noun}`
35 + // : formatProdErrorMessage(ERR_CODE, adj, noun)
36 + // );
37 // }
38 //
39 // where ERR_CODE is an error code: a unique identifier (a number
@@ -46,22 +46,11 @@ module.exports = function(babel) {
46 .split('%s')
47 .map(raw => t.templateElement({raw, cooked: String.raw({raw})}));
48
49 - const reactErrorIdentfier = helperModuleImports.addDefault(
50 - path,
51 - 'shared/ReactError',
52 - {
53 - nameHint: 'ReactError',
54 - }
55 - );
56 -
49 // Outputs:
58 - // throw ReactError(Error(`A ${adj} message that contains ${noun}`));
59 - const devThrow = t.throwStatement(
60 - t.callExpression(reactErrorIdentfier, [
61 - t.callExpression(t.identifier('Error'), [
62 - t.templateLiteral(errorMsgQuasis, errorMsgExpressions),
63 - ]),
64 - ])
50 + // `A ${adj} message that contains ${noun}`;
51 + const devMessage = t.templateLiteral(
52 + errorMsgQuasis,
53 + errorMsgExpressions
54 );
55
56 const parentStatementPath = path.parentPath;
@@ -77,12 +66,16 @@ module.exports = function(babel) {
66 //
67 // Outputs:
68 // if (!condition) {
80 - // throw ReactError(Error(`A ${adj} message that contains ${noun}`));
69 + // throw Error(`A ${adj} message that contains ${noun}`);
70 // }
71 parentStatementPath.replaceWith(
72 t.ifStatement(
73 t.unaryExpression('!', condition),
85 - t.blockStatement([devThrow])
74 + t.blockStatement([
75 + t.throwStatement(
76 + t.callExpression(t.identifier('Error'), [devMessage])
77 + ),
78 + ])
79 )
80 );
81 return;
@@ -104,15 +97,19 @@ module.exports = function(babel) {
97 // Outputs:
98 // /* FIXME (minify-errors-in-prod): Unminified error message in production build! */
99 // if (!condition) {
107 - // throw ReactError(Error(`A ${adj} message that contains ${noun}`));
100 + // throw Error(`A ${adj} message that contains ${noun}`);
101 // }
109 - path.replaceWith(
102 + parentStatementPath.replaceWith(
103 t.ifStatement(
104 t.unaryExpression('!', condition),
112 - t.blockStatement([devThrow])
105 + t.blockStatement([
106 + t.throwStatement(
107 + t.callExpression(t.identifier('Error'), [devMessage])
108 + ),
109 + ])
110 )
111 );
115 - path.addComment(
112 + parentStatementPath.addComment(
113 'leading',
114 'FIXME (minify-errors-in-prod): Unminified error message in production build!'
115 );
@@ -121,40 +118,42 @@ module.exports = function(babel) {
118 prodErrorId = parseInt(prodErrorId, 10);
119
120 // Import ReactErrorProd
124 - const reactErrorProdIdentfier = helperModuleImports.addDefault(
121 + const formatProdErrorMessageIdentifier = helperModuleImports.addDefault(
122 path,
126 - 'shared/ReactErrorProd',
127 - {nameHint: 'ReactErrorProd'}
123 + 'shared/formatProdErrorMessage',
124 + {nameHint: 'formatProdErrorMessage'}
125 );
126
127 // Outputs:
131 - // throw ReactErrorProd(Error(ERR_CODE), adj, noun);
132 - const prodThrow = t.throwStatement(
133 - t.callExpression(reactErrorProdIdentfier, [
134 - t.callExpression(t.identifier('Error'), [
135 - t.numericLiteral(prodErrorId),
136 - ]),
137 - ...errorMsgExpressions,
138 - ])
128 + // formatProdErrorMessage(ERR_CODE, adj, noun);
129 + const prodMessage = t.callExpression(
130 + formatProdErrorMessageIdentifier,
131 + [t.numericLiteral(prodErrorId), ...errorMsgExpressions]
132 );
133
134 // Outputs:
142 - // if (!condition) {
143 - // if (__DEV__) {
144 - // throw ReactError(Error(`A ${adj} message that contains ${noun}`));
145 - // } else {
146 - // throw ReactErrorProd(Error(ERR_CODE), adj, noun);
147 - // }
148 - // }
135 + // if (!condition) {
136 + // throw Error(
137 + // __DEV__
138 + // ? `A ${adj} message that contains ${noun}`
139 + // : formatProdErrorMessage(ERR_CODE, adj, noun)
140 + // );
141 + // }
142 parentStatementPath.replaceWith(
143 t.ifStatement(
144 t.unaryExpression('!', condition),
145 t.blockStatement([
153 - t.ifStatement(
154 - DEV_EXPRESSION,
155 - t.blockStatement([devThrow]),
156 - t.blockStatement([prodThrow])
157 - ),
146 + t.blockStatement([
147 + t.throwStatement(
148 + t.callExpression(t.identifier('Error'), [
149 + t.conditionalExpression(
150 + DEV_EXPRESSION,
151 + devMessage,
152 + prodMessage
153 + ),
154 + ])
155 + ),
156 + ]),
157 ])
158 )
159 );