From f09a96f88d7350600a8318bb1ecd71ddd5adafef Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Tue, 21 Jul 2026 11:07:16 +0200 Subject: [PATCH] fix(ai): seed React types for tsx linting so JSX resolves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A tsx script linted with `
` reported three false positives — "JSX.IntrinsicElements does not exist" and "React not in scope" — because type acquisition only ran for bun/bunnative, so @types/react never loaded. The editor had the same gap: it creates an ATA instance for tsx but never seeds react. Add a shared ataSeedImport(scriptLang) (bun -> bun-types, tsx -> react) that both the editor and the headless linter seed through, and extend the headless trigger to tsx. Parity by construction: neither path can seed differently. jsx routes through the lenient JavaScript worker, which does not flag JSX, so it needs no seed. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/lib/components/Editor.svelte | 9 +++++---- frontend/src/lib/components/lint/headlessLint.ts | 9 ++++----- frontend/src/lib/components/lint/typescriptAta.ts | 12 ++++++++++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 029ad195c6..28ed5a01ba 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -105,7 +105,7 @@ ensureResourceTypeNamespace, fetchCustomWmillTypesData } from './lint/typescriptExtraLibs' - import { createWindmillAta, genAtaRoot } from './lint/typescriptAta' + import { ataSeedImport, createWindmillAta, genAtaRoot } from './lint/typescriptAta' import { readModelMarkers } from './lint/markers' import { registerEditor as registerLintEditor } from './lint/headlessModelHost' import { buildDenoImportMap, hasLanguageServers, lspServersFor } from './lint/lspLanguageConfig' @@ -1625,10 +1625,11 @@ } } }) - if (scriptLang == 'bun') { - ata?.('import "bun-types"') + const seed = ataSeedImport(scriptLang) + if (seed) { + ata?.(seed) } - if (scriptLang == 'bunnative' || scriptLang == 'bun') { + if (scriptLang == 'bunnative' || scriptLang == 'bun' || scriptLang == 'tsx') { ata?.(code ?? '') } dispatch('ataReady') diff --git a/frontend/src/lib/components/lint/headlessLint.ts b/frontend/src/lib/components/lint/headlessLint.ts index 57c7a7eb51..40a82d267b 100644 --- a/frontend/src/lib/components/lint/headlessLint.ts +++ b/frontend/src/lib/components/lint/headlessLint.ts @@ -20,7 +20,7 @@ import { releaseOwnedModel } from './headlessModelHost' import { ensureCustomWmillTypes, ensureResourceTypeNamespace } from './typescriptExtraLibs' -import { createWindmillAta, genAtaRoot } from './typescriptAta' +import { ataSeedImport, createWindmillAta, genAtaRoot } from './typescriptAta' import { lintWithLsp } from './headlessLsp' // Lints code without a mounted editor, by driving the same Monaco model + global @@ -152,7 +152,7 @@ async function lintOne( ) } - if (req.scriptLang === 'bun' || req.scriptLang === 'bunnative') { + if (req.scriptLang === 'bun' || req.scriptLang === 'bunnative' || req.scriptLang === 'tsx') { await withDeadline( acquireTypes(req, ownedUri).catch((e) => console.error('headlessLint: type acquisition failed', e) @@ -189,9 +189,8 @@ async function acquireTypes(req: HeadlessLintRequest, uriString: string): Promis absolutePathExtraLibs }) ataByKey.set(key, ata) - if (req.scriptLang === 'bun') { - await ata('import "bun-types"') - } + const seed = ataSeedImport(req.scriptLang) + if (seed) await ata(seed) } await ata(req.content) } diff --git a/frontend/src/lib/components/lint/typescriptAta.ts b/frontend/src/lib/components/lint/typescriptAta.ts index 160f759c7e..c270409b72 100644 --- a/frontend/src/lib/components/lint/typescriptAta.ts +++ b/frontend/src/lib/components/lint/typescriptAta.ts @@ -13,6 +13,18 @@ import { lspTokenStore } from '$lib/stores' // global typescriptDefaults. Without it, TypeScript reports every third-party import // as unresolved, so headless linting and the editor must acquire types the same way. +/** + * The import a language must feed to ATA so its ambient types resolve, even when the code + * itself does not import them: `bun-types` for bun's globals, `react` for tsx's JSX namespace + * (without it, `
` reports "JSX.IntrinsicElements does not exist" / "React not in scope"). + * The editor and the headless linter both seed through this so their diagnostics can't drift. + */ +export function ataSeedImport(scriptLang: string | undefined): string | undefined { + if (scriptLang === 'bun') return 'import "bun-types"' + if (scriptLang === 'tsx') return 'import "react"' + return undefined +} + export async function genAtaRoot(workspace: string): Promise { let token = get(lspTokenStore) if (!token) {