diff --git a/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditor.tsx b/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditor.tsx index 28a8449a..c404b0a3 100644 --- a/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditor.tsx +++ b/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditor.tsx @@ -12,6 +12,7 @@ import { getLanguage } from './language-map'; import { useFilesRefresh } from '../../channels'; import { useWorkspace } from '../../components/Workspace'; import { useEditorTabs } from './use-editor-tabs'; +import { configureMonacoTypescript } from './monaco-setup'; type CodeEditorViewProps = { className?: string; @@ -286,10 +287,15 @@ export const CodeEditorView = ({ {activeFile ? ( setContent(activeFile.path, value ?? '')} + beforeMount={configureMonacoTypescript} onMount={handleEditorMount} options={{ automaticLayout: true, diff --git a/src/workspaces/officerdev/src/apps/CodeEditor/monaco-setup.ts b/src/workspaces/officerdev/src/apps/CodeEditor/monaco-setup.ts new file mode 100644 index 00000000..1408c51e --- /dev/null +++ b/src/workspaces/officerdev/src/apps/CodeEditor/monaco-setup.ts @@ -0,0 +1,68 @@ +import type { Monaco } from '@monaco-editor/react'; + +/** + * Teach Monaco's TypeScript worker what it can and cannot know here. + * + * Out of the box the editor underlined most of a `.tsx` file in red. Two separate causes, and neither is + * a real defect in the code being edited: + * + * ── 1. It was reading JSX as TypeScript ── + * + * With no `path`, `` names its model `inmemory://model/1`. TypeScript decides whether JSX is even + * legal from the file EXTENSION, and that URI has none — so every `` in the file was a syntax error. + * That is the wall-to-wall squiggling. Fixed by giving the model its real path (see `CodeEditor.tsx`) and + * by turning JSX on below; both are needed, since `.tsx` without the compiler option is still an error. + * + * ── 2. It was type-checking against a file system it cannot see ── + * + * Semantic checking is off, and this is the part worth being honest about. Monaco runs in the browser. The + * files it is editing are on the server, reached through the file API — there is no `node_modules` to + * resolve `react` from, no `tsconfig.json` to read paths out of, and no way to follow a relative import to + * a sibling file. So EVERY import resolves to nothing, which is where "Cannot find module './logo.svg'" + * came from: not a mistake in the file, a checker with no file system. + * + * Wrong errors are worse than no errors. They train you to ignore the colour, and then the one real + * problem underlined the same way goes unread. + * + * Syntactic checking stays ON, because that needs only the text in front of it: an unclosed brace really + * is an unclosed brace. It is the subset of "is this correct" that is answerable from a single buffer, and + * once the extension is right it is answered correctly. + * + * Making semantic checking work would mean shipping the project's dependency types into the browser, which + * is a language-server-over-websocket feature, not a config change. + */ + +// Monaco is a singleton behind the loader; configuring it twice is harmless but pointless. +let configured = false; + +export function configureMonacoTypescript(monaco: Monaco): void { + if (configured) return; + configured = true; + + const ts = monaco.languages.typescript; + + const compilerOptions = { + target: ts.ScriptTarget.ESNext, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + jsx: ts.JsxEmit.ReactJSX, + allowJs: true, + allowNonTsExtensions: true, + esModuleInterop: true, + // Nothing is emitted from here — this worker exists only to parse and highlight. + noEmit: true, + }; + + ts.typescriptDefaults.setCompilerOptions(compilerOptions); + ts.javascriptDefaults.setCompilerOptions(compilerOptions); + + // See above: no file system, so no honest semantic answer. Syntax is still checked. + const diagnostics = { noSemanticValidation: true, noSyntaxValidation: false, noSuggestionDiagnostics: true }; + ts.typescriptDefaults.setDiagnosticsOptions(diagnostics); + ts.javascriptDefaults.setDiagnosticsOptions(diagnostics); + + // The worker would otherwise only see the file that is open, and a `.d.ts` it cannot fetch is a request + // that fails on every keystroke. + ts.typescriptDefaults.setEagerModelSync(true); + ts.javascriptDefaults.setEagerModelSync(true); +}