diff --git a/compiler/forget/packages/playground/components/Editor/Input.tsx b/compiler/forget/packages/playground/components/Editor/Input.tsx index cf3e19e0ef..75f67c1558 100644 --- a/compiler/forget/packages/playground/components/Editor/Input.tsx +++ b/compiler/forget/packages/playground/components/Editor/Input.tsx @@ -3,16 +3,22 @@ */ import MonacoEditor, { type Monaco } from "@monaco-editor/react"; +import { CompilerErrorDetail } from "babel-plugin-react-forget"; import invariant from "invariant"; import type { editor } from "monaco-editor"; import { useEffect, useState } from "react"; +import { renderForgetMarkers } from "../../lib/forgetMonacoDiagnostics"; import { useStore, useStoreDispatch } from "../StoreContext"; import { monacoOptions } from "./monacoOptions"; // TODO: Make TS recognize .d.ts files, in addition to loading them with webpack. // @ts-ignore import React$Types from "../../node_modules/@types/react/index.d.ts"; -export default function Input({}: {}) { +type Props = { + errors: CompilerErrorDetail[]; +}; + +export default function Input({ errors }: Props) { const [monaco, setMonaco] = useState(null); const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -23,10 +29,11 @@ export default function Input({}: {}) { const uri = monaco.Uri.parse(`file:///index.js`); const model = monaco.editor.getModel(uri); invariant(model, "Model must exist for the selected input file."); + renderForgetMarkers({ monaco, model, details: errors }); // N.B. that `tabSize` is a model property, not an editor property. // So, the tab size has to be set per model. model.updateOptions({ tabSize: 2 }); - }, [monaco]); + }, [monaco, errors]); const handleChange = (value: string | undefined) => { if (!value) return; diff --git a/compiler/forget/packages/playground/components/Editor/Output.tsx b/compiler/forget/packages/playground/components/Editor/Output.tsx index 314d9fe59e..68b1200375 100644 --- a/compiler/forget/packages/playground/components/Editor/Output.tsx +++ b/compiler/forget/packages/playground/components/Editor/Output.tsx @@ -6,121 +6,104 @@ */ import generate from "@babel/generator"; -import { parse } from "@babel/parser"; -import traverse, { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; import MonacoEditor from "@monaco-editor/react"; import { printHIR, printReactiveFunction, - run, + type CompilerError, + type CompilerPipelineValue, } from "babel-plugin-react-forget"; import prettier from "prettier"; import prettierParserBabel from "prettier/parser-babel"; import { memo, useMemo, useState } from "react"; -import type { Store } from "../../lib/stores"; +import { type Store } from "../../lib/stores"; import TabbedWindow from "../TabbedWindow"; import { monacoOptions } from "./monacoOptions"; const MemoizedOutput = memo(Output); export default MemoizedOutput; +export type CompilerOutput = + | { kind: "ok"; results: Map } + | { kind: "err"; error: CompilerError }; + type Props = { store: Store; + compilerOutput: CompilerOutput; }; -type CompilerError = string; - -function parseFunctions( - source: string -): Array> { - try { - const ast = parse(source, { - plugins: ["typescript", "jsx"], - sourceType: "module", - }); - const items: Array> = []; - traverse(ast, { - FunctionDeclaration: { - enter(nodePath) { - items.push(nodePath); - }, - }, - }); - return items; - } catch (e) { - return []; - } -} - -function compile(source: string): Map | CompilerError { - try { - const astFunctions = parseFunctions(source); - if (astFunctions.length === 0) { - return ""; - } - - // TODO: Handle multiple functions - const func = astFunctions[0]; - const tabs = new Map(); - let ast: t.Function | null = null; - for (const result of run(func)) { +function tabify(source: string, compilerOutput: CompilerOutput) { + const tabs = new Map(); + const reorderedTabs = new Map(); + const concattedResults = new Map(); + let topLevelFnDecls: Array = []; + if (compilerOutput.kind === "err") return reorderedTabs; + // Concat all top level function declaration results into a single tab for each pass + for (const [passName, results] of compilerOutput.results) { + for (const result of results) { switch (result.kind) { - case "ast": { - ast = result.value; - break; - } case "hir": { - const text = printHIR(result.value.body); - tabs.set( - result.name, - - ); + const prev = concattedResults.get(result.name); + const next = printHIR(result.value.body); + const identName = `function ${result.value.id?.name}`; + if (prev != null) { + concattedResults.set(passName, `${prev}\n\n${identName}\n${next}`); + } else { + concattedResults.set(passName, `${identName}\n${next}`); + } break; } case "reactive": { - const text = printReactiveFunction(result.value); - tabs.set( - result.name, - - ); + const prev = concattedResults.get(passName); + const next = printReactiveFunction(result.value); + if (prev != null) { + concattedResults.set(passName, `${prev}\n\n${next}`); + } else { + concattedResults.set(passName, next); + } break; } + case "ast": + topLevelFnDecls.push(result.value); + break; default: { throw new Error("Unexpected result kind"); } } } - // Ensure that JS and the JS source map come first - const reorderedTabs = new Map(); - if (ast !== null) { - const { code, sourceMapUrl } = codegen(ast, source); - reorderedTabs.set("JS", ); - if (sourceMapUrl) { - reorderedTabs.set( - "SourceMap", - <> -