refactor(editor): extract Monaco model path/URI computation

Headless linting needs to derive the exact model URI the editor uses;
computeUri/computePath were component-local closures over scriptLang and
lang. Move them to lint/monacoUri.ts with those as explicit parameters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-07-20 12:30:13 +02:00
parent 7b813d1f74
commit b89e082e87
3 changed files with 89 additions and 34 deletions
+12 -34
View File
@@ -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 &&
@@ -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 <item path>/<id>
['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('')
})
})
@@ -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}`
}