@samitouri / QOS-React / commits / 4ce5c56fee

[healthcheck] Refactor checks into separate files

Makes it easier to extend later, if we want to add more checks. ghstack-source-id: 6fb3435555f1b988e1a185bfda8be9418eb622c5 Pull Request resolved: https://github.com/facebook/react-forget/pull/2924

Sathya Gunsasekaran committed May 1, 2024 at 16:05 UTC 4ce5c56fee9e960b8b0702dc114455cbfa5d4e95
4 files changed +177 -120
compiler/packages/healthcheck/src/checks/libraryCompat.ts new
+37
@@ -0,0 +1,37 @@
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 chalk from "chalk";
9 +import { config } from "../config";
10 +
11 +const packageJsonRE = /package\.json$/;
12 +const knownIncompatibleLibrariesUsage = new Set();
13 +
14 +export default {
15 + run(source: string, path: string): void {
16 + if (packageJsonRE.exec(path) !== null) {
17 + const contents = JSON.parse(source);
18 + const deps = contents.dependencies;
19 + for (const library of config.knownIncompatibleLibraries) {
20 + if (Object.hasOwn(deps, library)) {
21 + knownIncompatibleLibrariesUsage.add(library);
22 + }
23 + }
24 + }
25 + },
26 +
27 + report(): void {
28 + if (knownIncompatibleLibrariesUsage.size > 0) {
29 + console.log(chalk.red(`Found the following incompatible libraries:`));
30 + for (const library of knownIncompatibleLibrariesUsage) {
31 + console.log(library);
32 + }
33 + } else {
34 + console.log(chalk.green(`Found no usage of incompatible libraries.`));
35 + }
36 + },
37 +};
compiler/packages/healthcheck/src/checks/reactCompiler.ts new
+97
@@ -0,0 +1,97 @@
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 {
9 + ErrorSeverity,
10 + runReactForgetBabelPlugin,
11 + type CompilerErrorDetailOptions,
12 + type PluginOptions,
13 +} from "babel-plugin-react-forget/src";
14 +import { LoggerEvent } from "babel-plugin-react-forget/src/Entrypoint";
15 +import chalk from "chalk";
16 +
17 +const SucessfulCompilation: Array<LoggerEvent> = [];
18 +const ActionableFailures: Array<LoggerEvent> = [];
19 +const OtherFailures: Array<LoggerEvent> = [];
20 +
21 +const logger = {
22 + logEvent(_: string | null, event: LoggerEvent) {
23 + switch (event.kind) {
24 + case "CompileSuccess": {
25 + SucessfulCompilation.push(event);
26 + return;
27 + }
28 + case "CompileError": {
29 + if (isActionableDiagnostic(event.detail)) {
30 + ActionableFailures.push(event);
31 + return;
32 + }
33 + OtherFailures.push(event);
34 + return;
35 + }
36 + case "CompileDiagnostic":
37 + case "PipelineError":
38 + OtherFailures.push(event);
39 + return;
40 + }
41 + },
42 +};
43 +
44 +const COMPILER_OPTIONS: Partial<PluginOptions> = {
45 + noEmit: true,
46 + compilationMode: "infer",
47 + panicThreshold: "critical_errors",
48 + logger,
49 +};
50 +
51 +function isActionableDiagnostic(detail: CompilerErrorDetailOptions) {
52 + switch (detail.severity) {
53 + case ErrorSeverity.InvalidReact:
54 + case ErrorSeverity.InvalidJS:
55 + return true;
56 + case ErrorSeverity.InvalidConfig:
57 + case ErrorSeverity.Invariant:
58 + case ErrorSeverity.CannotPreserveMemoization:
59 + case ErrorSeverity.Todo:
60 + return false;
61 + default:
62 + throw new Error("Unhandled error severity");
63 + }
64 +}
65 +
66 +function compile(sourceCode: string, filename: string) {
67 + try {
68 + runReactForgetBabelPlugin(
69 + sourceCode,
70 + filename,
71 + "typescript",
72 + COMPILER_OPTIONS
73 + );
74 + } catch {}
75 +}
76 +
77 +const JsFileExtensionRE = /(js|ts|jsx|tsx|mjs)$/;
78 +
79 +export default {
80 + run(source: string, path: string): void {
81 + if (JsFileExtensionRE.exec(path) !== null) {
82 + compile(source, path);
83 + }
84 + },
85 +
86 + report(): void {
87 + const totalComponents =
88 + SucessfulCompilation.length +
89 + OtherFailures.length +
90 + ActionableFailures.length;
91 + console.log(
92 + chalk.green(
93 + `Successfully compiled ${SucessfulCompilation.length} out of ${totalComponents} components.`
94 + )
95 + );
96 + },
97 +};
compiler/packages/healthcheck/src/checks/strictMode.ts new
+32
@@ -0,0 +1,32 @@
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 chalk from "chalk";
9 +
10 +const JsFileExtensionRE = /(js|ts|jsx|tsx|mjs)$/;
11 +const StrictModeRE = /\<StrictMode\>/;
12 +let StrictModeUsage = false;
13 +
14 +export default {
15 + run(source: string, path: string): void {
16 + if (JsFileExtensionRE.exec(path) === null) {
17 + return;
18 + }
19 +
20 + if (!StrictModeUsage) {
21 + StrictModeUsage = StrictModeRE.exec(source) !== null;
22 + }
23 + },
24 +
25 + report(): void {
26 + if (StrictModeUsage) {
27 + console.log(chalk.green("StrictMode usage found."));
28 + } else {
29 + console.log(chalk.red("StrictMode usage not found."));
30 + }
31 + },
32 +};
compiler/packages/healthcheck/src/index.ts
+11 -120
@@ -5,82 +5,13 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {
9 - ErrorSeverity,
10 - runReactForgetBabelPlugin,
11 - type CompilerErrorDetailOptions,
12 - type PluginOptions,
13 -} from "babel-plugin-react-forget/src";
14 -import { LoggerEvent } from "babel-plugin-react-forget/src/Entrypoint";
15 -import chalk from "chalk";
8 import { glob } from "fast-glob";
9 import * as fs from "fs/promises";
10 import ora from "ora";
11 import yargs from "yargs/yargs";
20 -import { config } from "./config";
21 -
22 -const SUCCESS: Array<LoggerEvent> = [];
23 -const ACTIONABLE_FAILURES: Array<LoggerEvent> = [];
24 -const OTHER_FAILURES: Array<LoggerEvent> = [];
25 -let STRICT_MODE_USAGE = false;
26 -
27 -const StrictModeRE = /\<StrictMode\>/;
28 -
29 -const logger = {
30 - logEvent(_: string | null, event: LoggerEvent) {
31 - switch (event.kind) {
32 - case "CompileSuccess": {
33 - SUCCESS.push(event);
34 - return;
35 - }
36 - case "CompileError": {
37 - if (isActionableDiagnostic(event.detail)) {
38 - ACTIONABLE_FAILURES.push(event);
39 - return;
40 - }
41 - OTHER_FAILURES.push(event);
42 - return;
43 - }
44 - case "CompileDiagnostic":
45 - case "PipelineError":
46 - OTHER_FAILURES.push(event);
47 - return;
48 - }
49 - },
50 -};
51 -
52 -const COMPILER_OPTIONS: Partial<PluginOptions> = {
53 - noEmit: true,
54 - compilationMode: "infer",
55 - panicThreshold: "critical_errors",
56 - logger,
57 -};
58 -
59 -function isActionableDiagnostic(detail: CompilerErrorDetailOptions) {
60 - switch (detail.severity) {
61 - case ErrorSeverity.InvalidReact:
62 - case ErrorSeverity.InvalidJS:
63 - return true;
64 - case ErrorSeverity.InvalidConfig:
65 - case ErrorSeverity.Invariant:
66 - case ErrorSeverity.CannotPreserveMemoization:
67 - case ErrorSeverity.Todo:
68 - return false;
69 - default:
70 - throw new Error("Unhandled error severity");
71 - }
72 -}
73 -
74 -function compile(sourceCode: string, filename: string) {
75 - try {
76 - runReactForgetBabelPlugin(
77 - sourceCode,
78 - filename,
79 - "typescript",
80 - COMPILER_OPTIONS
81 - );
82 - } catch {}
83 -}
12 +import libraryCompatCheck from "./checks/libraryCompat";
13 +import reactCompilerCheck from "./checks/reactCompiler";
14 +import strictModeCheck from "./checks/strictMode";
15
16 async function main() {
17 const argv = yargs(process.argv.slice(2))
@@ -93,14 +24,9 @@ async function main() {
24 })
25 .parseSync();
26
96 - const spinner = ora("Compiling").start();
27 + const spinner = ora("Checking").start();
28 let src = argv.src;
29
99 - // no file extension specified
100 - if (!src.includes(".")) {
101 - src = src;
102 - }
103 -
30 const globOptions = {
31 onlyFiles: true,
32 ignore: [
@@ -115,53 +41,18 @@ async function main() {
41 ],
42 };
43
118 - const jsFileExtensionRE = /(js|ts|jsx|tsx|mjs)$/;
119 - const packageJsonRE = /package\.json$/;
120 - const knownIncompatibleLibrariesUsage = new Set();
121 -
44 for (const path of await glob(src, globOptions)) {
45 const source = await fs.readFile(path, "utf-8");
124 - if (jsFileExtensionRE.exec(path) !== null) {
125 - spinner.text = `Compiling ${path}`;
126 - compile(source, path);
127 -
128 - if (!STRICT_MODE_USAGE) {
129 - STRICT_MODE_USAGE = StrictModeRE.exec(source) !== null;
130 - }
131 - } else if (packageJsonRE.exec(path) !== null) {
132 - const contents = JSON.parse(source);
133 - const deps = contents.dependencies;
134 - for (const library of config.knownIncompatibleLibraries) {
135 - if (Object.hasOwn(deps, library)) {
136 - knownIncompatibleLibrariesUsage.add(library);
137 - }
138 - }
139 - }
46 + spinner.text = `Checking ${path}`;
47 + reactCompilerCheck.run(source, path);
48 + strictModeCheck.run(source, path);
49 + libraryCompatCheck.run(source, path);
50 }
51 spinner.stop();
52
143 - const totalComponents =
144 - SUCCESS.length + OTHER_FAILURES.length + ACTIONABLE_FAILURES.length;
145 - console.log(
146 - chalk.green(
147 - `Successfully compiled ${SUCCESS.length} out of ${totalComponents} components.`
148 - )
149 - );
150 -
151 - if (STRICT_MODE_USAGE) {
152 - console.log(chalk.green("StrictMode usage found."));
153 - } else {
154 - console.log(chalk.red("StrictMode usage not found."));
155 - }
156 -
157 - if (knownIncompatibleLibrariesUsage.size > 0) {
158 - console.log(chalk.red(`Found the following incompatible libraries:`));
159 - for (const library of knownIncompatibleLibrariesUsage) {
160 - console.log(library);
161 - }
162 - } else {
163 - console.log(chalk.green(`Found no usage of incompatible libraries.`));
164 - }
53 + reactCompilerCheck.report();
54 + strictModeCheck.report();
55 + libraryCompatCheck.report();
56 }
57
58 main();