Skip to content

Commit

Permalink
[compiler] Flow support for playground
Browse files Browse the repository at this point in the history
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: b99b1568d7de602dd70d8cf1d8110d62530cf43b
Pull Request resolved: facebook#30150
  • Loading branch information
mvitousek committed Jul 1, 2024
1 parent 97c5e6c commit 9a6e2d0
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 35 deletions.
55 changes: 34 additions & 21 deletions compiler/apps/playground/components/Editor/EditorImpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

import { parse, ParserPlugin } from "@babel/parser";
import { parse as babelParse, ParserPlugin } from "@babel/parser";
import * as HermesParser from "hermes-parser";
import traverse, { NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import {
Expand Down Expand Up @@ -42,8 +43,26 @@ import {
PrintedCompilerPipelineValue,
} from "./Output";

function parseInput(input: string, language: "flow" | "typescript") {
// Extract the first line to quickly check for custom test directives
if (language === "flow") {
return HermesParser.parse(input, {
babel: true,
flow: "all",
sourceType: "module",
enableExperimentalComponentSyntax: true,
});
} else {
return babelParse(input, {
plugins: ["typescript", "jsx"],
sourceType: "module",
});
}
}

function parseFunctions(
source: string
source: string,
language: "flow" | "typescript"
): Array<
NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
Expand All @@ -55,20 +74,7 @@ function parseFunctions(
>
> = [];
try {
const isFlow = source
.trim()
.split("\n", 1)[0]
.match(/\s*\/\/\s*\@flow\s*/);
let type_transform: ParserPlugin;
if (isFlow) {
type_transform = "flow";
} else {
type_transform = "typescript";
}
const ast = parse(source, {
plugins: [type_transform, "jsx"],
sourceType: "module",
});
const ast = parseInput(source, language);
traverse(ast, {
FunctionDeclaration(nodePath) {
items.push(nodePath);
Expand Down Expand Up @@ -163,7 +169,7 @@ function getReactFunctionType(
return "Other";
}

function compile(source: string): CompilerOutput {
function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
const results = new Map<string, PrintedCompilerPipelineValue[]>();
const error = new CompilerError();
const upsert = (result: PrintedCompilerPipelineValue) => {
Expand All @@ -174,12 +180,18 @@ function compile(source: string): CompilerOutput {
results.set(result.name, [result]);
}
};
let language: "flow" | "typescript";
if (source.match(/\@flow/)) {
language = "flow";
} else {
language = "typescript";
}
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf("\n"));
const config = parseConfigPragma(pragma);

for (const fn of parseFunctions(source)) {
for (const fn of parseFunctions(source, language)) {
if (!fn.isFunctionDeclaration()) {
error.pushErrorDetail(
new CompilerErrorDetail({
Expand Down Expand Up @@ -279,17 +291,17 @@ function compile(source: string): CompilerOutput {
}
}
if (error.hasErrors()) {
return { kind: "err", results, error: error };
return [{ kind: "err", results, error: error }, language];
}
return { kind: "ok", results };
return [{ kind: "ok", results }, language];
}

export default function Editor() {
const store = useStore();
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();
const { enqueueSnackbar } = useSnackbar();
const compilerOutput = useMemo(
const [compilerOutput, language] = useMemo(
() => compile(deferredStore.source),
[deferredStore.source]
);
Expand Down Expand Up @@ -321,6 +333,7 @@ export default function Editor() {
<div className="relative flex basis top-14">
<div className={clsx("relative sm:basis-1/4")}>
<Input
language={language}
errors={
compilerOutput.kind === "err" ? compilerOutput.error.details : []
}
Expand Down
43 changes: 31 additions & 12 deletions compiler/apps/playground/components/Editor/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ loader.config({ monaco });

type Props = {
errors: CompilerErrorDetail[];
language: "flow" | "typescript";
};

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

const flowDiagnosticDisable = [
7028 /* unused label */, 6133 /* var declared but not read */,
];
useEffect(() => {
// Ignore "can only be used in TypeScript files." errors, since
// we want to support syntax highlighting for Flow (*.js) files
// and Flow is not a built-in language.
if (!monaco) return;
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
diagnosticCodesToIgnore: [
8002,
8003,
8004,
8005,
8006,
8008,
8009,
8010,
8011,
8012,
8013,
...(language === "flow" ? flowDiagnosticDisable : []),
],
noSemanticValidation: true,
// Monaco can't validate Flow component syntax
noSyntaxValidation: language === "flow",
});
}, [monaco, language]);

const handleChange = (value: string | undefined) => {
if (!value) return;

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

// Ignore "can only be used in TypeScript files." errors, since
// we want to support syntax highlighting for Flow (*.js) files
// and Flow is not a built-in language.
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
diagnosticCodesToIgnore: [
8002, 8003, 8004, 8005, 8006, 8008, 8009, 8010, 8011, 8012, 8013,
],
noSemanticValidation: true,
noSyntaxValidation: false,
});

const tscOptions = {
allowNonTsExtensions: true,
target: monaco.languages.typescript.ScriptTarget.ES2015,
Expand Down
20 changes: 20 additions & 0 deletions compiler/apps/playground/lib/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// v0.17.1
declare module "hermes-parser" {
type HermesParserOptions = {
allowReturnOutsideFunction?: boolean;
babel?: boolean;
flow?: "all" | "detect";
enableExperimentalComponentSyntax?: boolean;
sourceFilename?: string;
sourceType?: "module" | "script" | "unambiguous";
tokens?: boolean;
};
export function parse(code: string, options: Partial<HermesParserOptions>);
}
5 changes: 5 additions & 0 deletions compiler/apps/playground/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const nextConfig = {
"../../packages/react-compiler-runtime"
),
};
config.resolve.fallback = {
fs: false,
path: false,
os: false,
};

return config;
},
Expand Down
7 changes: 5 additions & 2 deletions compiler/apps/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"@monaco-editor/react": "^4.4.6",
"@playwright/test": "^1.42.1",
"@use-gesture/react": "^10.2.22",
"fs": "^0.0.1-security",
"hermes-eslint": "^0.14.0",
"hermes-parser": "^0.22.0",
"invariant": "^2.2.4",
"lz-string": "^1.5.0",
"monaco-editor": "^0.34.1",
Expand All @@ -34,8 +36,8 @@
"pretty-format": "^29.3.1",
"re-resizable": "^6.9.16",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-compiler-runtime": "*"
"react-compiler-runtime": "*",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "18.11.9",
Expand All @@ -46,6 +48,7 @@
"clsx": "^1.2.1",
"eslint": "^8.28.0",
"eslint-config-next": "^13.5.6",
"hermes-parser": "^0.22.0",
"monaco-editor-webpack-plugin": "^7.1.0",
"postcss": "^8.4.31",
"tailwindcss": "^3.2.4"
Expand Down
17 changes: 17 additions & 0 deletions compiler/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5410,6 +5410,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==

fs@^0.0.1-security:
version "0.0.1-security"
resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4"
integrity sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==

[email protected], fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
Expand Down Expand Up @@ -5773,6 +5778,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.20.1.tgz#0b9a544cf883a779a8e1444b915fa365bef7f72d"
integrity sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==

[email protected]:
version "0.22.0"
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.22.0.tgz#38559502b119f728901d2cfe2ef422f277802a1d"
integrity sha512-FLBt5X9OfA8BERUdc6aZS36Xz3rRuB0Y/mfocSADWEJfomc1xfene33GdyAmtTkKTBXTN/EgAy+rjTKkkZJHlw==

[email protected]:
version "0.14.0"
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.14.0.tgz#edb2e7172fce996d2c8bbba250d140b70cc1aaaf"
Expand Down Expand Up @@ -5808,6 +5818,13 @@ hermes-parser@^0.20.1:
dependencies:
hermes-estree "0.20.1"

hermes-parser@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.22.0.tgz#fc8e0e6c7bfa8db85b04c9f9544a102c4fcb4040"
integrity sha512-gn5RfZiEXCsIWsFGsKiykekktUoh0PdFWYocXsUdZIyWSckT6UIyPcyyUIPSR3kpnELWeK3n3ztAse7Mat6PSA==
dependencies:
hermes-estree "0.22.0"

html-encoding-sniffer@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
Expand Down

0 comments on commit 9a6e2d0

Please sign in to comment.