diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 8563190a89..67dff11d07 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -41,7 +41,6 @@ import { editorConfig, registerWebviewPaste, updateOptions } from '$lib/editorUtils' import { editorFontSize } from '$lib/editorFontSize.svelte' - import { createHash as randomHash } from '$lib/editorLangUtils' import { workspaceStore } from '$lib/stores' import DdlMigrationGuard from './DdlMigrationGuard.svelte' import { @@ -109,7 +108,8 @@ import type { ScriptLintResult } from './copilot/chat/shared' import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte' import { editorPositionMap } from '$lib/utils' - import { extToLang, langToExt } from '$lib/editorLangUtils' + import { extToLang } from '$lib/editorLangUtils' + import { computeModelPath, computeModelUri } from './lint/monacoUri' import { aiChatManager } from './copilot/chat/AIChatManager.svelte' import type { Selection } from 'monaco-editor' import { canHavePreprocessor, getPreprocessorModuleCode } from '$lib/script_helpers' @@ -252,7 +252,12 @@ cmdEnterAction?.() } - let filePath = $state(computePath(untrack(() => path))) + let filePath = $state( + computeModelPath( + untrack(() => path), + untrack(() => scriptLang) + ) + ) let initialPath: string | undefined = $state(untrack(() => path)) @@ -269,41 +274,14 @@ let dbSchema: DBSchema | undefined = $state(undefined) let destroyed = false - const uri = computeUri( + const uri = computeModelUri( untrack(() => filePath), - untrack(() => scriptLang) + untrack(() => scriptLang), + untrack(() => lang) ) console.log('uri', uri) - function computeUri(filePath: string, scriptLang: string | undefined) { - let file - if (filePath.includes('.')) { - file = filePath - } else { - file = `${filePath}.${scriptLang == 'tsx' ? 'tsx' : langToExt(lang)}` - } - if (file.startsWith('/')) { - file = file.slice(1) - } - return !['deno', 'go', 'python3'].includes(scriptLang ?? '') - ? `file:///${file}` - : `file:///tmp/monaco/${file}` - } - - function computePath(path: string | undefined): string { - if ( - ['deno', 'go', 'python3'].includes(scriptLang ?? '') || - path == '' || - path == undefined //||path.startsWith('/') - ) { - return randomHash() - } else { - // console.log('path', path) - return path as string - } - } - export function switchToFile(path: string, value: string, lang: string) { if (editor) { const uri = mUri.parse(path) @@ -2035,7 +2013,7 @@ }) $effect(() => { - filePath = computePath(path) + filePath = computeModelPath(path, scriptLang) }) $effect(() => { path != initialPath && diff --git a/frontend/src/lib/components/lint/monacoUri.test.ts b/frontend/src/lib/components/lint/monacoUri.test.ts new file mode 100644 index 0000000000..87390b65c2 --- /dev/null +++ b/frontend/src/lib/components/lint/monacoUri.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from 'vitest' +import { scriptLangToEditorLang } from '$lib/scripts' +import { computeModelPath, computeModelUri } from './monacoUri' + +// Mirrors what Editor.svelte does: path -> filePath -> uri. +function uriFor(path: string | undefined, scriptLang: string) { + const filePath = computeModelPath(path, scriptLang) + return computeModelUri(filePath, scriptLang, scriptLangToEditorLang(scriptLang as any)) +} + +describe('computeModelUri', () => { + it.each([ + ['u/admin/my_script', 'bun', 'file:///u/admin/my_script.ts'], + ['u/admin/my_script', 'bunnative', 'file:///u/admin/my_script.ts'], + ['u/admin/my_script', 'nativets', 'file:///u/admin/my_script.ts'], + ['u/admin/component', 'tsx', 'file:///u/admin/component.tsx'], + ['u/admin/component', 'jsx', 'file:///u/admin/component.js'], + ['u/admin/script', 'javascript', 'file:///u/admin/script.js'], + // flow module and raw-app runnable derive their path as / + ['f/flows/myflow/a', 'bun', 'file:///f/flows/myflow/a.ts'], + ['u/admin/myapp/backend_1', 'bun', 'file:///u/admin/myapp/backend_1.ts'], + ['/u/admin/leading_slash', 'bun', 'file:///u/admin/leading_slash.ts'], + // a path that already carries an extension keeps it verbatim + ['u/admin/lib.ts', 'bun', 'file:///u/admin/lib.ts'] + ])('%s (%s) -> %s', (path, scriptLang, expected) => { + expect(uriFor(path, scriptLang)).toBe(expected) + }) + + it.each(['deno', 'go', 'python3'])( + 'randomizes the path and namespaces %s under /tmp/monaco', + (scriptLang) => { + const uri = uriFor('u/admin/my_script', scriptLang) + expect(uri.startsWith('file:///tmp/monaco/')).toBe(true) + expect(uri).not.toContain('my_script') + expect(uri).not.toBe(uriFor('u/admin/my_script', scriptLang)) + } + ) + + it('falls back to a random path when none is given', () => { + expect(computeModelPath(undefined, 'bun')).not.toBe('') + expect(computeModelPath('', 'bun')).not.toBe('') + }) +}) diff --git a/frontend/src/lib/components/lint/monacoUri.ts b/frontend/src/lib/components/lint/monacoUri.ts new file mode 100644 index 0000000000..5cdadb7562 --- /dev/null +++ b/frontend/src/lib/components/lint/monacoUri.ts @@ -0,0 +1,34 @@ +import { createHash as randomHash, langToExt } from '$lib/editorLangUtils' + +// Canonical Monaco model path/URI for a piece of Windmill code. Diagnostics are a +// property of the model at a given URI, so any code that lints headlessly must +// derive the URI from here — a URI that differs from the editor's yields a second, +// independently-validated model and diverging markers. + +const RANDOMIZED_PATH_LANGS = ['deno', 'go', 'python3'] + +export function computeModelPath(path: string | undefined, scriptLang: string | undefined): string { + if (RANDOMIZED_PATH_LANGS.includes(scriptLang ?? '') || path == '' || path == undefined) { + return randomHash() + } + return path +} + +export function computeModelUri( + filePath: string, + scriptLang: string | undefined, + editorLang: string +): string { + let file: string + if (filePath.includes('.')) { + file = filePath + } else { + file = `${filePath}.${scriptLang == 'tsx' ? 'tsx' : langToExt(editorLang)}` + } + if (file.startsWith('/')) { + file = file.slice(1) + } + return !RANDOMIZED_PATH_LANGS.includes(scriptLang ?? '') + ? `file:///${file}` + : `file:///tmp/monaco/${file}` +}