1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+"use strict";
9
+
10
+import * as t from "@babel/types";
11
+import { parse } from "@babel/parser";
12
+import traverse, { NodePath } from "@babel/traverse";
13
+import path from "path";
14
+
15
+import * as CompilerPipeline from "../CompilerPipeline";
16
+import { Effect, ValueKind } from "../HIR";
17
+import { printFunction } from "../HIR/PrintHIR";
18
+import { EnvironmentOptions } from "../HIR/Environment";
19
+import { Result, Ok, Err } from "../Utils/Result";
20
+import { toggleLogging } from "../Utils/logger";
21
+import generateTestsFromFixtures from "./test-utils/generateTestsFromFixtures";
22
+import invariant from "invariant";
23
+
24
+// TODO: make pipeline names an enum
25
+// Currently, this is the last pass that operates on hir
26
+const LAST_HIR_PASS = "LeaveSSA";
27
+
28
+describe("React Forget (HIR version)", () => {
29
+ generateTestsFromFixtures(
30
+ path.join(__dirname, "fixtures", "hir"),
31
+ (input, file, options) => {
32
+ if (options.debug) {
33
+ toggleLogging(options.debug);
34
+ }
35
+
36
+ const compileResult = compile(input, options.language, {
37
+ customHooks: new Map([
38
+ [
39
+ "useFreeze",
40
+ {
41
+ name: "useFreeze",
42
+ kind: "Custom",
43
+ valueKind: ValueKind.Frozen,
44
+ effectKind: Effect.Freeze,
45
+ },
46
+ ],
47
+ ]),
48
+ });
49
+
50
+ if (compileResult.isErr()) {
51
+ const error = compileResult.unwrapErr();
52
+ error.message = `Expected fixture '${file}' to succeed but it failed with error:\n\n${error.message}`;
53
+ throw error;
54
+ }
55
+ const transformedSources = compileResult.unwrap().map(({ hir }) => {
56
+ return `
57
+## HIR
58
+
59
+${wrapWithTripleBackticks(hir, "javascript")}
60
+
61
+ `.trim();
62
+ });
63
+ if (transformedSources === null || transformedSources.length === 0) {
64
+ throw new Error(`Expected at least one output for file '${file}'.`);
65
+ }
66
+ return `
67
+## Input
68
+
69
+${wrapWithTripleBackticks(input, "javascript")}
70
+
71
+${transformedSources.join("\n")}
72
+ `;
73
+ }
74
+ );
75
+});
76
+
77
+type CompileResult = {
78
+ hir: string;
79
+};
80
+
81
+function compile(
82
+ source: string,
83
+ language: "flow" | "typescript",
84
+ compilerEnv: Partial<EnvironmentOptions>
85
+): Result<Array<CompileResult>, Error> {
86
+ const transformedFns = new Array<CompileResult>();
87
+ const babelAsts = parseFunctions(source, language);
88
+ if (babelAsts.isErr()) {
89
+ return babelAsts;
90
+ }
91
+
92
+ try {
93
+ for (const ast of babelAsts.unwrap()) {
94
+ let hirString: string | null = null;
95
+ for (const result of CompilerPipeline.run(ast, compilerEnv)) {
96
+ switch (result.kind) {
97
+ case "hir": {
98
+ if (result.name === LAST_HIR_PASS) {
99
+ hirString = printFunction(result.value);
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ }
105
+ invariant(
106
+ hirString !== null,
107
+ `Expected to find pass with name ${LAST_HIR_PASS}`
108
+ );
109
+ transformedFns.push({ hir: hirString });
110
+ }
111
+ return Ok(transformedFns);
112
+ } catch (e) {
113
+ return Err(e);
114
+ }
115
+}
116
+
117
+function parseFunctions(
118
+ source: string,
119
+ language: "flow" | "typescript"
120
+): Result<Array<NodePath<t.FunctionDeclaration>>, Error> {
121
+ const items: Array<NodePath<t.FunctionDeclaration>> = [];
122
+ try {
123
+ const ast = parse(source, {
124
+ plugins: [language, "jsx"],
125
+ sourceType: "module",
126
+ });
127
+ traverse(ast, {
128
+ // Only collect top-level functions
129
+ FunctionDeclaration(nodePath) {
130
+ items.push(nodePath);
131
+ nodePath.skip();
132
+ },
133
+ });
134
+ } catch (e) {
135
+ return Err(e);
136
+ }
137
+ return Ok(items);
138
+}
139
+
140
+function wrapWithTripleBackticks(s: string, ext?: string) {
141
+ return `\`\`\`${ext ?? ""}
142
+${s}
143
+\`\`\``;
144
+}