main
ts 153 lines 4.15 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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 import type * as BabelCore from '@babel/core';
9 import {transformFromAstSync} from '@babel/core';
10 import * as BabelParser from '@babel/parser';
11 import BabelPluginReactCompiler, {
12 ErrorSeverity,
13 type CompilerErrorDetailOptions,
14 type PluginOptions,
15 } from 'babel-plugin-react-compiler/src';
16 import {LoggerEvent as RawLoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint';
17 import chalk from 'chalk';
18
19 type LoggerEvent = RawLoggerEvent & {filename: string | null};
20
21 const SucessfulCompilation: Array<LoggerEvent> = [];
22 const ActionableFailures: Array<LoggerEvent> = [];
23 const OtherFailures: Array<LoggerEvent> = [];
24
25 const logger = {
26 logEvent(filename: string | null, rawEvent: RawLoggerEvent) {
27 const event = {...rawEvent, filename};
28 switch (event.kind) {
29 case 'CompileSuccess': {
30 SucessfulCompilation.push(event);
31 return;
32 }
33 case 'CompileError': {
34 if (isActionableDiagnostic(event.detail)) {
35 ActionableFailures.push(event);
36 return;
37 }
38 OtherFailures.push(event);
39 return;
40 }
41 case 'CompileDiagnostic':
42 case 'PipelineError':
43 OtherFailures.push(event);
44 return;
45 }
46 },
47 };
48
49 const COMPILER_OPTIONS: PluginOptions = {
50 noEmit: true,
51 compilationMode: 'infer',
52 panicThreshold: 'critical_errors',
53 logger,
54 };
55
56 function isActionableDiagnostic(detail: CompilerErrorDetailOptions) {
57 switch (detail.severity) {
58 case ErrorSeverity.InvalidReact:
59 case ErrorSeverity.InvalidJS:
60 return true;
61 case ErrorSeverity.InvalidConfig:
62 case ErrorSeverity.Invariant:
63 case ErrorSeverity.CannotPreserveMemoization:
64 case ErrorSeverity.Todo:
65 return false;
66 default:
67 throw new Error(`Unhandled error severity \`${detail.severity}\``);
68 }
69 }
70
71 function runBabelPluginReactCompiler(
72 text: string,
73 file: string,
74 language: 'flow' | 'typescript',
75 options: PluginOptions | null,
76 ): BabelCore.BabelFileResult {
77 const ast = BabelParser.parse(text, {
78 sourceFilename: file,
79 plugins: [language, 'jsx'],
80 sourceType: 'module',
81 });
82 const result = transformFromAstSync(ast, text, {
83 filename: file,
84 highlightCode: false,
85 retainLines: true,
86 plugins: [[BabelPluginReactCompiler, options]],
87 sourceType: 'module',
88 configFile: false,
89 babelrc: false,
90 });
91 if (result?.code == null) {
92 throw new Error(
93 `Expected BabelPluginReactForget to codegen successfully, got: ${result}`,
94 );
95 }
96 return result;
97 }
98
99 function compile(sourceCode: string, filename: string) {
100 try {
101 runBabelPluginReactCompiler(
102 sourceCode,
103 filename,
104 'typescript',
105 COMPILER_OPTIONS,
106 );
107 } catch {}
108 }
109
110 const JsFileExtensionRE = /(js|ts|jsx|tsx)$/;
111
112 /**
113 * Counts unique source locations (filename + function definition location)
114 * in source.
115 * The compiler currently occasionally emits multiple error events for a
116 * single file (e.g. to report multiple rules of react violations in the
117 * same pass).
118 * TODO: enable non-destructive `CompilerDiagnostic` logging in dev mode,
119 * and log a "CompilationStart" event for every function we begin processing.
120 */
121 function countUniqueLocInEvents(events: Array<LoggerEvent>): number {
122 const seenLocs = new Set<string>();
123 let count = 0;
124 for (const e of events) {
125 if (e.filename != null && e.fnLoc != null) {
126 seenLocs.add(`${e.filename}:${e.fnLoc.start}:${e.fnLoc.end}`);
127 } else {
128 // failed to dedup due to lack of source locations
129 count++;
130 }
131 }
132 return count + seenLocs.size;
133 }
134
135 export default {
136 run(source: string, path: string): void {
137 if (JsFileExtensionRE.exec(path) !== null) {
138 compile(source, path);
139 }
140 },
141
142 report(): void {
143 const totalComponents =
144 SucessfulCompilation.length +
145 countUniqueLocInEvents(OtherFailures) +
146 countUniqueLocInEvents(ActionableFailures);
147 console.log(
148 chalk.green(
149 `Successfully compiled ${SucessfulCompilation.length} out of ${totalComponents} components.`,
150 ),
151 );
152 },
153 };