Skip to content

Commit c93b8b6

Browse files
committed
fix(types): expose Lynx runtime globals in editor
Inject a minimal virtual declaration file for Lynx UI projects that explicitly use @lynx-js/types with an ES-only lib. This exposes console and the timeout/interval globals without enabling DOM or Node APIs. The installed @lynx-js/types package declares timer functions outside declare global and exports Console without a global console variable, so Lynxtron GO reported valid runtime APIs as unresolved names. Validated with the TypeScript language-service regression test and a production desktop rspack build. The broader language-service suite still has the pre-existing desktop Response diagnostic failure.
1 parent af33fb6 commit c93b8b6

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

lynxtron-go/src/extension-host/__tests__/typescript.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ import { useState } from '@lynx-js/react';
304304
305305
export function App() {
306306
const [count] = useState(1);
307+
const interval = setInterval(() => console.log(count), 1000);
308+
const timeout = setTimeout(() => clearInterval(interval), 1000);
309+
clearTimeout(timeout);
307310
return <view><text>{count}</text></view>;
308311
}
309312
`);
@@ -320,6 +323,11 @@ root.render(<App />);
320323

321324
expect(markers.some(m => m.code === 2875)).toBe(false);
322325
expect(markers.some(m => m.code === 2307)).toBe(false);
326+
expect(markers.some(m => m.message.includes("Cannot find name 'setInterval'"))).toBe(false);
327+
expect(markers.some(m => m.message.includes("Cannot find name 'clearInterval'"))).toBe(false);
328+
expect(markers.some(m => m.message.includes("Cannot find name 'setTimeout'"))).toBe(false);
329+
expect(markers.some(m => m.message.includes("Cannot find name 'clearTimeout'"))).toBe(false);
330+
expect(markers.some(m => m.message.includes("Cannot find name 'console'"))).toBe(false);
323331
expect(markers.filter(m => m.severity === 'error')).toHaveLength(0);
324332
} finally {
325333
fs.rmSync(tempRoot, { recursive: true, force: true });

lynxtron-go/src/extension-host/language-server/typescript.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@ interface FileEntry {
1010
content: string;
1111
}
1212

13+
const LYNX_GLOBALS_WORKAROUND_FILE = '.lynxtron-go-lynx-globals.d.ts';
14+
const LYNX_GLOBALS_WORKAROUND_SOURCE = `
15+
export {};
16+
17+
declare global {
18+
interface Console {
19+
debug(...args: any[]): void;
20+
error(...args: any[]): void;
21+
group(label?: string): void;
22+
groupEnd(): void;
23+
info(...args: any[]): void;
24+
log(...args: any[]): void;
25+
alog(...args: any[]): void;
26+
warn(...args: any[]): void;
27+
}
28+
29+
var console: Console;
30+
31+
function setTimeout(callback: (...args: unknown[]) => unknown, delay: number): number;
32+
function setInterval(callback: (...args: unknown[]) => unknown, delay: number): number;
33+
function clearTimeout(timeoutId: number): void;
34+
function clearInterval(timeoutId: number): void;
35+
}
36+
`;
37+
1338
interface ProjectConfig {
1439
key: string;
1540
projectRoot: string;
@@ -236,6 +261,16 @@ function shouldUseBundledModuleFallback(
236261
return /\.(?:[cm]?js|jsx)$/.test(resolved.resolvedFileName);
237262
}
238263

264+
function needsLynxGlobalsWorkaround(options: ts.CompilerOptions): boolean {
265+
if (!options.types?.includes('@lynx-js/types')) return false;
266+
267+
// An omitted lib list uses TypeScript's defaults, which already include DOM.
268+
// Lynx app configs explicitly choose an ES-only lib to avoid browser globals.
269+
if (!options.lib) return false;
270+
271+
return !options.lib.some((libPath) => path.basename(libPath).toLowerCase() === 'lib.dom.d.ts');
272+
}
273+
239274
class LanguageServiceHost implements ts.LanguageServiceHost {
240275
private files = new Map<string, FileEntry>();
241276
private rootFiles: string[];
@@ -253,6 +288,16 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
253288
this.options = options;
254289
this.rootFiles = [...new Set(rootFiles)];
255290
this.bundledFallback = bundledFallback;
291+
292+
// @lynx-js/types@3.8 declares the timer functions outside declare global
293+
// and exports Console without declaring the global console variable. Keep
294+
// Lynx projects free of DOM types while making the runtime globals visible.
295+
if (needsLynxGlobalsWorkaround(options)) {
296+
this.files.set(path.join(projectRoot, LYNX_GLOBALS_WORKAROUND_FILE), {
297+
version: 0,
298+
content: LYNX_GLOBALS_WORKAROUND_SOURCE,
299+
});
300+
}
256301
}
257302

258303
updateFile(filePath: string, content: string, version: number) {
@@ -294,10 +339,13 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
294339
}
295340

296341
fileExists(path: string): boolean {
342+
if (this.files.has(path)) return true;
297343
return ts.sys.fileExists(path);
298344
}
299345

300346
readFile(path: string): string | undefined {
347+
const entry = this.files.get(path);
348+
if (entry) return entry.content;
301349
return ts.sys.readFile(path);
302350
}
303351

0 commit comments

Comments
 (0)