mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-10-26 15:20:18 +03:00
Re-factor of the React createElement logic
Summary: Release notes: none Now that we have snapshotting from Object.assign and ToObject for abstracts, we can now upgrade the ReactElement creation logic, simplifying a bunch of routes which actually resulted in a bunch of components inlining in tests where they didn't before. Furthermore, it allows us to tighten up the Flow typings in the React reconciler and tidy up some of the checks for `key`s or `ref`s logic, which was a bit hard to read before and didn't take into account objects with multiple values in their values domain. This also fixes a bug with abstract conditional component types not getting the deafultProps values correctly. Closes https://github.com/facebook/prepack/pull/1996 Differential Revision: D8172083 Pulled By: trueadm fbshipit-source-id: 0a8a1e0631883c92e914aba1308a109b3afda137
This commit is contained in:
parent
62636d4b2a
commit
47f84b405a
@ -1023,17 +1023,11 @@ ReactStatistics {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "refs are not supported on <Components />",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
"status": "NEW_TREE",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
@ -1478,6 +1472,36 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, JSX output Functional component folding Dynamic ReactElement type #3 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Foo",
|
||||
"status": "INLINED",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Bar",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, JSX output Functional component folding Dynamic ReactElement type 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -2280,6 +2304,23 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, JSX output Functional component folding Simple 16 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, JSX output Functional component folding Simple children 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -2686,6 +2727,8 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, JSX output Functional component folding Unsafe spread 1`] = `"Failed to optimize React component tree for \\"App\\" due to a fatal error during evaluation: A fatal error occurred while prepacking."`;
|
||||
|
||||
exports[`Test React with JSX input, JSX output Render props React Context 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -4770,17 +4813,11 @@ ReactStatistics {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "refs are not supported on <Components />",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
"status": "NEW_TREE",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
@ -5225,6 +5262,36 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, create-element output Functional component folding Dynamic ReactElement type #3 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Foo",
|
||||
"status": "INLINED",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Bar",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, create-element output Functional component folding Dynamic ReactElement type 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -6027,6 +6094,23 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, create-element output Functional component folding Simple 16 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, create-element output Functional component folding Simple children 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -6433,6 +6517,8 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with JSX input, create-element output Functional component folding Unsafe spread 1`] = `"Failed to optimize React component tree for \\"App\\" due to a fatal error during evaluation: A fatal error occurred while prepacking."`;
|
||||
|
||||
exports[`Test React with JSX input, create-element output Render props React Context 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -8517,17 +8603,11 @@ ReactStatistics {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "refs are not supported on <Components />",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
"status": "NEW_TREE",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
@ -8972,6 +9052,36 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Dynamic ReactElement type #3 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Foo",
|
||||
"status": "INLINED",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Bar",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Dynamic ReactElement type 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -9774,6 +9884,23 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Simple 16 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Simple children 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -10004,16 +10131,30 @@ ReactStatistics {
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Simple with multiple JSX spreads #2 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "IWantThisToBeInlined",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "Button",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
@ -10021,16 +10162,23 @@ ReactStatistics {
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Simple with multiple JSX spreads #3 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"componentsEvaluated": 2,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Button",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"inlinedComponents": 1,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
@ -10089,16 +10237,30 @@ ReactStatistics {
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Simple with multiple JSX spreads 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "IWantThisToBeInlined",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "Button",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
@ -10145,6 +10307,23 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, JSX output Functional component folding Unsafe spread 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, JSX output Render props React Context 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -12229,17 +12408,11 @@ ReactStatistics {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "refs are not supported on <Components />",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "ClassComponent",
|
||||
"status": "BAIL-OUT",
|
||||
"status": "NEW_TREE",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
@ -12684,6 +12857,36 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Dynamic ReactElement type #3 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Foo",
|
||||
"status": "INLINED",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Bar",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Dynamic ReactElement type 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -13486,6 +13689,23 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Simple 16 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Simple children 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
@ -13716,16 +13936,30 @@ ReactStatistics {
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Simple with multiple JSX spreads #2 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "IWantThisToBeInlined",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "Button",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
@ -13733,16 +13967,23 @@ ReactStatistics {
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Simple with multiple JSX spreads #3 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"componentsEvaluated": 2,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Button",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"inlinedComponents": 1,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
@ -13801,16 +14042,30 @@ ReactStatistics {
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Simple with multiple JSX spreads 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "IWantThisToBeInlined",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "Button",
|
||||
"status": "INLINED",
|
||||
},
|
||||
],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"inlinedComponents": 2,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
@ -13857,6 +14112,23 @@ ReactStatistics {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, create-element output Functional component folding Unsafe spread 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 1,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test React with create-element input, create-element output Render props React Context 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 3,
|
||||
|
@ -59,7 +59,7 @@ let prepackOptions = {
|
||||
errorsCaptured.push(diag);
|
||||
if (diag.severity !== "Warning") {
|
||||
if (diag.errorCode === "PP0025") {
|
||||
// recover from `unable to evaluate "key" and "ref" on a ReactElement due to an abstract config passed to createElement`
|
||||
// recover from `unable to evaluate "key" and "ref" on a ReactElement
|
||||
return "Recover";
|
||||
}
|
||||
return "Fail";
|
||||
|
@ -63,12 +63,18 @@ MockURI.prototype.makeString = function() {
|
||||
};
|
||||
|
||||
function runTestSuite(outputJsx, shouldTranspileSource) {
|
||||
let checkForReconcilerFatalError = false;
|
||||
let checkForPartialKeyOrRefError = false;
|
||||
let errorsCaptured = [];
|
||||
let reactTestRoot = path.join(__dirname, "../test/react/");
|
||||
let prepackOptions = {
|
||||
errorHandler: diag => {
|
||||
errorsCaptured.push(diag);
|
||||
if (diag.severity !== "Warning" && diag.severity !== "Information") {
|
||||
if (diag.errorCode === "PP0025" && !checkForPartialKeyOrRefError) {
|
||||
// recover from `unable to evaluate "key" and "ref" on a ReactElement
|
||||
return "Recover";
|
||||
}
|
||||
return "Fail";
|
||||
}
|
||||
return "Recover";
|
||||
@ -85,8 +91,6 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
|
||||
stripFlow: true,
|
||||
};
|
||||
|
||||
let checkForReconcilerFatalError = false;
|
||||
|
||||
async function expectReconcilerFatalError(func) {
|
||||
checkForReconcilerFatalError = true;
|
||||
try {
|
||||
@ -99,6 +103,18 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
|
||||
}
|
||||
}
|
||||
|
||||
async function expectPartialKeyOrRefError(func) {
|
||||
checkForPartialKeyOrRefError = true;
|
||||
try {
|
||||
await func();
|
||||
} catch (e) {
|
||||
expect(e.__isReconcilerFatalError).toBe(true);
|
||||
expect(e.message).toMatchSnapshot();
|
||||
} finally {
|
||||
checkForPartialKeyOrRefError = false;
|
||||
}
|
||||
}
|
||||
|
||||
function compileSourceWithPrepack(source) {
|
||||
let code = `(function(){${source}})()`;
|
||||
let serialized;
|
||||
@ -106,7 +122,7 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
|
||||
try {
|
||||
serialized = prepackSources([{ filePath: "", fileContents: code, sourceMapContents: "" }], prepackOptions);
|
||||
} catch (e) {
|
||||
if (e.__isReconcilerFatalError && checkForReconcilerFatalError) {
|
||||
if (e.__isReconcilerFatalError && (checkForReconcilerFatalError || checkForPartialKeyOrRefError)) {
|
||||
throw e;
|
||||
}
|
||||
errorsCaptured.forEach(error => {
|
||||
@ -344,6 +360,10 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
|
||||
await runTest(directory, "simple-15.js");
|
||||
});
|
||||
|
||||
it("Simple 16", async () => {
|
||||
await runTest(directory, "simple-16.js");
|
||||
});
|
||||
|
||||
it("__reactCompilerDoNotOptimize", async () => {
|
||||
await runTest(directory, "do-not-optimize.js");
|
||||
});
|
||||
@ -404,6 +424,12 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
|
||||
await runTest(directory, "refs3.js");
|
||||
});
|
||||
|
||||
it("Unsafe spread", async () => {
|
||||
await expectPartialKeyOrRefError(async () => {
|
||||
await runTest(directory, "unsafe-spread.js");
|
||||
});
|
||||
});
|
||||
|
||||
it("Simple with abstract props", async () => {
|
||||
await runTest(directory, "simple-with-abstract-props.js");
|
||||
});
|
||||
@ -575,6 +601,10 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
|
||||
it("Dynamic ReactElement type #2", async () => {
|
||||
await runTest(directory, "dynamic-type2.js");
|
||||
});
|
||||
|
||||
it("Dynamic ReactElement type #3", async () => {
|
||||
await runTest(directory, "dynamic-type3.js");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Class component folding", () => {
|
||||
|
@ -25,20 +25,21 @@ import type {
|
||||
import {
|
||||
AbstractObjectValue,
|
||||
ArrayValue,
|
||||
ECMAScriptFunctionValue,
|
||||
StringValue,
|
||||
Value,
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
FunctionValue,
|
||||
AbstractValue,
|
||||
} from "../values/index.js";
|
||||
import { convertJSXExpressionToIdentifier } from "../react/jsx.js";
|
||||
import * as t from "babel-types";
|
||||
import { Get } from "../methods/index.js";
|
||||
import { Create, Environment, Properties } from "../singletons.js";
|
||||
import { Create, Environment, Properties, To } from "../singletons.js";
|
||||
import invariant from "../invariant.js";
|
||||
import { createReactElement } from "../react/elements.js";
|
||||
import { objectHasNoPartialKeyAndRef, deleteRefAndKeyFromProps } from "../react/utils.js";
|
||||
import { flagPropsWithNoPartialKeyOrRef, hasNoPartialKeyOrRef } from "../react/utils.js";
|
||||
import { FatalError } from "../errors.js";
|
||||
|
||||
// taken from Babel
|
||||
function cleanJSXElementLiteralChild(child: string): null | string {
|
||||
@ -149,9 +150,9 @@ function evaluateJSXChildren(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): ArrayValue | Value {
|
||||
): ArrayValue | Value | void {
|
||||
if (children.length === 0) {
|
||||
return realm.intrinsics.undefined;
|
||||
return undefined;
|
||||
}
|
||||
if (children.length === 1) {
|
||||
let singleChild = evaluateJSXValue(children[0], strictCode, env, realm);
|
||||
@ -208,29 +209,16 @@ function evaluateJSXAttributes(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): ObjectValue | AbstractValue {
|
||||
): ObjectValue | AbstractObjectValue {
|
||||
let config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
// start by having key and ref deleted, if they actually exist, they will be added later
|
||||
deleteRefAndKeyFromProps(realm, config);
|
||||
let abstractPropsArgs = [];
|
||||
let containsAbstractSpreadAttribute = false;
|
||||
let mayContainRefOrKey = false;
|
||||
let attributesAssigned = 0;
|
||||
let abstractSpreadCount = 0;
|
||||
let safeAbstractSpreadCount = 0;
|
||||
let spreadValue;
|
||||
let keyValue;
|
||||
let refValue;
|
||||
|
||||
const setConfigProperty = (name: string, value: Value): void => {
|
||||
invariant(config instanceof ObjectValue);
|
||||
if (name === "key") {
|
||||
keyValue = value;
|
||||
mayContainRefOrKey = true;
|
||||
} else if (name === "ref") {
|
||||
refValue = value;
|
||||
mayContainRefOrKey = true;
|
||||
}
|
||||
Properties.Set(realm, config, name, value, true);
|
||||
attributesAssigned++;
|
||||
};
|
||||
|
||||
for (let astAttribute of astAttributes) {
|
||||
@ -251,20 +239,20 @@ function evaluateJSXAttributes(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
keyValue = undefined;
|
||||
refValue = undefined;
|
||||
containsAbstractSpreadAttribute = true;
|
||||
invariant(spreadValue instanceof AbstractValue || spreadValue instanceof ObjectValue);
|
||||
abstractSpreadCount++;
|
||||
if (spreadValue instanceof AbstractValue && !(spreadValue instanceof AbstractObjectValue)) {
|
||||
spreadValue = To.ToObject(realm, spreadValue);
|
||||
}
|
||||
invariant(spreadValue instanceof AbstractObjectValue || spreadValue instanceof ObjectValue);
|
||||
|
||||
if (!objectHasNoPartialKeyAndRef(realm, spreadValue)) {
|
||||
mayContainRefOrKey = true;
|
||||
if (hasNoPartialKeyOrRef(realm, spreadValue)) {
|
||||
safeAbstractSpreadCount++;
|
||||
}
|
||||
if (!isObjectEmpty(config)) {
|
||||
abstractPropsArgs.push(config);
|
||||
}
|
||||
abstractPropsArgs.push(spreadValue);
|
||||
config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
deleteRefAndKeyFromProps(realm, config);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -272,70 +260,55 @@ function evaluateJSXAttributes(
|
||||
}
|
||||
}
|
||||
|
||||
if (containsAbstractSpreadAttribute) {
|
||||
// if we haven't assigned any attributes and we are dealing with a single
|
||||
// spread attribute, we can just make the spread object the props
|
||||
if (
|
||||
attributesAssigned === 0 &&
|
||||
((spreadValue instanceof ObjectValue && spreadValue.isPartialObject()) || spreadValue instanceof AbstractValue)
|
||||
) {
|
||||
// the spread is partial, so we can re-use that value
|
||||
config = spreadValue;
|
||||
if (config instanceof ObjectValue || config instanceof AbstractObjectValue) {
|
||||
// as we're applying a spread, the config needs to be simple/partial
|
||||
config.makePartial();
|
||||
config.makeSimple();
|
||||
}
|
||||
} else {
|
||||
// we create an abstract Object.assign() to deal with the fact that we don't what
|
||||
// the props are because they contain abstract spread attributes that we can't
|
||||
// evaluate ahead of time
|
||||
// push the current config
|
||||
if (config.properties.size > 0) {
|
||||
abstractPropsArgs.push(config);
|
||||
}
|
||||
// create a new config object that will be the target of the Object.assign
|
||||
config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
// as this is "config that is abstract, we need to make it partial and simple
|
||||
config.makePartial();
|
||||
config.makeSimple();
|
||||
// get the global Object.assign
|
||||
let globalObj = Get(realm, realm.$GlobalObject, "Object");
|
||||
invariant(globalObj instanceof ObjectValue);
|
||||
let objAssign = Get(realm, globalObj, "assign");
|
||||
invariant(realm.generator);
|
||||
if (abstractSpreadCount > 0) {
|
||||
// we create an abstract Object.assign() to deal with the fact that we don't what
|
||||
// the props are because they contain abstract spread attributes that we can't
|
||||
// evaluate ahead of time
|
||||
// push the current config
|
||||
abstractPropsArgs.push(config);
|
||||
|
||||
invariant(realm.generator);
|
||||
AbstractValue.createTemporalFromBuildFunction(
|
||||
realm,
|
||||
FunctionValue,
|
||||
[objAssign, config, ...abstractPropsArgs],
|
||||
([methodNode, ..._args]) => {
|
||||
return t.callExpression(methodNode, ((_args: any): Array<any>));
|
||||
}
|
||||
);
|
||||
if (!mayContainRefOrKey) {
|
||||
deleteRefAndKeyFromProps(realm, config);
|
||||
} else {
|
||||
if (keyValue !== undefined) {
|
||||
setConfigProperty("key", keyValue);
|
||||
}
|
||||
if (refValue !== undefined) {
|
||||
setConfigProperty("ref", refValue);
|
||||
// create a new config object that will be the target of the Object.assign
|
||||
config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
// ensure the config partial
|
||||
config.makePartial();
|
||||
|
||||
// get the global Object.assign
|
||||
let globalObj = Get(realm, realm.$GlobalObject, "Object");
|
||||
invariant(globalObj instanceof ObjectValue);
|
||||
let objAssign = Get(realm, globalObj, "assign");
|
||||
invariant(objAssign instanceof ECMAScriptFunctionValue);
|
||||
let objectAssignCall = objAssign.$Call;
|
||||
invariant(objectAssignCall !== undefined);
|
||||
|
||||
try {
|
||||
objectAssignCall(realm.intrinsics.undefined, [config, ...abstractPropsArgs]);
|
||||
if (safeAbstractSpreadCount === abstractSpreadCount) {
|
||||
flagPropsWithNoPartialKeyOrRef(realm, config);
|
||||
}
|
||||
} catch (e) {
|
||||
if (realm.isInPureScope() && e instanceof FatalError) {
|
||||
let flagProps = hasNoPartialKeyOrRef(realm, config);
|
||||
|
||||
config = AbstractValue.createTemporalFromBuildFunction(
|
||||
realm,
|
||||
ObjectValue,
|
||||
[objAssign, config, ...abstractPropsArgs],
|
||||
([methodNode, ..._args]) => {
|
||||
return t.callExpression(methodNode, ((_args: any): Array<any>));
|
||||
}
|
||||
);
|
||||
invariant(config instanceof AbstractObjectValue);
|
||||
if (flagProps) {
|
||||
flagPropsWithNoPartialKeyOrRef(realm, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
invariant(config instanceof ObjectValue || config instanceof AbstractValue);
|
||||
invariant(config instanceof ObjectValue || config instanceof AbstractObjectValue);
|
||||
return config;
|
||||
}
|
||||
|
||||
export default function(
|
||||
ast: BabelNodeJSXElement,
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): ObjectValue {
|
||||
export default function(ast: BabelNodeJSXElement, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value {
|
||||
invariant(realm.react.enabled, "JSXElements can only be evaluated with the reactEnabled option");
|
||||
let openingElement = ast.openingElement;
|
||||
let type = evaluateJSXIdentifier(openingElement.name, strictCode, env, realm);
|
||||
|
16
src/intrinsics/fb-www/react-mocks.js
vendored
16
src/intrinsics/fb-www/react-mocks.js
vendored
@ -18,14 +18,13 @@ import {
|
||||
Value,
|
||||
AbstractObjectValue,
|
||||
AbstractValue,
|
||||
NullValue,
|
||||
FunctionValue,
|
||||
NumberValue,
|
||||
} from "../../values/index.js";
|
||||
import { Environment } from "../../singletons.js";
|
||||
import { createReactHintObject, getReactSymbol, isReactElement } from "../../react/utils.js";
|
||||
import { createReactElement } from "../../react/elements.js";
|
||||
import { Properties, Create } from "../../singletons.js";
|
||||
import { Properties, Create, To } from "../../singletons.js";
|
||||
import { renderToString } from "../../react/experimental-server-rendering/rendering.js";
|
||||
import * as t from "babel-types";
|
||||
import invariant from "../../invariant";
|
||||
@ -499,16 +498,14 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV
|
||||
if (config === realm.intrinsics.undefined || config === realm.intrinsics.null || config === undefined) {
|
||||
config = new ObjectValue(realm, realm.intrinsics.ObjectPrototype);
|
||||
}
|
||||
invariant(
|
||||
config instanceof ObjectValue ||
|
||||
config instanceof AbstractObjectValue ||
|
||||
config instanceof AbstractValue ||
|
||||
config instanceof NullValue
|
||||
);
|
||||
if (config instanceof AbstractValue && !(config instanceof AbstractObjectValue)) {
|
||||
config = To.ToObject(realm, config);
|
||||
}
|
||||
invariant(config instanceof ObjectValue || config instanceof AbstractObjectValue);
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
if (children.length === 0) {
|
||||
children = realm.intrinsics.undefined;
|
||||
children = undefined;
|
||||
} else if (children.length === 1) {
|
||||
children = children[0];
|
||||
} else {
|
||||
@ -522,7 +519,6 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV
|
||||
children.makeFinal();
|
||||
}
|
||||
}
|
||||
invariant(children instanceof Value);
|
||||
return createReactElement(realm, type, config, children);
|
||||
}
|
||||
);
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
} from "../values/index.js";
|
||||
import * as t from "babel-types";
|
||||
import type { BabelNodeIdentifier } from "babel-types";
|
||||
import { valueIsClassComponent, deleteRefAndKeyFromProps, getProperty, getValueFromFunctionCall } from "./utils";
|
||||
import { flagPropsWithNoPartialKeyOrRef, getProperty, getValueFromFunctionCall, valueIsClassComponent } from "./utils";
|
||||
import { ExpectedBailOut, SimpleClassBailOut } from "./errors.js";
|
||||
import { Get, Construct } from "../methods/index.js";
|
||||
import { Properties } from "../singletons.js";
|
||||
@ -64,9 +64,9 @@ export function getInitialProps(
|
||||
}
|
||||
}
|
||||
let value = AbstractValue.createAbstractObject(realm, propsName || "props");
|
||||
// props objects don't have a key and ref, so we remove them
|
||||
deleteRefAndKeyFromProps(realm, value);
|
||||
invariant(value instanceof AbstractObjectValue);
|
||||
flagPropsWithNoPartialKeyOrRef(realm, value);
|
||||
value.makeFinal();
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -14,16 +14,15 @@ import {
|
||||
AbstractValue,
|
||||
AbstractObjectValue,
|
||||
ArrayValue,
|
||||
ECMAScriptFunctionValue,
|
||||
NumberValue,
|
||||
Value,
|
||||
ObjectValue,
|
||||
FunctionValue,
|
||||
NullValue,
|
||||
Value,
|
||||
} from "../values/index.js";
|
||||
import { Create, Properties } from "../singletons.js";
|
||||
import invariant from "../invariant.js";
|
||||
import { Get } from "../methods/index.js";
|
||||
import { getProperty, getReactSymbol, objectHasNoPartialKeyAndRef, deleteRefAndKeyFromProps } from "./utils.js";
|
||||
import { flagPropsWithNoPartialKeyOrRef, getProperty, getReactSymbol, hasNoPartialKeyOrRef } from "./utils.js";
|
||||
import * as t from "babel-types";
|
||||
import { computeBinary } from "../evaluators/BinaryExpression.js";
|
||||
import { CompilerDiagnostic, FatalError } from "../errors.js";
|
||||
@ -31,23 +30,44 @@ import { CompilerDiagnostic, FatalError } from "../errors.js";
|
||||
function createPropsObject(
|
||||
realm: Realm,
|
||||
type: Value,
|
||||
config: ObjectValue | AbstractValue | AbstractObjectValue | NullValue,
|
||||
children: Value
|
||||
) {
|
||||
let defaultProps = type instanceof ObjectValue ? Get(realm, type, "defaultProps") : realm.intrinsics.undefined;
|
||||
config: ObjectValue | AbstractObjectValue,
|
||||
children: void | Value
|
||||
): { key: Value, ref: Value, props: ObjectValue | AbstractObjectValue } {
|
||||
let defaultProps =
|
||||
type instanceof ObjectValue || type instanceof AbstractObjectValue
|
||||
? Get(realm, type, "defaultProps")
|
||||
: realm.intrinsics.undefined;
|
||||
|
||||
let props = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
// start by having key and ref deleted, if they actually exist, they will be add later
|
||||
deleteRefAndKeyFromProps(realm, props);
|
||||
let key = realm.intrinsics.null;
|
||||
let ref = realm.intrinsics.null;
|
||||
|
||||
if (!hasNoPartialKeyOrRef(realm, config)) {
|
||||
// if either are abstract, this will impact the reconcilation process
|
||||
// and ultimately prevent us from folding ReactElements properly
|
||||
let diagnostic = new CompilerDiagnostic(
|
||||
`unable to evaluate "key" and "ref" on a ReactElement due to an abstract config passed to createElement`,
|
||||
realm.currentLocation,
|
||||
"PP0025",
|
||||
"FatalError"
|
||||
);
|
||||
realm.handleError(diagnostic);
|
||||
if (realm.handleError(diagnostic) === "Fail") throw new FatalError();
|
||||
}
|
||||
|
||||
let possibleKey = Get(realm, config, "key");
|
||||
if (possibleKey !== realm.intrinsics.null && possibleKey !== realm.intrinsics.undefined) {
|
||||
key = computeBinary(realm, "+", realm.intrinsics.emptyString, possibleKey);
|
||||
}
|
||||
|
||||
let possibleRef = Get(realm, config, "ref");
|
||||
if (possibleRef !== realm.intrinsics.null && possibleRef !== realm.intrinsics.undefined) {
|
||||
ref = possibleRef;
|
||||
}
|
||||
|
||||
const setProp = (name: string, value: Value): void => {
|
||||
if (name === "key" && value !== realm.intrinsics.null) {
|
||||
key = computeBinary(realm, "+", realm.intrinsics.emptyString, value);
|
||||
} else if (name === "ref") {
|
||||
ref = value;
|
||||
} else if (name !== "__self" && name !== "__source") {
|
||||
invariant(props instanceof ObjectValue);
|
||||
if (name !== "__self" && name !== "__source" && name !== "key" && name !== "ref") {
|
||||
invariant(props instanceof ObjectValue || props instanceof AbstractObjectValue);
|
||||
Properties.Set(realm, props, name, value, true);
|
||||
}
|
||||
};
|
||||
@ -64,68 +84,48 @@ function createPropsObject(
|
||||
|
||||
if (
|
||||
(config instanceof AbstractObjectValue && config.isPartialObject()) ||
|
||||
config instanceof AbstractValue ||
|
||||
(config instanceof ObjectValue && config.isPartialObject() && config.isSimpleObject())
|
||||
) {
|
||||
// if we have defaultProps, we need to create a new merge of the objects along with our config
|
||||
if (defaultProps !== realm.intrinsics.undefined || children !== realm.intrinsics.undefined) {
|
||||
if (objectHasNoPartialKeyAndRef(realm, config)) {
|
||||
let args = [];
|
||||
if (defaultProps !== realm.intrinsics.undefined) {
|
||||
args.push(defaultProps);
|
||||
}
|
||||
args.push(config);
|
||||
// create a new props object that will be the target of the Object.assign
|
||||
props = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
// as this is "props" that is abstract, we need to make it partial and simple
|
||||
props.makePartial();
|
||||
props.makeSimple();
|
||||
// props objects also don't have a key and ref, so we remove them
|
||||
deleteRefAndKeyFromProps(realm, props);
|
||||
let args = [];
|
||||
if (defaultProps !== realm.intrinsics.undefined) {
|
||||
args.push(defaultProps);
|
||||
}
|
||||
args.push(config);
|
||||
// create a new props object that will be the target of the Object.assign
|
||||
props = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
|
||||
// get the global Object.assign
|
||||
let globalObj = Get(realm, realm.$GlobalObject, "Object");
|
||||
invariant(globalObj instanceof ObjectValue);
|
||||
let objAssign = Get(realm, globalObj, "assign");
|
||||
invariant(realm.generator);
|
||||
// get the global Object.assign
|
||||
let globalObj = Get(realm, realm.$GlobalObject, "Object");
|
||||
invariant(globalObj instanceof ObjectValue);
|
||||
let objAssign = Get(realm, globalObj, "assign");
|
||||
invariant(objAssign instanceof ECMAScriptFunctionValue);
|
||||
let objectAssignCall = objAssign.$Call;
|
||||
invariant(objectAssignCall !== undefined);
|
||||
|
||||
AbstractValue.createTemporalFromBuildFunction(
|
||||
if (children !== undefined) {
|
||||
let childrenObject = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
Properties.Set(realm, props, "children", children, true);
|
||||
args.push(childrenObject);
|
||||
}
|
||||
|
||||
try {
|
||||
objectAssignCall(realm.intrinsics.undefined, [props, ...args]);
|
||||
} catch (e) {
|
||||
if (realm.isInPureScope() && e instanceof FatalError) {
|
||||
props = AbstractValue.createTemporalFromBuildFunction(
|
||||
realm,
|
||||
FunctionValue,
|
||||
ObjectValue,
|
||||
[objAssign, props, ...args],
|
||||
([methodNode, ..._args]) => {
|
||||
return t.callExpression(methodNode, ((_args: any): Array<any>));
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// if either are abstract, this will impact the reconcilation process
|
||||
// and ultimately prevent us from folding ReactElements properly
|
||||
let diagnostic = new CompilerDiagnostic(
|
||||
`unable to evaluate "key" and "ref" on a ReactElement due to an abstract config passed to createElement`,
|
||||
realm.currentLocation,
|
||||
"PP0025",
|
||||
"FatalError"
|
||||
);
|
||||
realm.handleError(diagnostic);
|
||||
if (realm.handleError(diagnostic) === "Fail") throw new FatalError();
|
||||
}
|
||||
if (children !== realm.intrinsics.undefined) {
|
||||
setProp("children", children);
|
||||
}
|
||||
} else {
|
||||
// as the config is partial and simple, we don't know about its prototype or properties
|
||||
// we don't have to worry about non-enumerable properties as its properties will never
|
||||
// be serialized, rather this object will be serialized as a spread.
|
||||
props = config;
|
||||
// if there are any properties that do exist, it's because we know for sure they exist
|
||||
// i.e. they were added on as part of snapshotting or at the end of a spread, like
|
||||
// {...foo, ...bar, x: 5}
|
||||
applyProperties();
|
||||
}
|
||||
} else {
|
||||
applyProperties();
|
||||
|
||||
if (children !== realm.intrinsics.undefined) {
|
||||
if (children !== undefined) {
|
||||
setProp("children", children);
|
||||
}
|
||||
|
||||
@ -137,21 +137,61 @@ function createPropsObject(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (defaultProps instanceof AbstractObjectValue) {
|
||||
invariant(false, "TODO: we need to eventually support this");
|
||||
}
|
||||
}
|
||||
if (props instanceof ObjectValue || props instanceof AbstractObjectValue) {
|
||||
// ensure the props is marked as final
|
||||
invariant(props instanceof ObjectValue || props instanceof AbstractObjectValue);
|
||||
// We know the props has no keys because if it did it would have thrown above
|
||||
// so we can remove them the props we create.
|
||||
flagPropsWithNoPartialKeyOrRef(realm, props);
|
||||
// If the object is an object or abstract object value that has a backing object
|
||||
// value for a template, we can make them final. We can't make abstract values
|
||||
// final though – but that's okay. They also can't be havoced.
|
||||
if (props instanceof ObjectValue || (props instanceof AbstractObjectValue && !props.values.isTop())) {
|
||||
props.makeFinal();
|
||||
}
|
||||
return { key, props, ref };
|
||||
}
|
||||
|
||||
function splitReactElementsByConditionalType(
|
||||
realm: Realm,
|
||||
condValue: AbstractValue,
|
||||
consequentVal: Value,
|
||||
alternateVal: Value,
|
||||
config: ObjectValue | AbstractObjectValue,
|
||||
children: void | Value
|
||||
): Value {
|
||||
return realm.evaluateWithAbstractConditional(
|
||||
condValue,
|
||||
() => {
|
||||
return realm.evaluateForEffects(
|
||||
() => createReactElement(realm, consequentVal, config, children),
|
||||
null,
|
||||
"splitReactElementsByConditionalType consequent"
|
||||
);
|
||||
},
|
||||
() => {
|
||||
return realm.evaluateForEffects(
|
||||
() => createReactElement(realm, alternateVal, config, children),
|
||||
null,
|
||||
"splitReactElementsByConditionalType alternate"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function createReactElement(
|
||||
realm: Realm,
|
||||
type: Value,
|
||||
config: ObjectValue | AbstractValue | AbstractObjectValue | NullValue,
|
||||
children: Value
|
||||
) {
|
||||
config: ObjectValue | AbstractObjectValue,
|
||||
children: void | Value
|
||||
): Value {
|
||||
if (type instanceof AbstractValue && type.kind === "conditional") {
|
||||
let [condValue, consequentVal, alternateVal] = type.args;
|
||||
invariant(condValue instanceof AbstractValue);
|
||||
return splitReactElementsByConditionalType(realm, condValue, consequentVal, alternateVal, config, children);
|
||||
}
|
||||
let { key, props, ref } = createPropsObject(realm, type, config, children);
|
||||
return createInternalReactElement(realm, type, key, ref, props);
|
||||
}
|
||||
@ -161,8 +201,8 @@ export function createInternalReactElement(
|
||||
type: Value,
|
||||
key: Value,
|
||||
ref: Value,
|
||||
props: ObjectValue | AbstractValue
|
||||
) {
|
||||
props: ObjectValue | AbstractObjectValue
|
||||
): ObjectValue {
|
||||
let obj = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||
Create.CreateDataPropertyOrThrow(realm, obj, "$$typeof", getReactSymbol("react.element", realm));
|
||||
Create.CreateDataPropertyOrThrow(realm, obj, "type", type);
|
||||
|
@ -316,7 +316,7 @@ export class Reconciler {
|
||||
|
||||
_resolveComplexClassComponent(
|
||||
componentType: ECMAScriptSourceFunctionValue,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
classMetadata: ClassComponentMetadata,
|
||||
branchStatus: BranchStatusEnum,
|
||||
@ -352,7 +352,7 @@ export class Reconciler {
|
||||
|
||||
_resolveSimpleClassComponent(
|
||||
componentType: ECMAScriptSourceFunctionValue,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
branchState: BranchState | null,
|
||||
@ -369,7 +369,7 @@ export class Reconciler {
|
||||
|
||||
_resolveFunctionalComponent(
|
||||
componentType: ECMAScriptSourceFunctionValue,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
) {
|
||||
@ -378,7 +378,7 @@ export class Reconciler {
|
||||
|
||||
_getClassComponentMetadata(
|
||||
componentType: ECMAScriptSourceFunctionValue,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue
|
||||
): ClassComponentMetadata {
|
||||
if (this.realm.react.classComponentMetadata.has(componentType)) {
|
||||
@ -607,7 +607,7 @@ export class Reconciler {
|
||||
|
||||
_resolveClassComponent(
|
||||
componentType: ECMAScriptSourceFunctionValue,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
branchState: BranchState | null,
|
||||
@ -674,7 +674,7 @@ export class Reconciler {
|
||||
|
||||
_resolveClassComponentForFirstRenderOnly(
|
||||
componentType: ECMAScriptSourceFunctionValue,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
branchState: BranchState | null,
|
||||
@ -715,7 +715,7 @@ export class Reconciler {
|
||||
|
||||
_resolveRelayContainer(
|
||||
reactHint: ReactHint,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
branchState: BranchState | null,
|
||||
@ -743,7 +743,7 @@ export class Reconciler {
|
||||
|
||||
_resolveComponent(
|
||||
componentType: Value,
|
||||
props: ObjectValue | AbstractValue | AbstractObjectValue,
|
||||
props: ObjectValue | AbstractObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
branchState: BranchState | null,
|
||||
@ -922,7 +922,7 @@ export class Reconciler {
|
||||
return this.realm.evaluateForEffects(
|
||||
() => this._resolveDeeply(componentType, alternateVal, context, "NEW_BRANCH", newBranchState, evaluatedNode),
|
||||
null,
|
||||
"_resolveAbstractConditionalValue consequent"
|
||||
"_resolveAbstractConditionalValue alternate"
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -980,51 +980,6 @@ export class Reconciler {
|
||||
return value;
|
||||
}
|
||||
|
||||
_resolveBranchedComponentType(
|
||||
componentType: Value,
|
||||
reactElement: ObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
branchState: BranchState | null,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
): Value {
|
||||
let typeValue = getProperty(this.realm, reactElement, "type");
|
||||
let propsValue = getProperty(this.realm, reactElement, "props");
|
||||
let keyValue = getProperty(this.realm, reactElement, "key");
|
||||
let refValue = getProperty(this.realm, reactElement, "ref");
|
||||
|
||||
const resolveBranch = (abstract: AbstractValue): Value => {
|
||||
invariant(abstract.kind === "conditional", "the reconciler tried to resolve a non conditional abstract");
|
||||
let condition = abstract.args[0];
|
||||
invariant(condition instanceof AbstractValue);
|
||||
let left = abstract.args[1];
|
||||
let right = abstract.args[2];
|
||||
|
||||
if (left instanceof AbstractValue && left.kind === "conditional") {
|
||||
left = resolveBranch(left);
|
||||
} else {
|
||||
invariant(propsValue instanceof ObjectValue || propsValue instanceof AbstractValue);
|
||||
left = createInternalReactElement(this.realm, left, keyValue, refValue, propsValue);
|
||||
}
|
||||
if (right instanceof AbstractValue && right.kind === "conditional") {
|
||||
right = resolveBranch(right);
|
||||
} else {
|
||||
invariant(propsValue instanceof ObjectValue || propsValue instanceof AbstractValue);
|
||||
right = createInternalReactElement(this.realm, right, keyValue, refValue, propsValue);
|
||||
}
|
||||
return AbstractValue.createFromConditionalOp(this.realm, condition, left, right);
|
||||
};
|
||||
invariant(typeValue instanceof AbstractValue);
|
||||
return this._resolveDeeply(
|
||||
componentType,
|
||||
resolveBranch(typeValue),
|
||||
context,
|
||||
branchStatus,
|
||||
branchState,
|
||||
evaluatedNode
|
||||
);
|
||||
}
|
||||
|
||||
_resolveUnknownComponentType(reactElement: ObjectValue, evaluatedNode: ReactEvaluatedNode) {
|
||||
let typeValue = getProperty(this.realm, reactElement, "type");
|
||||
let propsValue = getProperty(this.realm, reactElement, "props");
|
||||
@ -1195,20 +1150,24 @@ export class Reconciler {
|
||||
evaluatedNode
|
||||
);
|
||||
}
|
||||
if (
|
||||
!(
|
||||
propsValue instanceof ObjectValue ||
|
||||
propsValue instanceof AbstractObjectValue ||
|
||||
propsValue instanceof AbstractValue
|
||||
)
|
||||
) {
|
||||
this._assignBailOutMessage(reactElement, `props on <Component /> was not not an ObjectValue or an AbstractValue`);
|
||||
if (!(propsValue instanceof ObjectValue || propsValue instanceof AbstractObjectValue)) {
|
||||
this._assignBailOutMessage(
|
||||
reactElement,
|
||||
`props on <Component /> was not not an ObjectValue or an AbstractObjectValue`
|
||||
);
|
||||
return reactElement;
|
||||
}
|
||||
let componentResolutionStrategy = this._getComponentResolutionStrategy(typeValue);
|
||||
|
||||
// we do not support "ref" on <Component /> ReactElements, unless it's a forwarded ref
|
||||
if (!(refValue instanceof NullValue) && componentResolutionStrategy !== "FORWARD_REF") {
|
||||
// We do not support "ref" on <Component /> ReactElements, unless it's a forwarded ref
|
||||
if (
|
||||
!(refValue instanceof NullValue) &&
|
||||
componentResolutionStrategy !== "FORWARD_REF" &&
|
||||
// If we have an abstract value, it might mean a bad ref, but we will have
|
||||
// already thrown a FatalError in the createElement implementation by this
|
||||
// point, so if we're here, then the FatalError has been recovered explicitly
|
||||
!(refValue instanceof AbstractValue)
|
||||
) {
|
||||
this._resolveReactElementBadRef(reactElement, evaluatedNode);
|
||||
}
|
||||
try {
|
||||
@ -1217,13 +1176,9 @@ export class Reconciler {
|
||||
switch (componentResolutionStrategy) {
|
||||
case "NORMAL": {
|
||||
if (typeValue instanceof AbstractValue && typeValue.kind === "conditional") {
|
||||
return this._resolveBranchedComponentType(
|
||||
componentType,
|
||||
reactElement,
|
||||
context,
|
||||
branchStatus,
|
||||
branchState,
|
||||
evaluatedNode
|
||||
invariant(
|
||||
false,
|
||||
"We should never have a component type that is condition, it should be handled in the createReactElement logic"
|
||||
);
|
||||
}
|
||||
if (
|
||||
|
@ -543,26 +543,38 @@ export function getComponentTypeFromRootValue(realm: Realm, value: Value): ECMAS
|
||||
}
|
||||
}
|
||||
|
||||
// props should never have "ref" or "key" properties, as they're part of ReactElement
|
||||
// object instead. to ensure that we can give this hint, we create them and then
|
||||
// delete them, so their descriptor is left undefined. we use this knowledge later
|
||||
// to ensure that when dealing with creating ReactElements with partial config,
|
||||
// we don't have to bail out becuase "config" may or may not have "key" or/and "ref"
|
||||
export function deleteRefAndKeyFromProps(realm: Realm, props: ObjectValue | AbstractObjectValue): void {
|
||||
setProperty(props, "ref", realm.intrinsics.undefined);
|
||||
deleteProperty(props, "ref");
|
||||
setProperty(props, "key", realm.intrinsics.undefined);
|
||||
deleteProperty(props, "key");
|
||||
export function flagPropsWithNoPartialKeyOrRef(realm: Realm, props: ObjectValue | AbstractObjectValue): void {
|
||||
realm.react.propsWithNoPartialKeyOrRef.add(props);
|
||||
}
|
||||
|
||||
export function objectHasNoPartialKeyAndRef(
|
||||
realm: Realm,
|
||||
object: ObjectValue | AbstractValue | AbstractObjectValue
|
||||
): boolean {
|
||||
if (object instanceof AbstractValue) {
|
||||
export function hasNoPartialKeyOrRef(realm: Realm, props: ObjectValue | AbstractObjectValue): boolean {
|
||||
if (realm.react.propsWithNoPartialKeyOrRef.has(props)) {
|
||||
return true;
|
||||
}
|
||||
return !(Get(realm, object, "key") instanceof AbstractValue || Get(realm, object, "ref") instanceof AbstractValue);
|
||||
if (props instanceof ObjectValue && !props.isPartialObject()) {
|
||||
return true;
|
||||
}
|
||||
if (props instanceof AbstractObjectValue) {
|
||||
if (props.values.isTop()) {
|
||||
return false;
|
||||
}
|
||||
let elements = props.values.getElements();
|
||||
if (elements.size === 1) {
|
||||
props = Array.from(elements)[0];
|
||||
} else {
|
||||
for (let element of elements) {
|
||||
let wasSafe = hasNoPartialKeyOrRef(realm, element);
|
||||
if (!wasSafe) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (props instanceof ObjectValue && props.properties.has("key") && props.properties.has("ref")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function recursivelyFlattenArray(realm: Realm, array, targetArray): void {
|
||||
@ -630,6 +642,9 @@ export function deleteProperty(object: ObjectValue | AbstractObjectValue, proper
|
||||
let elements = object.values.getElements();
|
||||
if (elements && elements.size > 0) {
|
||||
object = Array.from(elements)[0];
|
||||
} else {
|
||||
// intentionally left in
|
||||
invariant(false, "TODO: should we hit this?");
|
||||
}
|
||||
invariant(object instanceof ObjectValue);
|
||||
}
|
||||
@ -659,6 +674,9 @@ export function setProperty(
|
||||
let elements = object.values.getElements();
|
||||
if (elements && elements.size > 0) {
|
||||
object = Array.from(elements)[0];
|
||||
} else {
|
||||
// intentionally left in
|
||||
invariant(false, "TODO: should we hit this?");
|
||||
}
|
||||
invariant(object instanceof ObjectValue);
|
||||
}
|
||||
@ -710,8 +728,12 @@ export function getProperty(
|
||||
return realm.intrinsics.undefined;
|
||||
}
|
||||
let elements = object.values.getElements();
|
||||
if (elements && elements.size > 0) {
|
||||
invariant(elements);
|
||||
if (elements.size > 0) {
|
||||
object = Array.from(elements)[0];
|
||||
} else {
|
||||
// intentionally left in
|
||||
invariant(false, "TODO: should we hit this?");
|
||||
}
|
||||
invariant(object instanceof ObjectValue);
|
||||
}
|
||||
|
@ -255,6 +255,7 @@ export class Realm {
|
||||
hoistableReactElements: new WeakMap(),
|
||||
noopFunction: undefined,
|
||||
output: opts.reactOutput || "create-element",
|
||||
propsWithNoPartialKeyOrRef: new WeakSet(),
|
||||
reactElements: new WeakSet(),
|
||||
symbols: new Map(),
|
||||
verbose: opts.reactVerbose || false,
|
||||
@ -342,6 +343,7 @@ export class Realm {
|
||||
hoistableReactElements: WeakMap<ObjectValue, boolean>,
|
||||
noopFunction: void | ECMAScriptSourceFunctionValue,
|
||||
output?: ReactOutputTypes,
|
||||
propsWithNoPartialKeyOrRef: WeakSet<ObjectValue | AbstractObjectValue>,
|
||||
reactElements: WeakSet<ObjectValue>,
|
||||
symbols: Map<ReactSymbolTypes, SymbolValue>,
|
||||
verbose: boolean,
|
||||
|
42
test/react/functional-components/dynamic-type3.js
Normal file
42
test/react/functional-components/dynamic-type3.js
Normal file
@ -0,0 +1,42 @@
|
||||
var React = require('react');
|
||||
// the JSX transform converts to React, so we need to add it back in
|
||||
this['React'] = React;
|
||||
|
||||
function Foo(props) {
|
||||
return <span>{props.name}</span>;
|
||||
}
|
||||
|
||||
Foo.defaultProps = {
|
||||
name: "Hello world 1",
|
||||
};
|
||||
|
||||
function Bar(props) {
|
||||
return <div>{props.name}</div>;
|
||||
}
|
||||
|
||||
Bar.defaultProps = {
|
||||
name: "Hello world 2",
|
||||
};
|
||||
|
||||
function App(props) {
|
||||
let Type = props.switch ? Foo : Bar;
|
||||
|
||||
return <Type />
|
||||
}
|
||||
|
||||
App.getTrials = function(renderer, Root) {
|
||||
let results = [];
|
||||
renderer.update(<Root switch={false} />);
|
||||
results.push(['render with dynamic type', renderer.toJSON()]);
|
||||
renderer.update(<Root switch={true} />);
|
||||
results.push(['render with dynamic type update', renderer.toJSON()]);
|
||||
renderer.update(<Root switch={false} />);
|
||||
results.push(['render with dynamic type update', renderer.toJSON()]);
|
||||
return results;
|
||||
};
|
||||
|
||||
if (this.__optimizeReactComponentTree) {
|
||||
__optimizeReactComponentTree(App);
|
||||
}
|
||||
|
||||
module.exports = App;
|
21
test/react/functional-components/simple-16.js
Normal file
21
test/react/functional-components/simple-16.js
Normal file
@ -0,0 +1,21 @@
|
||||
var React = require('React');
|
||||
// the JSX transform converts to React, so we need to add it back in
|
||||
this['React'] = React;
|
||||
|
||||
function App(props) {
|
||||
return <div children={'hi'}>{undefined}</div>
|
||||
}
|
||||
|
||||
|
||||
App.getTrials = function(renderer, Root) {
|
||||
renderer.update(<Root />);
|
||||
return [['undefined children', renderer.toJSON()]];
|
||||
};
|
||||
|
||||
if (this.__optimizeReactComponentTree) {
|
||||
__optimizeReactComponentTree(App, {
|
||||
firstRenderOnly: true,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = App;
|
30
test/react/functional-components/unsafe-spread.js
Normal file
30
test/react/functional-components/unsafe-spread.js
Normal file
@ -0,0 +1,30 @@
|
||||
var React = require('react');
|
||||
this['React'] = React;
|
||||
|
||||
class Wat extends React.Component {
|
||||
render() {
|
||||
return <div {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return <Wat {...props.inner} />;
|
||||
}
|
||||
|
||||
App.getTrials = function(renderer, Root) {
|
||||
var obj;
|
||||
function ref(inst) {
|
||||
obj = inst;
|
||||
}
|
||||
renderer.update(<Root inner={{className: 'foo', ref}} />);
|
||||
return [
|
||||
['simple render with jsx spread 6', renderer.toJSON()],
|
||||
['type', Object.keys(obj).join(',')]
|
||||
];
|
||||
};
|
||||
|
||||
if (this.__optimizeReactComponentTree) {
|
||||
__optimizeReactComponentTree(App);
|
||||
}
|
||||
|
||||
module.exports = App;
|
Loading…
Reference in New Issue
Block a user