diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 000000000..e69de29bb diff --git a/package.json b/package.json index 757cfc38a..f329b14aa 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "test-node-cli-mode": "bash < scripts/test-node-cli-mode.sh", "test-std-in": "bash < scripts/test-std-in.sh", "test-react": "jest scripts/test-react", - "test": "yarn test-residual && yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-internal && yarn test-react", + "test": "yarn test-residual && yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-react && yarn test-internal", "test-coverage-most": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/remap-istanbul -i coverage.most/coverage.json -o coverage-sourcemapped -t html", "test-all-coverage": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover -- ./lib/test262-runner.js --timeout 50 --singleThreaded && ./node_modules/.bin/remap-istanbul -i coverage/coverage.json -i coverage.most/coverage.json -o coverage-sourcemapped -t html", "repl": "node lib/repl-cli.js", diff --git a/scripts/detect_bad_deps.js b/scripts/detect_bad_deps.js index 23583e2c9..10a9d8c14 100644 --- a/scripts/detect_bad_deps.js +++ b/scripts/detect_bad_deps.js @@ -41,7 +41,7 @@ exec("flow check --profile", function(error, stdout, stderr) { process.exit(1); } console.log("Biggest cycle: " + cycle_len); - let MAX_CYCLE_LEN = 59; + let MAX_CYCLE_LEN = 61; if (cycle_len > MAX_CYCLE_LEN) { console.log("Error: You increased cycle length from the previous high of " + MAX_CYCLE_LEN); process.exit(1); diff --git a/scripts/test-react.js b/scripts/test-react.js index 5cbcd30a7..b3c530465 100644 --- a/scripts/test-react.js +++ b/scripts/test-react.js @@ -35,15 +35,11 @@ let prepackOptions = { function compileSourceWithPrepack(source) { let code = `(function(){${source}})()`; let serialized = prepackSources([{ filePath: "", fileContents: code, sourceMapContents: "" }], prepackOptions); - // add the React require back in, as we've removed it with our Prepack mock - // the regex checks for any Prepack variable that matches "_$**any digit**.React" - let compiledSource = serialized.code.replace(/_\$[\d].React/, "React = require('react')"); if (serialized == null || serialized.reactStatistics == null) { throw new Error("React test runner failed during serialization"); } return { - // replace the code to put back the generator (Prepack doesn't serialize them yet) - compiledSource, + compiledSource: serialized.code, statistics: serialized.reactStatistics, }; } @@ -145,6 +141,10 @@ describe("Test React", () => { await runTest(directory, "type-change.js"); }); + it("Component type same", async () => { + await runTest(directory, "type-same.js"); + }); + it("Dynamic props", async () => { await runTest(directory, "dynamic-props.js"); }); diff --git a/src/flow/abstractObjectFactories.js b/src/flow/abstractObjectFactories.js new file mode 100644 index 000000000..d0aafcac2 --- /dev/null +++ b/src/flow/abstractObjectFactories.js @@ -0,0 +1,123 @@ +/** + * 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 */ + +import { Realm } from "../realm.js"; +import buildExpressionTemplate from "../utils/builder.js"; +import { ObjectCreate, ArrayCreate } from "../methods/index.js"; +import { ValuesDomain } from "../domains/index.js"; +import { Value, AbstractValue, ObjectValue, ArrayValue } from "../values/index.js"; +import invariant from "../invariant.js"; +import { type ObjectTypeTemplate } from "./utils.js"; + +export function createObject(realm: Realm, shape: ObjectTypeTemplate | null, name: string | null): ObjectValue { + let obj = ObjectCreate(realm, realm.intrinsics.ObjectPrototype); + if (shape != null) { + // to get around Flow complaining that shape could be null + let shapeThatIsNotNull = shape; + Object.keys(shape).forEach((id: string) => { + let value = shapeThatIsNotNull[id]; + invariant(value instanceof Value, "creation of object failed due to object containing non-value properties"); + obj.$Set(id, value, obj); + if (name !== null) { + value.intrinsicName = `${name}.${id}`; + } + }); + } + if (name !== null) { + obj.intrinsicName = name; + } + return obj; +} + +export function createArray(realm: Realm, name: string | null): ArrayValue { + let obj = ArrayCreate(realm, 0, realm.intrinsics.ArrayPrototype); + if (name !== null) { + obj.intrinsicName = name; + } + return ((obj: any): ArrayValue); +} + +function _createAbstractArray(realm: Realm, name: string): AbstractValue { + let value = AbstractValue.createFromTemplate(realm, buildExpressionTemplate(name), ArrayValue, [], name); + value.intrinsicName = name; + let template = createArray(realm, name); + template.makePartial(); + template.makeSimple(); + value.values = new ValuesDomain(new Set([template])); + realm.rebuildNestedProperties(value, name); + return value; +} + +function _createAbstractObject( + realm: Realm, + name: string | null, + objectTypes: ObjectTypeTemplate | null +): AbstractValue { + if (name === null) { + name = "unknown"; + } + let value = AbstractValue.createFromTemplate(realm, buildExpressionTemplate(name), ObjectValue, [], name); + value.intrinsicName = name; + let template = createObject(realm, objectTypes, name); + template.makePartial(); + template.makeSimple(); + value.values = new ValuesDomain(new Set([template])); + realm.rebuildNestedProperties(value, name); + return value; +} + +export function createAbstractObject( + realm: Realm, + name: string | null, + objectTypes: ObjectTypeTemplate | null | string +): ObjectValue | AbstractValue { + if (typeof objectTypes === "string") { + invariant( + objectTypes === "empty" || objectTypes === "object", + `Expected an object or a string of "empty" or "object" for createAbstractObject() paramater "objectTypes"` + ); + return _createAbstractObject(realm, name, null); + } + if (objectTypes !== null) { + let propTypeObject = {}; + let objTypes = objectTypes; + invariant(objTypes); + Object.keys(objTypes).forEach(key => { + let value = objTypes[key]; + let propertyName = name !== null ? `${name}.${key}` : key; + if (typeof value === "string") { + if (value === "array") { + propTypeObject[key] = _createAbstractArray(realm, propertyName); + } else if (value === "object") { + propTypeObject[key] = _createAbstractObject(realm, propertyName, null); + } else { + propTypeObject[key] = createAbstractByType(realm, value, propertyName); + } + } else if (typeof value === "object" && value !== null) { + propTypeObject[key] = createAbstractObject(realm, propertyName, value); + } else { + invariant(false, `Unknown propType value of "${value}" for "${key}"`); + } + }); + + return _createAbstractObject(realm, name, propTypeObject); + } else { + return _createAbstractObject(realm, name, null); + } +} + +export function createAbstractByType(realm: Realm, typeNameString: string, name: string): Value { + let type = Value.getTypeFromName(typeNameString); + invariant(type !== undefined, "createAbstractByType() cannot be undefined"); + let value = AbstractValue.createFromTemplate(realm, buildExpressionTemplate(name), type, [], name); + value.intrinsicName = name; + return value; +} diff --git a/src/flow/utils.js b/src/flow/utils.js index d5e7d5ff5..c0fb923ef 100644 --- a/src/flow/utils.js +++ b/src/flow/utils.js @@ -9,10 +9,73 @@ /* @flow */ +import type { typeAnnotation } from "babel-types"; +import invariant from "../invariant.js"; import traverse from "babel-traverse"; import { BabelNode } from "babel-types"; import * as t from "babel-types"; +export type ObjectTypeTemplate = { + [key: string]: string | ObjectTypeTemplate, +}; + +export function flowAnnotationToObjectTypeTemplate(annotation: typeAnnotation): string | ObjectTypeTemplate { + if (annotation.type === "TypeAnnotation") { + return flowAnnotationToObjectTypeTemplate(annotation.typeAnnotation); + } else if (annotation.type === "GenericTypeAnnotation") { + if (annotation.id.type === "Identifier") { + let identifier = annotation.id.name; + + switch (identifier) { + case "Function": + return "function"; + case "Object": + return "object"; + case "Array": + return "array"; + case "any": + case "empty": + return "empty"; + default: + // get the Flow type + invariant(false, "Flow types are currently not supported"); + } + } else { + invariant(false, "unknown generic Flow type annotation node"); + } + } else if (annotation.type === "EmptyTypeAnnotation") { + return "empty"; + } else if (annotation.type === "BooleanTypeAnnotation") { + return "boolean"; + } else if (annotation.type === "StringTypeAnnotation") { + return "string"; + } else if (annotation.type === "NumberTypeAnnotation") { + return "number"; + } else if (annotation.type === "FunctionTypeAnnotation") { + return "function"; + } else if (annotation.type === "ArrayTypeAnnotation") { + return "array"; + } else if (annotation.type === "ObjectTypeAnnotation") { + let obj = {}; + annotation.properties.forEach(property => { + if (property.type === "ObjectTypeProperty") { + if (property.key.type === "Identifier") { + obj[property.key.name] = flowAnnotationToObjectTypeTemplate(property.value); + } else { + invariant(false, "only Identifier nodes are supported in ObjectTypeProperty keys"); + } + } else { + invariant(false, "only ObjectTypeProperty properties are supported in ObjectTypeAnnotation"); + } + }); + return obj; + } else if (annotation.type === "AnyTypeAnnotation") { + return "empty"; + } else { + invariant(false, "unknown Flow type annotation node"); + } +} + // Taken directly from Babel: // https://github.com/babel/babel/blob/cde005422701a69ff21044c138c29a5ad23b6d0a/packages/babel-plugin-transform-flow-strip-types/src/index.js#L32-L107 // Copyright 2015-present Sebastian McKenzie / Babel project (https://github.com/babel) diff --git a/src/intrinsics/react-mocks/global.js b/src/intrinsics/react-mocks/global.js index 52ca7ad4a..35e5c4aeb 100644 --- a/src/intrinsics/react-mocks/global.js +++ b/src/intrinsics/react-mocks/global.js @@ -10,10 +10,11 @@ /* @flow */ import type { Realm } from "../../realm.js"; -import { AbstractValue, NativeFunctionValue, ObjectValue, Value } from "../../values/index.js"; -import { ObjectCreate, CreateDataPropertyOrThrow, GetValue } from "../../methods/index.js"; +import { AbstractValue, NativeFunctionValue, Value, StringValue } from "../../values/index.js"; +import { ObjectCreate } from "../../methods/index.js"; import buildExpressionTemplate from "../../utils/builder.js"; -import { createMockReactComponent, createMockReactCloneElement } from "./mocks.js"; +import { createMockReact } from "./mocks.js"; +import invariant from "../../invariant"; export default function(realm: Realm): void { let global = realm.$GlobalObject; @@ -31,41 +32,25 @@ export default function(realm: Realm): void { enumerable: false, configurable: true, }); - // require("SomeModule") support (makes them abstract) - let type = Value.getTypeFromName("function"); - let requireValue = AbstractValue.createFromTemplate( - realm, - buildExpressionTemplate("require"), - ((type: any): typeof Value), - [], - "require" - ); - requireValue.intrinsicName = "require"; - global.$DefineOwnProperty("require", { - value: requireValue, - writable: true, - enumerable: false, - configurable: true, - }); + // apply React mock (for now just React.Component) - global.$DefineOwnProperty("__createReactMock", { - value: new NativeFunctionValue(realm, "global.__createReactMock", "__createReactMock", 0, (context, []) => { - // React object - let reactValue = ObjectCreate(realm, realm.intrinsics.ObjectPrototype); - reactValue.intrinsicName = "React"; - // React.Component - let reactComponent = GetValue(realm, realm.$GlobalEnv.evaluate(createMockReactComponent(), false)); - reactComponent.intrinsicName = "React.Component"; - let prototypeValue = ((reactComponent: any): ObjectValue).properties.get("prototype"); - if (prototypeValue && prototypeValue.descriptor) { - ((prototypeValue.descriptor.value: any): Value).intrinsicName = `React.Component.prototype`; + global.$DefineOwnProperty("require", { + value: new NativeFunctionValue(realm, "global.require", "require", 0, (context, [requireNameVal]) => { + invariant(requireNameVal instanceof StringValue); + if (requireNameVal.value === "react" || requireNameVal.value === "React") { + return createMockReact(realm); } - CreateDataPropertyOrThrow(realm, reactValue, "Component", reactComponent); - // React.cloneElement - let reactCloneElement = GetValue(realm, realm.$GlobalEnv.evaluate(createMockReactCloneElement(), false)); - reactCloneElement.intrinsicName = "React.cloneElement"; - CreateDataPropertyOrThrow(realm, reactValue, "cloneElement", reactCloneElement); - return reactValue; + let requireName = `require("${requireNameVal.value}")`; + let type = Value.getTypeFromName("function"); + let requireValue = AbstractValue.createFromTemplate( + realm, + buildExpressionTemplate(requireName), + ((type: any): typeof Value), + [], + requireName + ); + requireValue.intrinsicName = requireName; + return requireValue; }), writable: true, enumerable: false, diff --git a/src/intrinsics/react-mocks/mocks.js b/src/intrinsics/react-mocks/mocks.js index a10ca533b..db22387a9 100644 --- a/src/intrinsics/react-mocks/mocks.js +++ b/src/intrinsics/react-mocks/mocks.js @@ -9,89 +9,109 @@ /* @flow */ +import type { Realm } from "../../realm.js"; import { parseExpression } from "babylon"; +import { ObjectValue } from "../../values/index.js"; +import { Get, GetValue } from "../../methods/index.js"; +import invariant from "../../invariant"; -// this a mock of React.Component, to be used for tests -export function createMockReactComponent() { - let componentCode = ` - class Component { +let reactCode = ` + { + Component: class Component { constructor(props, context) { this.props = props || {}; this.context = context || {}; this.refs = {}; this.state = {}; } + isReactComponent() { + return true; + } getChildContext() {} - } - `; - return parseExpression(componentCode, { plugins: ["flow"] }); -} - -// this a mock of React.Component, to be used for tests -export function createMockReactCloneElement() { - let cloneElementCode = ` - function cloneElement(element, config, children) { - var propName; - var RESERVED_PROPS = { - key: true, - ref: true, - __self: true, - __source: true, - }; - var hasOwnProperty = Object.prototype.hasOwnProperty; - var props = Object.assign({}, element.props); - - var key = element.key; - var ref = element.ref; - var self = element._self; - var source = element._source; - var owner = element._owner; - - if (config != null) { - if (config.ref !== undefined) { - // owner = ReactCurrentOwner.current; - } - if (config.key !== undefined) { - key = '' + config.key; - } - var defaultProps; - if (element.type && element.type.defaultProps) { - defaultProps = element.type.defaultProps; - } - for (propName in config) { - if ( - hasOwnProperty.call(config, propName) && - !RESERVED_PROPS.hasOwnProperty(propName) - ) { - if (config[propName] === undefined && defaultProps !== undefined) { - // Resolve default props - props[propName] = defaultProps[propName]; - } else { - props[propName] = config[propName]; + }, + createElement: function() { + // TODO + }, + cloneElement(element, config, children) { + var propName; + var RESERVED_PROPS = { + key: true, + ref: true, + __self: true, + __source: true, + }; + var hasOwnProperty = Object.prototype.hasOwnProperty; + var props = Object.assign({}, element.props); + + var key = element.key; + var ref = element.ref; + var self = element._self; + var source = element._source; + var owner = element._owner; + + if (config != null) { + if (config.ref !== undefined) { + // owner = ReactCurrentOwner.current; + } + if (config.key !== undefined) { + key = '' + config.key; + } + var defaultProps; + if (element.type && element.type.defaultProps) { + defaultProps = element.type.defaultProps; + } + for (propName in config) { + if ( + hasOwnProperty.call(config, propName) && + !RESERVED_PROPS.hasOwnProperty(propName) + ) { + if (config[propName] === undefined && defaultProps !== undefined) { + // Resolve default props + props[propName] = defaultProps[propName]; + } else { + props[propName] = config[propName]; + } } } } - } - var childrenLength = arguments.length - 2; - if (childrenLength === 1) { - props.children = children; - } else if (childrenLength > 1) { - var childArray = Array(childrenLength); - for (var i = 0; i < childrenLength; i++) { - childArray[i] = arguments[i + 2]; + var childrenLength = arguments.length - 2; + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + var childArray = Array(childrenLength); + for (var i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 2]; + } + props.children = childArray; } - props.children = childArray; - } - - return { - $$typeof: element.$$typeof, - type: element.type, - key: key, - ref: ref, - props: props, - _owner: owner, - }; + + return { + $$typeof: element.$$typeof, + type: element.type, + key: key, + ref: ref, + props: props, + _owner: owner, + }; + }, } - `; - return parseExpression(cloneElementCode, { plugins: ["flow"] }); +`; +let reactAst = parseExpression(reactCode, { plugins: ["flow"] }); + +export function createMockReact(realm: Realm): ObjectValue { + let reactValue = GetValue(realm, realm.$GlobalEnv.evaluate(reactAst, false)); + reactValue.intrinsicName = `require("react")`; + invariant(reactValue instanceof ObjectValue); + + let reactComponentValue = Get(realm, reactValue, "Component"); + reactComponentValue.intrinsicName = `require("react").Component`; + invariant(reactComponentValue instanceof ObjectValue); + + let reactComponentPrototypeValue = Get(realm, reactComponentValue, "prototype"); + reactComponentPrototypeValue.intrinsicName = `require("react").Component.prototype`; + + let reactCloneElementValue = Get(realm, reactValue, "cloneElement"); + reactCloneElementValue.intrinsicName = `require("react").cloneElement`; + + return reactValue; } diff --git a/src/react/branching.js b/src/react/branching.js new file mode 100644 index 000000000..628f2eac1 --- /dev/null +++ b/src/react/branching.js @@ -0,0 +1,101 @@ +/** + * 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 */ + +import { Realm } from "../realm.js"; +import { + ECMAScriptSourceFunctionValue, + Value, + UndefinedValue, + StringValue, + NumberValue, + BooleanValue, + NullValue, + AbstractValue, + ArrayValue, + ObjectValue, +} from "../values/index.js"; +import { type ReactSerializerState } from "../serializer/types.js"; +import { isReactElement, addKeyToReactElement, mapOverArrayValue } from "./utils"; +import { ExpectedBailOut } from "./reconcilation.js"; + +// Branch status is used for when Prepack returns an abstract value from a render +// that results in a conditional path occuring. This can be problematic for reconcilation +// as the reconciler then needs to understand if this is the start of a new branch, or if +// it's actually deep into an existing branch. If it's a new branch, we need to apply +// keys to the root JSX element so that it keeps it identity (because we're folding trees). +// Furthermore, we also need to bail-out of folding class components where they have lifecycle +// events, as we can't merge lifecycles of mutliple trees when branched reliably +export type BranchStatusEnum = "NO_BRANCH" | "NEW_BRANCH" | "BRANCH"; + +// Branch state is used to capture branched ReactElements so they can be analyzed and compared +// once all branches have been processed. This allows us to add keys to the respective ReactElement +// objects depending on various heuristics (if they have the same "type" for example) +// A new branch state is created on a branch status of "NEW_BRANCH" and is reset to null once the branch is no +// longer new +export class BranchState { + constructor() { + this._branchesToValidate = []; + } + _applyBranchedLogicValue(realm: Realm, reactSerializerState: ReactSerializerState, value: Value): void { + if ( + value instanceof StringValue || + value instanceof NumberValue || + value instanceof BooleanValue || + value instanceof NullValue || + value instanceof UndefinedValue + ) { + // terminal values + } else if (value instanceof ObjectValue && isReactElement(value)) { + addKeyToReactElement(realm, reactSerializerState, value); + } else if (value instanceof ArrayValue) { + mapOverArrayValue(realm, value, elementValue => { + this._applyBranchedLogicValue(realm, reactSerializerState, elementValue); + }); + } else if (value instanceof AbstractValue) { + let length = value.args.length; + if (length > 0) { + for (let i = 0; i < length; i++) { + this._applyBranchedLogicValue(realm, reactSerializerState, value.args[i]); + } + } + } else { + throw new ExpectedBailOut("Unsupported value encountered when applying branched logic to values"); + } + } + applyBranchedLogic(realm: Realm, reactSerializerState: ReactSerializerState): void { + let reactElementType; + let applyBranchedLogic = false; + + for (let i = 0; i < this._branchesToValidate.length; i++) { + let { type } = this._branchesToValidate[i]; + if (reactElementType === undefined) { + reactElementType = type; + } else if (type !== reactElementType) { + // the types of the ReactElements do not match, so apply branch logic + applyBranchedLogic = true; + break; + } + } + if (applyBranchedLogic) { + for (let i = 0; i < this._branchesToValidate.length; i++) { + this._applyBranchedLogicValue(realm, reactSerializerState, this._branchesToValidate[i].value); + } + } + } + captureBranchedValue(type: StringValue | ECMAScriptSourceFunctionValue, value: Value): Value { + this._branchesToValidate.push({ type, value }); + return value; + } + _branchesToValidate: Array<{ + type: StringValue | ECMAScriptSourceFunctionValue, + value: Value, + }>; +} diff --git a/src/react/reconcilation.js b/src/react/reconcilation.js new file mode 100644 index 000000000..bdd404cb0 --- /dev/null +++ b/src/react/reconcilation.js @@ -0,0 +1,315 @@ +/** + * 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 */ + +import { Realm, type Effects } from "../realm.js"; +import { ModuleTracer } from "../serializer/modules.js"; +import { + ECMAScriptSourceFunctionValue, + Value, + UndefinedValue, + StringValue, + NumberValue, + BooleanValue, + NullValue, + AbstractValue, + ArrayValue, + ObjectValue, +} from "../values/index.js"; +import { ReactStatistics, type ReactSerializerState } from "../serializer/types.js"; +import { isReactElement, valueIsClassComponent, mapOverArrayValue } from "./utils"; +import { Get } from "../methods/index.js"; +import invariant from "../invariant.js"; +import { flowAnnotationToObjectTypeTemplate } from "../flow/utils.js"; +import * as t from "babel-types"; +import type { BabelNodeIdentifier } from "babel-types"; +import { createAbstractObject } from "../flow/abstractObjectFactories.js"; +import { CompilerDiagnostic, FatalError } from "../errors.js"; +import { BranchState, type BranchStatusEnum } from "./branching.js"; + +// ExpectedBailOut is like an error, that gets thrown during the reconcilation phase +// allowing the reconcilation to continue on other branches of the tree, the message +// given to ExpectedBailOut will be assigned to the value.$BailOutReason property and serialized +// as a comment in the output source to give the user hints as to what they need to do +// to fix the bail-out case +export class ExpectedBailOut { + message: string; + constructor(message: string) { + this.message = message; + } +} + +function getInitialProps(realm: Realm, componentType: ECMAScriptSourceFunctionValue): ObjectValue | AbstractValue { + let propsName = null; + let propTypes = null; + if (valueIsClassComponent(realm, componentType)) { + // it's a class component, so we need to check the type on for props of the component prototype + // as we don't support class components yet, throw a fatal error + throw new ExpectedBailOut("class components not yet supported"); + } else { + // otherwise it's a functional component, where the first paramater of the function is "props" (if it exists) + if (componentType.$FormalParameters.length > 0) { + let firstParam = componentType.$FormalParameters[0]; + if (t.isIdentifier(firstParam)) { + propsName = ((firstParam: any): BabelNodeIdentifier).name; + } + let propsTypeAnnotation = firstParam.typeAnnotation !== undefined && firstParam.typeAnnotation; + // we expect that if there's a props paramater, it should always have Flow annotations + if (!propsTypeAnnotation) { + throw new ExpectedBailOut(`root component missing Flow type annotations for the "props" paramater`); + } + propTypes = flowAnnotationToObjectTypeTemplate(propsTypeAnnotation); + } + } + return createAbstractObject(realm, propsName, propTypes); +} + +function getInitialContext(realm: Realm, componentType: ECMAScriptSourceFunctionValue): ObjectValue | AbstractValue { + let contextName = null; + let contextTypes = null; + if (valueIsClassComponent(realm, componentType)) { + // it's a class component, so we need to check the type on for context of the component prototype + // as we don't support class components yet, throw a fatal error + throw new ExpectedBailOut("class components not yet supported"); + } else { + // otherwise it's a functional component, where the second paramater of the function is "context" (if it exists) + if (componentType.$FormalParameters.length > 1) { + let secondParam = componentType.$FormalParameters[1]; + if (t.isIdentifier(secondParam)) { + contextName = ((secondParam: any): BabelNodeIdentifier).name; + } + let contextTypeAnnotation = secondParam.typeAnnotation !== undefined && secondParam.typeAnnotation; + // we expect that if there's a context param, it should always have Flow annotations + if (!contextTypeAnnotation) { + throw new ExpectedBailOut(`root component missing Flow type annotations for the "context" paramater`); + } + contextTypes = flowAnnotationToObjectTypeTemplate(contextTypeAnnotation); + } + } + return createAbstractObject(realm, contextName, contextTypes); +} + +export class Reconciler { + constructor( + realm: Realm, + moduleTracer: ModuleTracer, + statistics: ReactStatistics, + reactSerializerState: ReactSerializerState + ) { + this.realm = realm; + this.moduleTracer = moduleTracer; + this.statistics = statistics; + this.reactSerializerState = reactSerializerState; + } + + realm: Realm; + moduleTracer: ModuleTracer; + statistics: ReactStatistics; + reactSerializerState: ReactSerializerState; + + render(componentType: ECMAScriptSourceFunctionValue): Effects { + return this.realm.wrapInGlobalEnv(() => + // TODO: (sebmarkbage): You could use the return value of this to detect if there are any mutations on objects other + // than newly created ones. Then log those to the error logger. That'll help us track violations in + // components. :) + this.realm.evaluateForEffects(() => { + // initialProps and initialContext are created from Flow types from: + // - if a functional component, the 1st and 2nd paramater of function + // - if a class component, use this.props and this.context + // if there are no Flow types for props or context, we will throw a + // FatalError, unless it's a functional component that has no paramater + // i.e let MyComponent = () =>