diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index e8a1177b4e439..312492b48c3c1 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -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 { @@ -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 @@ -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); @@ -163,7 +169,7 @@ function getReactFunctionType( return "Other"; } -function compile(source: string): CompilerOutput { +function compile(source: string): [CompilerOutput, "flow" | "typescript"] { const results = new Map(); const error = new CompilerError(); const upsert = (result: PrintedCompilerPipelineValue) => { @@ -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({ @@ -279,9 +291,9 @@ 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() { @@ -289,7 +301,7 @@ export default function Editor() { const deferredStore = useDeferredValue(store); const dispatchStore = useStoreDispatch(); const { enqueueSnackbar } = useSnackbar(); - const compilerOutput = useMemo( + const [compilerOutput, language] = useMemo( () => compile(deferredStore.source), [deferredStore.source] ); @@ -321,6 +333,7 @@ export default function Editor() {
(null); const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -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; @@ -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, diff --git a/compiler/apps/playground/lib/types.d.ts b/compiler/apps/playground/lib/types.d.ts new file mode 100644 index 0000000000000..40771d183c06b --- /dev/null +++ b/compiler/apps/playground/lib/types.d.ts @@ -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); +} diff --git a/compiler/apps/playground/next.config.js b/compiler/apps/playground/next.config.js index ddb2f958caedf..d288d71153124 100644 --- a/compiler/apps/playground/next.config.js +++ b/compiler/apps/playground/next.config.js @@ -34,6 +34,11 @@ const nextConfig = { "../../packages/react-compiler-runtime" ), }; + config.resolve.fallback = { + fs: false, + path: false, + os: false, + }; return config; }, diff --git a/compiler/apps/playground/package.json b/compiler/apps/playground/package.json index c5cd50e24b1b0..55f146fe2591c 100644 --- a/compiler/apps/playground/package.json +++ b/compiler/apps/playground/package.json @@ -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", @@ -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", @@ -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" diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6df7539281795..f54b9be27d474 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -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== + fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" @@ -5773,6 +5778,11 @@ hermes-estree@0.20.1: resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.20.1.tgz#0b9a544cf883a779a8e1444b915fa365bef7f72d" integrity sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg== +hermes-estree@0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.22.0.tgz#38559502b119f728901d2cfe2ef422f277802a1d" + integrity sha512-FLBt5X9OfA8BERUdc6aZS36Xz3rRuB0Y/mfocSADWEJfomc1xfene33GdyAmtTkKTBXTN/EgAy+rjTKkkZJHlw== + hermes-parser@0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.14.0.tgz#edb2e7172fce996d2c8bbba250d140b70cc1aaaf" @@ -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"