fix(ai): seed React types for tsx linting so JSX resolves

A tsx script linted with `<div>` 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) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-07-21 11:07:16 +02:00
parent 468c3d73e9
commit f09a96f88d
3 changed files with 21 additions and 9 deletions
+5 -4
View File
@@ -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')
@@ -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)
}
@@ -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, `<div>` 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<string> {
let token = get(lspTokenStore)
if (!token) {