Re-adds Flow parsing and stripping when option is enabled

Summary:
Release notes: adds option to parse and strip Flow annotations

In a [tweet I made at the end of last year](https://twitter.com/trueadm/status/944908776896978946) I linked to a REPL example that contained Flow type annotations. The example is now broken as we removed Flow type annotation parsing. :(

This PR adds this back behind the option `stripFlow` which is now enabled by default on the REPL and for React tests. I've also added a test to match the REPL for regression checking so this doesn't happen.
Closes https://github.com/facebook/prepack/pull/1439

Differential Revision: D6976663

Pulled By: trueadm

fbshipit-source-id: 47e28bc0986f1a1a252180f26200ce32314a4e5b
This commit is contained in:
Dominic Gannaway 2018-02-13 08:34:42 -08:00 committed by Facebook Github Bot
parent d8908e31e6
commit 414cc67b93
10 changed files with 152 additions and 0 deletions

View File

@ -329,6 +329,13 @@ ReactStatistics {
}
`;
exports[`Test React (JSX) fb-www mocks repl example 1`] = `
ReactStatistics {
"inlinedComponents": 6,
"optimizedTrees": 1,
}
`;
exports[`Test React (create-element) Class component folding Classes with state 1`] = `
ReactStatistics {
"inlinedComponents": 0,
@ -657,3 +664,10 @@ ReactStatistics {
"optimizedTrees": 2,
}
`;
exports[`Test React (create-element) fb-www mocks repl example 1`] = `
ReactStatistics {
"inlinedComponents": 6,
"optimizedTrees": 1,
}
`;

View File

@ -53,6 +53,7 @@ function runTestSuite(outputJsx) {
inlineExpressions: true,
omitInvariants: true,
abstractEffectsInAdditionalFunctions: true,
stripFlow: true,
};
function compileSourceWithPrepack(source) {
@ -374,6 +375,10 @@ function runTestSuite(outputJsx) {
it("fb-www 8", async () => {
await runTest(directory, "fb8.js");
});
it("repl example", async () => {
await runTest(directory, "repl-example.js");
});
});
});
}

View File

@ -33,6 +33,7 @@ export type RealmOptions = {
maxStackDepth?: number,
reactEnabled?: boolean,
reactOutput?: ReactOutputTypes,
stripFlow?: boolean,
abstractEffectsInAdditionalFunctions?: boolean,
};

View File

@ -47,6 +47,7 @@ export type PrepackOptions = {|
initializeMoreModules?: boolean,
statsFile?: string,
strictlyMonotonicDateNow?: boolean,
stripFlow?: boolean,
timeout?: number,
trace?: boolean,
uniqueSuffix?: string,
@ -70,6 +71,7 @@ export function getRealmOptions({
serialize = !residual,
check,
strictlyMonotonicDateNow,
stripFlow,
timeout,
maxStackDepth,
}: PrepackOptions): RealmOptions {
@ -88,6 +90,7 @@ export function getRealmOptions({
serialize,
check,
strictlyMonotonicDateNow,
stripFlow,
timeout,
maxStackDepth,
};

View File

@ -191,6 +191,8 @@ export class Realm {
hoistableFunctions: new WeakMap(),
};
this.stripFlow = opts.stripFlow || false;
this.fbLibraries = {
other: new Map(),
react: undefined,
@ -255,6 +257,7 @@ export class Realm {
output?: ReactOutputTypes,
symbols: Map<ReactSymbolTypes, SymbolValue>,
};
stripFlow: boolean;
fbLibraries: {
other: Map<string, AbstractValue>,

View File

@ -23,6 +23,7 @@ import type { ReactSerializerState, SerializedResult } from "./types.js";
import { Functions } from "./functions.js";
import { Logger } from "../utils/logger.js";
import { Modules } from "../utils/modules.js";
import { stripFlowTypeAnnotations } from "../utils/flow.js";
import { LoggingTracer } from "./LoggingTracer.js";
import { ResidualHeapVisitor } from "./ResidualHeapVisitor.js";
import { ResidualHeapSerializer } from "./ResidualHeapSerializer.js";
@ -224,6 +225,9 @@ export class Serializer {
);
let ast = residualHeapSerializer.serialize();
if (this.realm.stripFlow) {
stripFlowTypeAnnotations(ast);
}
// the signature for generate is not complete, hence the any
let generated = generate(ast, { sourceMaps: sourceMaps }, (code: any));

78
src/utils/flow.js Normal file
View File

@ -0,0 +1,78 @@
/**
* 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 traverse from "babel-traverse";
import { BabelNode } from "babel-types";
import * as t from "babel-types";
// 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)
// only the lines reflected in the above were used
export function stripFlowTypeAnnotations(ast: BabelNode): void {
traverse(
ast,
{
ImportDeclaration(path) {
if (!path.node.specifiers.length) return;
let typeCount = 0;
path.node.specifiers.forEach(({ importKind }) => {
if (importKind === "type" || importKind === "typeof") {
typeCount++;
}
});
if (typeCount === path.node.specifiers.length) {
path.remove();
}
},
Flow(path) {
path.remove();
},
ClassProperty(path) {
path.node.variance = null;
path.node.typeAnnotation = null;
if (!path.node.value) path.remove();
},
Class(path) {
path.node.implements = null;
path.get("body.body").forEach(child => {
if (child.isClassProperty()) {
child.node.typeAnnotation = null;
if (!child.node.value) child.remove();
}
});
},
AssignmentPattern({ node }) {
node.left.optional = false;
},
Function({ node }) {
for (let i = 0; i < node.params.length; i++) {
const param = node.params[i];
param.optional = false;
if (param.type === "AssignmentPattern") {
param.left.optional = false;
}
}
node.predicate = null;
},
TypeCastExpression(path) {
let { node } = path;
do {
node = node.expression;
} while (t.isTypeCastExpression(node));
path.replaceWith(node);
},
},
undefined,
(undefined: any),
undefined
);
}

View File

@ -31,6 +31,9 @@ export default function(
if (realm.react.enabled) {
plugins.push("jsx");
}
if (realm.stripFlow) {
plugins.push("flow");
}
let ast = parse(code, { filename, sourceType, startLine, plugins });
traverseFast(ast, node => {
invariant(node.loc);

View File

@ -0,0 +1,35 @@
var React = require('React');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function Yar(props) {
return <a href={props.href}><em>{props.name}</em></a>
}
function Bar(props) {
return <div><span style={{color: "red"}}>Here's a link</span>: <Yar {...props} /></div>;
}
// for now, we require inline Flow type annotations on the root component
// for its props (if it has any)
function Foo(props: {href: string}) {
var collection = [
{ href: props.href, name: "First Item" },
{ href: props.href, name: "Second Item" },
{ href: props.href, name: "Third Item" },
];
return (
<div>
{collection.map(item => <Bar {...item} /> )}
</div>
);
}
// this is a special Prepack function hook
// that tells Prepack the root of a React component tree
if (this.__registerReactComponentRoot) {
__registerReactComponentRoot(Foo);
}
window.Foo = Foo;

View File

@ -63,6 +63,12 @@ var optionsConfig = [
defaultVal: "jsx",
description: "Specifies the serialization output of JSX nodes when React mode is enabled."
},
{
type: "boolean",
name: "stripFlow",
defaultVal: true,
description: "Removes Flow type annotations from the output."
},
];
var demos = [];