Skip to content

Commit 9a6e2d0

Browse files
committed
[compiler] Flow support for playground
Summary: The playground currently has limited support for Flow files--it tries to parse them if the // flow sigil is on the fist line, but this is often not the case for files one would like to inspect in practice. more importantly, component syntax isn't supported even then, because it depends on the Hermes parser. This diff improves the state of flow support in the playground to make it more useful: when we see `flow` anywhere in the file, we'll assume it's a flow file, parse it with the Hermes parser, and disable typescript-specific features of Monaco editor. ghstack-source-id: b99b156 Pull Request resolved: #30150
1 parent 97c5e6c commit 9a6e2d0

File tree

6 files changed

+112
-35
lines changed

6 files changed

+112
-35
lines changed

compiler/apps/playground/components/Editor/EditorImpl.tsx

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import { parse, ParserPlugin } from "@babel/parser";
8+
import { parse as babelParse, ParserPlugin } from "@babel/parser";
9+
import * as HermesParser from "hermes-parser";
910
import traverse, { NodePath } from "@babel/traverse";
1011
import * as t from "@babel/types";
1112
import {
@@ -42,8 +43,26 @@ import {
4243
PrintedCompilerPipelineValue,
4344
} from "./Output";
4445

46+
function parseInput(input: string, language: "flow" | "typescript") {
47+
// Extract the first line to quickly check for custom test directives
48+
if (language === "flow") {
49+
return HermesParser.parse(input, {
50+
babel: true,
51+
flow: "all",
52+
sourceType: "module",
53+
enableExperimentalComponentSyntax: true,
54+
});
55+
} else {
56+
return babelParse(input, {
57+
plugins: ["typescript", "jsx"],
58+
sourceType: "module",
59+
});
60+
}
61+
}
62+
4563
function parseFunctions(
46-
source: string
64+
source: string,
65+
language: "flow" | "typescript"
4766
): Array<
4867
NodePath<
4968
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -55,20 +74,7 @@ function parseFunctions(
5574
>
5675
> = [];
5776
try {
58-
const isFlow = source
59-
.trim()
60-
.split("\n", 1)[0]
61-
.match(/\s*\/\/\s*\@flow\s*/);
62-
let type_transform: ParserPlugin;
63-
if (isFlow) {
64-
type_transform = "flow";
65-
} else {
66-
type_transform = "typescript";
67-
}
68-
const ast = parse(source, {
69-
plugins: [type_transform, "jsx"],
70-
sourceType: "module",
71-
});
77+
const ast = parseInput(source, language);
7278
traverse(ast, {
7379
FunctionDeclaration(nodePath) {
7480
items.push(nodePath);
@@ -163,7 +169,7 @@ function getReactFunctionType(
163169
return "Other";
164170
}
165171

166-
function compile(source: string): CompilerOutput {
172+
function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
167173
const results = new Map<string, PrintedCompilerPipelineValue[]>();
168174
const error = new CompilerError();
169175
const upsert = (result: PrintedCompilerPipelineValue) => {
@@ -174,12 +180,18 @@ function compile(source: string): CompilerOutput {
174180
results.set(result.name, [result]);
175181
}
176182
};
183+
let language: "flow" | "typescript";
184+
if (source.match(/\@flow/)) {
185+
language = "flow";
186+
} else {
187+
language = "typescript";
188+
}
177189
try {
178190
// Extract the first line to quickly check for custom test directives
179191
const pragma = source.substring(0, source.indexOf("\n"));
180192
const config = parseConfigPragma(pragma);
181193

182-
for (const fn of parseFunctions(source)) {
194+
for (const fn of parseFunctions(source, language)) {
183195
if (!fn.isFunctionDeclaration()) {
184196
error.pushErrorDetail(
185197
new CompilerErrorDetail({
@@ -279,17 +291,17 @@ function compile(source: string): CompilerOutput {
279291
}
280292
}
281293
if (error.hasErrors()) {
282-
return { kind: "err", results, error: error };
294+
return [{ kind: "err", results, error: error }, language];
283295
}
284-
return { kind: "ok", results };
296+
return [{ kind: "ok", results }, language];
285297
}
286298

287299
export default function Editor() {
288300
const store = useStore();
289301
const deferredStore = useDeferredValue(store);
290302
const dispatchStore = useStoreDispatch();
291303
const { enqueueSnackbar } = useSnackbar();
292-
const compilerOutput = useMemo(
304+
const [compilerOutput, language] = useMemo(
293305
() => compile(deferredStore.source),
294306
[deferredStore.source]
295307
);
@@ -321,6 +333,7 @@ export default function Editor() {
321333
<div className="relative flex basis top-14">
322334
<div className={clsx("relative sm:basis-1/4")}>
323335
<Input
336+
language={language}
324337
errors={
325338
compilerOutput.kind === "err" ? compilerOutput.error.details : []
326339
}

compiler/apps/playground/components/Editor/Input.tsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ loader.config({ monaco });
2323

2424
type Props = {
2525
errors: CompilerErrorDetail[];
26+
language: "flow" | "typescript";
2627
};
2728

28-
export default function Input({ errors }: Props) {
29+
export default function Input({ errors, language }: Props) {
2930
const [monaco, setMonaco] = useState<Monaco | null>(null);
3031
const store = useStore();
3132
const dispatchStore = useStoreDispatch();
@@ -42,6 +43,35 @@ export default function Input({ errors }: Props) {
4243
model.updateOptions({ tabSize: 2 });
4344
}, [monaco, errors]);
4445

46+
const flowDiagnosticDisable = [
47+
7028 /* unused label */, 6133 /* var declared but not read */,
48+
];
49+
useEffect(() => {
50+
// Ignore "can only be used in TypeScript files." errors, since
51+
// we want to support syntax highlighting for Flow (*.js) files
52+
// and Flow is not a built-in language.
53+
if (!monaco) return;
54+
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
55+
diagnosticCodesToIgnore: [
56+
8002,
57+
8003,
58+
8004,
59+
8005,
60+
8006,
61+
8008,
62+
8009,
63+
8010,
64+
8011,
65+
8012,
66+
8013,
67+
...(language === "flow" ? flowDiagnosticDisable : []),
68+
],
69+
noSemanticValidation: true,
70+
// Monaco can't validate Flow component syntax
71+
noSyntaxValidation: language === "flow",
72+
});
73+
}, [monaco, language]);
74+
4575
const handleChange = (value: string | undefined) => {
4676
if (!value) return;
4777

@@ -56,17 +86,6 @@ export default function Input({ errors }: Props) {
5686
const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
5787
setMonaco(monaco);
5888

59-
// Ignore "can only be used in TypeScript files." errors, since
60-
// we want to support syntax highlighting for Flow (*.js) files
61-
// and Flow is not a built-in language.
62-
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
63-
diagnosticCodesToIgnore: [
64-
8002, 8003, 8004, 8005, 8006, 8008, 8009, 8010, 8011, 8012, 8013,
65-
],
66-
noSemanticValidation: true,
67-
noSyntaxValidation: false,
68-
});
69-
7089
const tscOptions = {
7190
allowNonTsExtensions: true,
7291
target: monaco.languages.typescript.ScriptTarget.ES2015,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
// v0.17.1
9+
declare module "hermes-parser" {
10+
type HermesParserOptions = {
11+
allowReturnOutsideFunction?: boolean;
12+
babel?: boolean;
13+
flow?: "all" | "detect";
14+
enableExperimentalComponentSyntax?: boolean;
15+
sourceFilename?: string;
16+
sourceType?: "module" | "script" | "unambiguous";
17+
tokens?: boolean;
18+
};
19+
export function parse(code: string, options: Partial<HermesParserOptions>);
20+
}

compiler/apps/playground/next.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ const nextConfig = {
3434
"../../packages/react-compiler-runtime"
3535
),
3636
};
37+
config.resolve.fallback = {
38+
fs: false,
39+
path: false,
40+
os: false,
41+
};
3742

3843
return config;
3944
},

compiler/apps/playground/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
"@monaco-editor/react": "^4.4.6",
2525
"@playwright/test": "^1.42.1",
2626
"@use-gesture/react": "^10.2.22",
27+
"fs": "^0.0.1-security",
2728
"hermes-eslint": "^0.14.0",
29+
"hermes-parser": "^0.22.0",
2830
"invariant": "^2.2.4",
2931
"lz-string": "^1.5.0",
3032
"monaco-editor": "^0.34.1",
@@ -34,8 +36,8 @@
3436
"pretty-format": "^29.3.1",
3537
"re-resizable": "^6.9.16",
3638
"react": "18.2.0",
37-
"react-dom": "18.2.0",
38-
"react-compiler-runtime": "*"
39+
"react-compiler-runtime": "*",
40+
"react-dom": "18.2.0"
3941
},
4042
"devDependencies": {
4143
"@types/node": "18.11.9",
@@ -46,6 +48,7 @@
4648
"clsx": "^1.2.1",
4749
"eslint": "^8.28.0",
4850
"eslint-config-next": "^13.5.6",
51+
"hermes-parser": "^0.22.0",
4952
"monaco-editor-webpack-plugin": "^7.1.0",
5053
"postcss": "^8.4.31",
5154
"tailwindcss": "^3.2.4"

compiler/yarn.lock

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5410,6 +5410,11 @@ fs.realpath@^1.0.0:
54105410
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
54115411
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
54125412

5413+
fs@^0.0.1-security:
5414+
version "0.0.1-security"
5415+
resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4"
5416+
integrity sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==
5417+
54135418
[email protected], fsevents@^2.3.2, fsevents@~2.3.2:
54145419
version "2.3.2"
54155420
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
@@ -5773,6 +5778,11 @@ [email protected]:
57735778
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.20.1.tgz#0b9a544cf883a779a8e1444b915fa365bef7f72d"
57745779
integrity sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==
57755780

5781+
5782+
version "0.22.0"
5783+
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.22.0.tgz#38559502b119f728901d2cfe2ef422f277802a1d"
5784+
integrity sha512-FLBt5X9OfA8BERUdc6aZS36Xz3rRuB0Y/mfocSADWEJfomc1xfene33GdyAmtTkKTBXTN/EgAy+rjTKkkZJHlw==
5785+
57765786
57775787
version "0.14.0"
57785788
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.14.0.tgz#edb2e7172fce996d2c8bbba250d140b70cc1aaaf"
@@ -5808,6 +5818,13 @@ hermes-parser@^0.20.1:
58085818
dependencies:
58095819
hermes-estree "0.20.1"
58105820

5821+
hermes-parser@^0.22.0:
5822+
version "0.22.0"
5823+
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.22.0.tgz#fc8e0e6c7bfa8db85b04c9f9544a102c4fcb4040"
5824+
integrity sha512-gn5RfZiEXCsIWsFGsKiykekktUoh0PdFWYocXsUdZIyWSckT6UIyPcyyUIPSR3kpnELWeK3n3ztAse7Mat6PSA==
5825+
dependencies:
5826+
hermes-estree "0.22.0"
5827+
58115828
html-encoding-sniffer@^3.0.0:
58125829
version "3.0.0"
58135830
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"

0 commit comments

Comments
 (0)