From dfa38c2bc4088ac6062659d33032a213f1eb1ae5 Mon Sep 17 00:00:00 2001 From: Caleb Meredith Date: Wed, 29 Aug 2018 17:51:49 -0700 Subject: [PATCH] Add basic support for throws in React (#2502) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Starts adding basic support for throws in React. Concretely there are three things this PR does outside of adding tests: 1. Allowing throw side-effects. 2. Removing an invalid invariant. `createdObjects` changes after calling `realm.captureEffects()` and this is expected. Later code which joins/incorporates effects will merge in the captured `createdObjects`. 3. Don’t catch `AbruptCompletion`s and handle them as errors. Instead let them propagate up to the nearest `realm.evaluateForEffects()`. (Or similar function.) I have not run this against the internal web bundle yet. Against the internal React Native bundle we get pretty far without removing throws with these changes. Pull Request resolved: https://github.com/facebook/prepack/pull/2502 Reviewed By: trueadm Differential Revision: D9566580 Pulled By: calebmer fbshipit-source-id: 3716a6afd5fc3ae824182ee50e38e51d72126dc2 --- src/completions.js | 6 - src/react/reconcilation.js | 34 +-- src/react/utils.js | 3 - .../ModifiedObjectPropertyLimitation.js | 2 +- test/error-handler/bad-functions.js | 2 +- test/react/FBMocks/fb16.js | 1 + test/react/FunctionalComponents-test.js | 2 +- test/react/FunctionalComponents/simple-13.js | 2 +- test/react/Throw-test.js | 23 ++ test/react/Throw/throw-conditional.js | 15 + test/react/Throw/throw.js | 19 ++ test/react/__snapshots__/FBMocks-test.js.snap | 284 +++++++++++++++++- .../FunctionalComponents-test.js.snap | 180 ++++++++++- test/react/__snapshots__/Throw-test.js.snap | 137 +++++++++ test/react/setupReactTests.js | 18 +- .../EmitPropertyRegressionTest.js | 2 +- .../Issue1821RegressionTest.js | 2 +- .../ModifiedObjectProperty.js | 2 +- .../RegressionTestForIssue1881.js | 2 +- 19 files changed, 666 insertions(+), 70 deletions(-) create mode 100644 test/react/Throw-test.js create mode 100644 test/react/Throw/throw-conditional.js create mode 100644 test/react/Throw/throw.js create mode 100644 test/react/__snapshots__/Throw-test.js.snap diff --git a/src/completions.js b/src/completions.js index 015dee1b43..690976bfe5 100644 --- a/src/completions.js +++ b/src/completions.js @@ -115,12 +115,6 @@ export class ThrowCompletion extends AbruptCompletion { constructor(value: Value, location: ?BabelNodeSourceLocation, nativeStack?: ?string) { super(value, location); this.nativeStack = nativeStack || new Error().stack; - let realm = value.$Realm; - if (realm.isInPureScope()) { - for (let callback of realm.reportSideEffectCallbacks) { - callback("EXCEPTION_THROWN", undefined, location); - } - } } nativeStack: string; diff --git a/src/react/reconcilation.js b/src/react/reconcilation.js index 401012ef3f..e51432db7d 100644 --- a/src/react/reconcilation.js +++ b/src/react/reconcilation.js @@ -54,7 +54,7 @@ import { getValueWithBranchingLogicApplied, wrapReactElementInBranchOrReturnValue, } from "./branching.js"; -import { Completion, SimpleNormalCompletion } from "../completions.js"; +import { AbruptCompletion, SimpleNormalCompletion } from "../completions.js"; import { getInitialProps, getInitialContext, @@ -179,6 +179,7 @@ export class Reconciler { this.statistics.optimizedTrees++; return result; } catch (error) { + if (error instanceof AbruptCompletion) throw error; this._handleComponentTreeRootFailure(error, evaluatedRootNode); // flow belives we can get here, when it should never be possible invariant(false, "resolveReactComponentTree error not handled correctly"); @@ -1224,6 +1225,7 @@ export class Reconciler { return result; } catch (error) { + if (error instanceof AbruptCompletion) throw error; return this._resolveComponentResolutionFailure( componentType, error, @@ -1235,7 +1237,7 @@ export class Reconciler { } } - _handleComponentTreeRootFailure(error: Error | Completion, evaluatedRootNode: ReactEvaluatedNode): void { + _handleComponentTreeRootFailure(error: Error, evaluatedRootNode: ReactEvaluatedNode): void { if (error.name === "Invariant Violation") { throw error; } else if (error instanceof ReconcilerFatalError) { @@ -1245,19 +1247,6 @@ export class Reconciler { `Failed to render React component root "${evaluatedRootNode.name}" due to ${error.message}`, evaluatedRootNode ); - } else if (error instanceof Completion) { - let value = error.value; - invariant(value instanceof ObjectValue); - let message = getProperty(this.realm, value, "message"); - let stack = getProperty(this.realm, value, "stack"); - invariant(message instanceof StringValue); - invariant(stack instanceof StringValue); - throw new ReconcilerFatalError( - `Failed to render React component "${evaluatedRootNode.name}" due to a JS error: ${message.value}\n${ - stack.value - }`, - evaluatedRootNode - ); } let message; if (error instanceof ExpectedBailOut) { @@ -1277,7 +1266,7 @@ export class Reconciler { _resolveComponentResolutionFailure( componentType: Value, - error: Error | Completion, + error: Error, reactElement: ObjectValue, context: ObjectValue | AbstractObjectValue, evaluatedNode: ReactEvaluatedNode, @@ -1294,17 +1283,6 @@ export class Reconciler { ); } else if (error instanceof DoNotOptimize) { return reactElement; - } else if (error instanceof Completion) { - let value = error.value; - invariant(value instanceof ObjectValue); - let message = getProperty(this.realm, value, "message"); - let stack = getProperty(this.realm, value, "stack"); - invariant(message instanceof StringValue); - invariant(stack instanceof StringValue); - throw new ReconcilerFatalError( - `Failed to render React component "${evaluatedNode.name}" due to a JS error: ${message.value}\n${stack.value}`, - evaluatedNode - ); } let typeValue = getProperty(this.realm, reactElement, "type"); let propsValue = getProperty(this.realm, reactElement, "props"); @@ -1418,6 +1396,8 @@ export class Reconciler { this.realm.applyEffects(effects); if (result instanceof SimpleNormalCompletion) { result = result.value; + } else { + invariant(false, "TODO support other types of completion"); } invariant(result instanceof Value); return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, needsKey); diff --git a/src/react/utils.js b/src/react/utils.js index 9d1195d308..e80db65da3 100644 --- a/src/react/utils.js +++ b/src/react/utils.js @@ -798,7 +798,6 @@ export function getValueFromFunctionCall( let funcCall = func.$Call; let newCall = func.$Construct; let completion; - let createdObjects = realm.createdObjects; try { let value; if (isConstructor) { @@ -814,8 +813,6 @@ export function getValueFromFunctionCall( } else { throw error; } - } finally { - invariant(createdObjects === realm.createdObjects, "realm.createdObjects was not correctly restored"); } return realm.returnOrThrowCompletion(completion); } diff --git a/test/error-handler/ModifiedObjectPropertyLimitation.js b/test/error-handler/ModifiedObjectPropertyLimitation.js index 011f098367..a9911b53aa 100644 --- a/test/error-handler/ModifiedObjectPropertyLimitation.js +++ b/test/error-handler/ModifiedObjectPropertyLimitation.js @@ -1,5 +1,5 @@ // recover-from-errors -// expected errors: [{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "}] +// expected errors: [{"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "}] (function() { let p = {}; function f(c) { diff --git a/test/error-handler/bad-functions.js b/test/error-handler/bad-functions.js index f4e107e454..fa0c9939e1 100644 --- a/test/error-handler/bad-functions.js +++ b/test/error-handler/bad-functions.js @@ -1,5 +1,5 @@ // recover-from-errors -// expected errors: [{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"location":{"start":{"line":12,"column":13},"end":{"line":12,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"},{"location":{"start":{"line":8,"column":13},"end":{"line":8,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"}] +// expected errors: [{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"location":{"start":{"line":12,"column":13},"end":{"line":12,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"},{"location":{"start":{"line":8,"column":13},"end":{"line":8,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"}] var wildcard = global.__abstract ? global.__abstract("number", "123") : 123; global.a = ""; diff --git a/test/react/FBMocks/fb16.js b/test/react/FBMocks/fb16.js index c4183a306d..2482d124cc 100644 --- a/test/react/FBMocks/fb16.js +++ b/test/react/FBMocks/fb16.js @@ -73,6 +73,7 @@ __evaluatePureFunction(function() { function ViewCount(props) { return React.createElement( "div", + null, fbt._({ "*": "{count} Views", _1: "{count} View" }, [ fbt._param("count", props.feedback.viewCountReduced), fbt._plural(props.feedback.viewCount), diff --git a/test/react/FunctionalComponents-test.js b/test/react/FunctionalComponents-test.js index 3ebc714ed5..34c79b6bf5 100644 --- a/test/react/FunctionalComponents-test.js +++ b/test/react/FunctionalComponents-test.js @@ -74,7 +74,7 @@ it("Simple 12", () => { it("Runtime error", () => { runTest(__dirname + "/FunctionalComponents/runtime-error.js", { - expectReconcilerError: true, + expectRuntimeError: true, }); }); diff --git a/test/react/FunctionalComponents/simple-13.js b/test/react/FunctionalComponents/simple-13.js index c5a56d0606..6528da5c73 100644 --- a/test/react/FunctionalComponents/simple-13.js +++ b/test/react/FunctionalComponents/simple-13.js @@ -27,7 +27,7 @@ function Child(props) { return children(); } -__optimizeReactComponentTree(App); +if (this.__optimizeReactComponentTree) __optimizeReactComponentTree(App); App.getTrials = function(renderer, Root) { // Just compile, don't run diff --git a/test/react/Throw-test.js b/test/react/Throw-test.js new file mode 100644 index 0000000000..b55ed4c9c7 --- /dev/null +++ b/test/react/Throw-test.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* @flow */ + +const path = require("path"); +const fs = require("fs"); +const setupReactTests = require("./setupReactTests"); +const { runTest } = setupReactTests(); + +const customConfig = new Map(); + +fs.readdirSync(path.resolve(__dirname, "Throw")).forEach(file => { + test(file, () => { + runTest(path.resolve(__dirname, "Throw", file), customConfig.get(file)); + }); +}); diff --git a/test/react/Throw/throw-conditional.js b/test/react/Throw/throw-conditional.js new file mode 100644 index 0000000000..2ad81ea575 --- /dev/null +++ b/test/react/Throw/throw-conditional.js @@ -0,0 +1,15 @@ +const React = require("react"); + +function MyComponent(props) { + if (props.b) throw new Error("abrupt"); + return 42; +} + +if (global.__optimizeReactComponentTree) global.__optimizeReactComponentTree(MyComponent); + +MyComponent.getTrials = renderer => { + renderer.update(); + return [["simple render", renderer.toJSON()]]; +}; + +module.exports = MyComponent; diff --git a/test/react/Throw/throw.js b/test/react/Throw/throw.js new file mode 100644 index 0000000000..c7624623c3 --- /dev/null +++ b/test/react/Throw/throw.js @@ -0,0 +1,19 @@ +const React = require("react"); + +function MyComponent() { + throw new Error("abrupt"); +} + +if (global.__optimizeReactComponentTree) global.__optimizeReactComponentTree(MyComponent); + +MyComponent.getTrials = renderer => { + let error = false; + try { + MyComponent({}); + } catch (error) { + error = true; + } + return [["component errors", error]]; +}; + +module.exports = MyComponent; diff --git a/test/react/__snapshots__/FBMocks-test.js.snap b/test/react/__snapshots__/FBMocks-test.js.snap index 7ff307719c..4ca52427ff 100644 --- a/test/react/__snapshots__/FBMocks-test.js.snap +++ b/test/react/__snapshots__/FBMocks-test.js.snap @@ -1560,13 +1560,153 @@ ReactStatistics { } `; -exports[`fb-www 12: (JSX => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 12: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 4, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "B", + "status": "INLINED", + }, + Object { + "children": Array [], + "message": "", + "name": "C", + "status": "INLINED", + }, + ], + "message": "", + "name": "A", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 3, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 12: (JSX => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 12: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 4, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "B", + "status": "INLINED", + }, + Object { + "children": Array [], + "message": "", + "name": "C", + "status": "INLINED", + }, + ], + "message": "", + "name": "A", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 3, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 12: (createElement => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 12: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 4, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "B", + "status": "INLINED", + }, + Object { + "children": Array [], + "message": "", + "name": "C", + "status": "INLINED", + }, + ], + "message": "", + "name": "A", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 3, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 12: (createElement => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 12: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 4, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "B", + "status": "INLINED", + }, + Object { + "children": Array [], + "message": "", + "name": "C", + "status": "INLINED", + }, + ], + "message": "", + "name": "A", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 3, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; exports[`fb-www 13: (JSX => JSX) 1`] = ` ReactStatistics { @@ -1900,21 +2040,141 @@ ReactStatistics { } `; -exports[`fb-www 18: (JSX => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 18: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 18: (JSX => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 18: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 18: (createElement => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 18: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 18: (createElement => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 18: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 19: (JSX => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 19: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 19: (JSX => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 19: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 19: (createElement => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 19: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`fb-www 19: (createElement => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`fb-www 19: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; exports[`fb-www 20: (JSX => JSX) 1`] = ` ReactStatistics { diff --git a/test/react/__snapshots__/FunctionalComponents-test.js.snap b/test/react/__snapshots__/FunctionalComponents-test.js.snap index abdd2fd7db..cf0bd315b9 100644 --- a/test/react/__snapshots__/FunctionalComponents-test.js.snap +++ b/test/react/__snapshots__/FunctionalComponents-test.js.snap @@ -9404,13 +9404,81 @@ ReactStatistics { } `; -exports[`Runtime error: (JSX => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Runtime error: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; + +exports[`Runtime error: (JSX => JSX) 2`] = `"Cannot read property 'push' of undefined"`; + +exports[`Runtime error: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; + +exports[`Runtime error: (JSX => createElement) 2`] = `"Cannot read property 'push' of undefined"`; + +exports[`Runtime error: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; -exports[`Runtime error: (JSX => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Runtime error: (createElement => JSX) 2`] = `"Cannot read property 'push' of undefined"`; -exports[`Runtime error: (createElement => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Runtime error: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; -exports[`Runtime error: (createElement => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Runtime error: (createElement => createElement) 2`] = `"Cannot read property 'push' of undefined"`; exports[`Simple 2: (JSX => JSX) 1`] = ` ReactStatistics { @@ -9956,13 +10024,13 @@ exports[`Simple 10: (createElement => JSX) 1`] = `"Failed to render React compon exports[`Simple 10: (createElement => createElement) 1`] = `"Failed to render React component \\"App\\" due to side-effects from mutating the binding \\"lazyVariable\\""`; -exports[`Simple 11: (JSX => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 11: (JSX => JSX) 1`] = `"Failed to render React component \\"App\\" due to side-effects from mutating the binding \\"lazyVariable\\""`; -exports[`Simple 11: (JSX => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 11: (JSX => createElement) 1`] = `"Failed to render React component \\"App\\" due to side-effects from mutating the binding \\"lazyVariable\\""`; -exports[`Simple 11: (createElement => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 11: (createElement => JSX) 1`] = `"Failed to render React component \\"App\\" due to side-effects from mutating the binding \\"lazyVariable\\""`; -exports[`Simple 11: (createElement => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 11: (createElement => createElement) 1`] = `"Failed to render React component \\"App\\" due to side-effects from mutating the binding \\"lazyVariable\\""`; exports[`Simple 12: (JSX => JSX) 1`] = ` ReactStatistics { @@ -10060,13 +10128,101 @@ ReactStatistics { } `; -exports[`Simple 13: (JSX => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 13: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 2, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "Child", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 1, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`Simple 13: (JSX => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 13: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 2, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "Child", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 1, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`Simple 13: (createElement => JSX) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 13: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 2, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "Child", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 1, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; -exports[`Simple 13: (createElement => createElement) 1`] = `"Failed to render React component root \\"App\\" due to side-effects from throwing exception"`; +exports[`Simple 13: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 2, + "evaluatedRootNodes": Array [ + Object { + "children": Array [ + Object { + "children": Array [], + "message": "", + "name": "Child", + "status": "INLINED", + }, + ], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 1, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; exports[`Simple 14: (JSX => JSX) 1`] = ` ReactStatistics { diff --git a/test/react/__snapshots__/Throw-test.js.snap b/test/react/__snapshots__/Throw-test.js.snap new file mode 100644 index 0000000000..b17c6af519 --- /dev/null +++ b/test/react/__snapshots__/Throw-test.js.snap @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throw.js: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; + +exports[`throw.js: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; + +exports[`throw.js: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; + +exports[`throw.js: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 0, +} +`; + +exports[`throw-conditional.js: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`throw-conditional.js: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`throw-conditional.js: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`throw-conditional.js: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "MyComponent", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; diff --git a/test/react/setupReactTests.js b/test/react/setupReactTests.js index 26ed340076..b36e0471a7 100644 --- a/test/react/setupReactTests.js +++ b/test/react/setupReactTests.js @@ -161,6 +161,8 @@ ${source} function runSource(source) { let transformedSource = ` + // Add global variable for spec compliance. + let global = this; // Inject React since compiled JSX would reference it. let React = require('react'); (function() { @@ -237,6 +239,7 @@ ${source} // We do the same in debug-fb-www script. shouldRecover = errorCode => errorCode === "PP0025", expectReconcilerError = false, + expectRuntimeError = false, expectedCreateElementCalls, data, } = options; @@ -286,8 +289,18 @@ ${source} let { getTrials: getTrialsA, independent } = A; let { getTrials: getTrialsB } = B; // Run tests that assert the rendered output matches. - let resultA = getTrialsA(rendererA, A, data, false); - let resultB = independent ? getTrialsB(rendererB, B, data, true) : getTrialsA(rendererB, B, data, false); + let resultA; + let resultB; + try { + resultA = getTrialsA(rendererA, A, data, false); + resultB = independent ? getTrialsB(rendererB, B, data, true) : getTrialsA(rendererB, B, data, false); + } catch (err) { + if (expectRuntimeError) { + expect(err.message).toMatchSnapshot(snapshotName); + return; + } + throw err; + } // The test has returned many values for us to check for (let i = 0; i < resultA.length; i++) { @@ -317,6 +330,7 @@ ${source} firstRenderOnly?: boolean, data?: mixed, expectReconcilerError?: boolean, + expectRuntimeError?: boolean, expectedCreateElementCalls?: number, shouldRecover?: (errorCode: string) => boolean, }; diff --git a/test/serializer/additional-functions/EmitPropertyRegressionTest.js b/test/serializer/additional-functions/EmitPropertyRegressionTest.js index 6511798077..6ae9d5aa80 100644 --- a/test/serializer/additional-functions/EmitPropertyRegressionTest.js +++ b/test/serializer/additional-functions/EmitPropertyRegressionTest.js @@ -1,4 +1,4 @@ -// expected Warning: PP1007,PP0023 +// expected Warning: PP0023 (function() { function URI(other) { if (other.foo) { diff --git a/test/serializer/additional-functions/Issue1821RegressionTest.js b/test/serializer/additional-functions/Issue1821RegressionTest.js index 2d147a68df..759b855fe2 100644 --- a/test/serializer/additional-functions/Issue1821RegressionTest.js +++ b/test/serializer/additional-functions/Issue1821RegressionTest.js @@ -1,4 +1,4 @@ -// expected Warning: PP1007, PP0023 +// expected Warning: PP0023 (function() { function URI(other) { if (other) { diff --git a/test/serializer/additional-functions/ModifiedObjectProperty.js b/test/serializer/additional-functions/ModifiedObjectProperty.js index c9bc6c9f93..a7e34b3457 100644 --- a/test/serializer/additional-functions/ModifiedObjectProperty.js +++ b/test/serializer/additional-functions/ModifiedObjectProperty.js @@ -1,4 +1,4 @@ -// expected Warning: PP1007, PP0023 +// expected Warning: PP0023 (function() { let p = {}; function f(c) { diff --git a/test/serializer/additional-functions/RegressionTestForIssue1881.js b/test/serializer/additional-functions/RegressionTestForIssue1881.js index 51b840c510..e6a5a465c5 100644 --- a/test/serializer/additional-functions/RegressionTestForIssue1881.js +++ b/test/serializer/additional-functions/RegressionTestForIssue1881.js @@ -1,4 +1,4 @@ -// expected Warning: PP1007, PP0023 +// expected Warning: PP0023 (function() { function f(c) { let o = {};