From b73d807e56a287a0a426cd902861908d4266fea6 Mon Sep 17 00:00:00 2001 From: "jyc.dev" Date: Fri, 18 Jul 2025 20:22:22 +0200 Subject: [PATCH 01/39] fix: Better detection version (#2801) * step 1 of version * start managing things * adding semver to test * let's not redo semver! * better fallback * not only this ;) * Update version.ts --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte-vscode/package.json | 2 + packages/svelte-vscode/src/sveltekit/utils.ts | 30 +++++++---- packages/svelte-vscode/src/version.ts | 51 +++++++++++++++++++ packages/svelte-vscode/test/version.spec.ts | 41 +++++++++++++++ pnpm-lock.yaml | 20 +++++++- 5 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 packages/svelte-vscode/src/version.ts create mode 100644 packages/svelte-vscode/test/version.spec.ts diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index 574715f96..f97cf561f 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -741,8 +741,10 @@ "devDependencies": { "@types/lodash": "^4.14.116", "@types/node": "^18.0.0", + "@types/semver": "^7.7.0", "@types/vscode": "^1.67", "js-yaml": "^3.14.0", + "semver": "^7.7.2", "tslib": "^2.4.0", "typescript": "^5.8.2", "vitest": "^3.2.4", diff --git a/packages/svelte-vscode/src/sveltekit/utils.ts b/packages/svelte-vscode/src/sveltekit/utils.ts index 6ca33f796..cce8e2de9 100644 --- a/packages/svelte-vscode/src/sveltekit/utils.ts +++ b/packages/svelte-vscode/src/sveltekit/utils.ts @@ -2,6 +2,7 @@ import { TextDecoder } from 'util'; import * as path from 'path'; import { Uri, workspace } from 'vscode'; import type { GenerateConfig } from './generateFiles/types'; +import { atLeast } from '../version'; export async function fileExists(file: string) { try { @@ -31,10 +32,20 @@ export async function checkProjectKind(path: string): Promise= jsconfig.length); let withSatisfies = false; @@ -44,7 +55,12 @@ export async function checkProjectKind(path: string): Promise= (minor ?? 0)) || - Number(majorVersion) > major - ); -} - export async function getVersionFromPackageJson(packageName: string): Promise { const packageJsonList = await workspace.findFiles('**/package.json', '**/node_modules/**'); diff --git a/packages/svelte-vscode/src/version.ts b/packages/svelte-vscode/src/version.ts new file mode 100644 index 000000000..7db8b318d --- /dev/null +++ b/packages/svelte-vscode/src/version.ts @@ -0,0 +1,51 @@ +import { lt, coerce, gte } from 'semver'; + +/** + * @example + * const supported = atLeast({ + * packageName: 'node', + * versionMin: '18.3', + * versionToCheck: process.versions.node + * fallback: true // optional + * }); + */ +export function atLeast(o: { + packageName: string; + versionMin: string; + versionToCheck: string; + fallback: boolean; +}): boolean; +export function atLeast(o: { + packageName: string; + versionMin: string; + versionToCheck: string; + fallback?: undefined; +}): boolean | undefined; + +// Implementation +export function atLeast(o: { + packageName: string; + versionMin: string; + versionToCheck: string; + fallback?: boolean; +}): boolean | undefined { + const { packageName, versionMin, versionToCheck, fallback } = o; + if (versionToCheck === undefined || versionToCheck === '') return fallback; + + if ( + versionToCheck.includes('latest') || + versionToCheck.includes('catalog:') || + versionToCheck.includes('http') + ) { + console.warn(`Version '${versionToCheck}' for '${packageName}' is not supported`); + return fallback; + } + try { + const vMin = coerce(versionMin); + const vToCheck = coerce(versionToCheck); + if (vMin && vToCheck) { + return gte(vToCheck, vMin); + } + } catch (error) {} + return fallback; +} diff --git a/packages/svelte-vscode/test/version.spec.ts b/packages/svelte-vscode/test/version.spec.ts new file mode 100644 index 000000000..f22b9c721 --- /dev/null +++ b/packages/svelte-vscode/test/version.spec.ts @@ -0,0 +1,41 @@ +import { expect, describe, it } from 'vitest'; +import { atLeast } from '../src/version'; + +describe('atLeast', () => { + const combinationsAtLeast = [ + { min: '5', version: '>=5', supported: true }, + { min: '5', version: '>=5.0.0', supported: true }, + { min: '5', version: '5.0.0', supported: true }, + { min: '5', version: '5', supported: true }, + { min: '5', version: '4', supported: false }, + { min: '5', version: '4.9', supported: false }, + { min: '5', version: '', supported: undefined }, + { min: '5', version: 'catalog:', supported: undefined }, + { min: '5', version: 'latest', supported: undefined }, + { min: '5', version: 'latest', fallback: true, supported: true }, + { min: '5', version: 'latest', fallback: false, supported: false } + ]; + it.each(combinationsAtLeast)( + '(min $min, $version, $fallback) => $supported', + ({ min, version, supported, fallback }) => { + if (fallback !== undefined) { + expect( + atLeast({ + packageName: 'myPkg', + versionMin: min, + versionToCheck: version, + fallback + }) + ).toEqual(supported); + } else { + expect( + atLeast({ + packageName: 'myPkg', + versionMin: min, + versionToCheck: version + }) + ).toEqual(supported); + } + } + ); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29ea244d6..38c0cb205 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,12 +207,18 @@ importers: '@types/node': specifier: ^18.0.0 version: 18.19.46 + '@types/semver': + specifier: ^7.7.0 + version: 7.7.0 '@types/vscode': specifier: ^1.67 version: 1.78.0 js-yaml: specifier: ^3.14.0 version: 3.14.1 + semver: + specifier: ^7.7.2 + version: 7.7.2 tslib: specifier: ^2.4.0 version: 2.5.2 @@ -771,6 +777,9 @@ packages: '@types/sade@1.7.4': resolution: {integrity: sha512-6ys13kmtlY0aIOz4KtMdeBD9BHs6vSE3aRcj4vAZqXjypT2el8WZt6799CMjElVgh1cbOH/t3vrpQ4IpwytcPA==} + '@types/semver@7.7.0': + resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + '@types/sinon@7.5.2': resolution: {integrity: sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==} @@ -1561,6 +1570,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + serialize-javascript@6.0.0: resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} @@ -2227,6 +2241,8 @@ snapshots: dependencies: '@types/mri': 1.1.1 + '@types/semver@7.7.0': {} + '@types/sinon@7.5.2': {} '@types/unist@2.0.6': {} @@ -3037,6 +3053,8 @@ snapshots: dependencies: lru-cache: 6.0.0 + semver@7.7.2: {} + serialize-javascript@6.0.0: dependencies: randombytes: 2.1.0 @@ -3290,7 +3308,7 @@ snapshots: vscode-languageclient@9.0.1: dependencies: minimatch: 5.1.6 - semver: 7.5.1 + semver: 7.7.2 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-protocol@3.17.2: From 23db5a4bc858ce9c75fc8eb50cf42445b04175c9 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:28:24 +0800 Subject: [PATCH 02/39] fix: handle object literal in MustacheTag (#2805) --- packages/svelte2tsx/src/htmlxtojsx_v2/nodes/MustacheTag.ts | 7 +++++++ .../samples/mustache-tag-object-literal/expectedv2.js | 5 +++++ .../samples/mustache-tag-object-literal/input.svelte | 5 +++++ 3 files changed, 17 insertions(+) create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/expectedv2.js create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/input.svelte diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/MustacheTag.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/MustacheTag.ts index 9e888b90c..ab132e67e 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/MustacheTag.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/MustacheTag.ts @@ -10,6 +10,13 @@ export function handleMustacheTag(str: MagicString, node: BaseNode, parent: Base // handled inside Attribute.ts / StyleDirective.ts return; } + const text = str.original.slice(node.start + 1, node.end - 1); + if (text.trimStart().startsWith('{')) { + // possibly an object literal, wrapping it in parentheses so it's treated as an expression + str.overwrite(node.start, node.start + 1, ';(', { contentOnly: true }); + str.overwrite(node.end - 1, node.end, ');', { contentOnly: true }); + return; + } str.overwrite(node.start, node.start + 1, '', { contentOnly: true }); str.overwrite(node.end - 1, node.end, ';', { contentOnly: true }); } diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/expectedv2.js new file mode 100644 index 000000000..ce8c12ef9 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/expectedv2.js @@ -0,0 +1,5 @@ +;({ + toString() { return "Hello World" } +}); + +;({ a: '' }['a']); \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/input.svelte new file mode 100644 index 000000000..0d001f271 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/mustache-tag-object-literal/input.svelte @@ -0,0 +1,5 @@ +{{ + toString() { return "Hello World" } +}} + +{{ a: '' }['a']} \ No newline at end of file From d84b178edf08cfc95e9cb21a446914e2cb5bddbd Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:30:05 +0800 Subject: [PATCH 03/39] fix: prevent organize imports from removing snippet (#2809) * wip * test, fix leftover indent --- .../features/CodeActionsProvider.ts | 78 +++++++- .../typescript/features/CompletionProvider.ts | 19 +- .../src/plugins/typescript/utils.ts | 7 + .../features/CodeActionsProvider.test.ts | 178 +++++++++++++++++- .../organize-import-all-remove.svelte | 7 + .../organize-imports-snippet.svelte | 9 + 6 files changed, 277 insertions(+), 21 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-import-all-remove.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-snippet.svelte diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 244ae4e12..6887e2514 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -29,6 +29,7 @@ import { flatten, getIndent, isNotNullOrUndefined, + isPositionEqual, memoize, modifyLines, normalizePath, @@ -42,6 +43,7 @@ import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; import { LanguageServiceContainer } from '../service'; import { changeSvelteComponentName, + cloneRange, convertRange, isInScript, toGeneratedSvelteComponentName @@ -469,6 +471,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { }) ); + for (const change of documentChanges) { + this.checkIndentLeftover(change, document); + } + return [ CodeAction.create( skipDestructiveCodeActions ? 'Sort Imports' : 'Organize Imports', @@ -521,21 +527,20 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { snapshot: DocumentSnapshot, range: Range ) { + if (!(snapshot instanceof SvelteDocumentSnapshot)) { + return range; + } // Handle svelte2tsx wrong import mapping: // The character after the last import maps to the start of the script // TODO find a way to fix this in svelte2tsx and then remove this if ( (range.end.line === 0 && range.end.character === 1) || - range.end.line < range.start.line + range.end.line < range.start.line || + (isInScript(range.start, snapshot) && !isInScript(range.end, snapshot)) ) { edit.span.length -= 1; range = mapRangeToOriginal(snapshot, convertRange(snapshot, edit.span)); - if (!(snapshot instanceof SvelteDocumentSnapshot)) { - range.end.character += 1; - return range; - } - const line = getLineAtPosition(range.end, snapshot.getOriginalText()); // remove-import code action will removes the // line break generated by svelte2tsx, @@ -554,6 +559,67 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return range; } + private checkIndentLeftover(change: TextDocumentEdit, document: Document) { + if (!change.edits.length) { + return; + } + const orderedByStart = change.edits.sort((a, b) => { + if (a.range.start.line !== b.range.start.line) { + return a.range.start.line - b.range.start.line; + } + return a.range.start.character - b.range.start.character; + }); + + let current: TextEdit | undefined; + let groups: TextEdit[] = []; + for (let i = 0; i < orderedByStart.length; i++) { + const edit = orderedByStart[i]; + if (!current) { + current = { range: cloneRange(edit.range), newText: edit.newText }; + continue; + } + if (isPositionEqual(current.range.end, edit.range.start)) { + current.range.end = edit.range.end; + current.newText += edit.newText; + } else { + groups.push(current); + current = { range: cloneRange(edit.range), newText: edit.newText }; + } + } + if (current) { + groups.push(current); + } + + for (const edit of groups) { + if (edit.newText) { + continue; + } + const range = edit.range; + const lineContentBeforeRemove = document.getText({ + start: { line: range.start.line, character: 0 }, + end: range.start + }); + + const onlyIndentLeft = !lineContentBeforeRemove.trim(); + if (!onlyIndentLeft) { + continue; + } + + const lineContentAfterRemove = document.getText({ + start: range.end, + end: { line: range.end.line, character: Number.MAX_VALUE } + }); + const emptyAfterRemove = + !lineContentAfterRemove.trim() || lineContentAfterRemove.startsWith(''); + if (emptyAfterRemove) { + change.edits.push({ + range: { start: { line: range.start.line, character: 0 }, end: range.start }, + newText: '' + }); + } + } + } + private async applyQuickfix( document: Document, range: Range, diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index bc1764d87..b19f48967 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -38,7 +38,8 @@ import { convertRange, isInScript, isGeneratedSvelteComponentName, - scriptElementKindToCompletionItemKind + scriptElementKindToCompletionItemKind, + cloneRange } from '../utils'; import { getJsDocTemplateCompletion } from './getJsDocTemplateCompletion'; import { @@ -595,7 +596,7 @@ export class CompletionsProviderImpl implements CompletionsProvider ({ label: name, kind: CompletionItemKind.Property, - textEdit: TextEdit.replace(this.cloneRange(replacementRange), name), + textEdit: TextEdit.replace(cloneRange(replacementRange), name), commitCharacters: [] })); } @@ -641,18 +642,11 @@ export class CompletionsProviderImpl implements CompletionsProvider{ uri: pathToUrl(filePath), - text: harmonizeNewLines(ts.sys.readFile(filePath) || '') + text: ts.sys.readFile(filePath) || '' }); return { provider, document, docManager, lsAndTsDocResolver }; } @@ -2086,6 +2086,116 @@ describe('CodeActionsProvider', function () { ]); }); + it('organize imports without leftover indentation', async () => { + const { provider, document } = setup('organize-import-all-remove.svelte'); + + const codeActions = await provider.getCodeActions( + document, + Range.create(Position.create(1, 4), Position.create(1, 5)), + { + diagnostics: [], + only: [CodeActionKind.SourceOrganizeImports] + } + ); + + assert.deepStrictEqual(codeActions, [ + { + title: 'Organize Imports', + edit: { + documentChanges: [ + { + textDocument: { + uri: getUri('organize-import-all-remove.svelte'), + version: null + }, + edits: [ + { + range: { + start: { + line: 1, + character: 4 + }, + end: { + line: 2, + character: 4 + } + }, + newText: '' + }, + { + range: { + start: { + line: 2, + character: 4 + }, + end: { + line: 3, + character: 0 + } + }, + newText: '' + }, + { + range: { + start: { + line: 4, + character: 4 + }, + end: { + line: 5, + character: 4 + } + }, + newText: '' + }, + { + range: { + start: { + line: 5, + character: 4 + }, + end: { + line: 6, + character: 0 + } + }, + newText: '' + }, + { + range: { + start: { + line: 1, + character: 0 + }, + end: { + line: 1, + character: 4 + } + }, + newText: '' + }, + { + range: { + start: { + line: 4, + character: 0 + }, + end: { + line: 4, + character: 4 + } + }, + newText: '' + } + ] + } + ] + }, + kind: 'source.organizeImports' + } + ]); + }); + it('should do extract into function refactor', async () => { const { provider, document } = setup('codeactions.svelte'); @@ -2310,4 +2420,70 @@ describe('CodeActionsProvider', function () { assert.deepStrictEqual(codeActions, []); }); + + if (!isSvelte5Plus) { + return; + } + + it('organizes imports with top-level snippets', async () => { + const { provider, document } = setup('organize-imports-snippet.svelte'); + + const codeActions = await provider.getCodeActions( + document, + Range.create(Position.create(4, 15), Position.create(4, 15)), + { + diagnostics: [], + only: [CodeActionKind.SourceOrganizeImports] + } + ); + + (codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach( + (edit) => (edit.newText = harmonizeNewLines(edit.newText)) + ); + + assert.deepStrictEqual(codeActions, [ + { + edit: { + documentChanges: [ + { + edits: [ + { + newText: '', + range: { + end: { + character: 0, + line: 5 + }, + start: { + character: 4, + line: 4 + } + } + }, + { + newText: '', + range: { + end: { + character: 4, + line: 4 + }, + start: { + character: 0, + line: 4 + } + } + } + ], + textDocument: { + uri: getUri('organize-imports-snippet.svelte'), + version: null + } + } + ] + }, + kind: 'source.organizeImports', + title: 'Organize Imports' + } + ]); + }); }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-import-all-remove.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-import-all-remove.svelte new file mode 100644 index 000000000..92836210a --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-import-all-remove.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-snippet.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-snippet.svelte new file mode 100644 index 000000000..4d9159cac --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-snippet.svelte @@ -0,0 +1,9 @@ + + + + +{#snippet baz()} +{/snippet} \ No newline at end of file From 9d719dbfbd43ee4d068bb163731b62a8789d7960 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:25:25 +0800 Subject: [PATCH 04/39] chore: bump to typescript 5.9 (#2808) * chore: bump to typescript 5.9 * typescript-plugin also needs support for the new parameters --- package.json | 2 +- packages/language-server/package.json | 2 +- packages/language-server/src/ls-config.ts | 24 ++++++++-- .../typescript/features/HoverProvider.ts | 8 +++- .../component-handler/expectedv2.json | 4 +- .../fixtures/element-handler/expectedv2.json | 12 ++--- packages/svelte-check/package.json | 2 +- packages/svelte-vscode/package.json | 2 +- packages/svelte-vscode/src/extension.ts | 2 + packages/svelte2tsx/package.json | 2 +- .../expected/TestNoScript.svelte.d.ts | 4 +- packages/typescript-plugin/package.json | 2 +- .../src/language-service/hover.ts | 6 +-- pnpm-lock.yaml | 46 +++++++++---------- 14 files changed, 70 insertions(+), 48 deletions(-) diff --git a/package.json b/package.json index 5f6d5b13a..e2eeff50c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "prettier --check ." }, "dependencies": { - "typescript": "^5.8.2" + "typescript": "^5.9.2" }, "devDependencies": { "cross-env": "^7.0.2", diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 7ecaf5e97..668cdc65b 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -62,7 +62,7 @@ "prettier-plugin-svelte": "^3.4.0", "svelte": "^4.2.19", "svelte2tsx": "workspace:~", - "typescript": "^5.8.2", + "typescript": "^5.9.2", "typescript-auto-import-cache": "^0.3.6", "vscode-css-languageservice": "~6.3.5", "vscode-html-languageservice": "~5.4.0", diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index 1d82adebf..ff1ddb059 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -212,6 +212,10 @@ export interface TSUserConfig { workspaceSymbols?: TsWorkspaceSymbolsConfig; } +interface TsJsSharedConfig { + hover?: { maximumLength?: number }; +} + /** * A subset of the JS/TS VS Code settings which * are transformed to ts.UserPreferences. @@ -291,6 +295,12 @@ export interface TsWorkspaceSymbolsConfig { export type TsUserConfigLang = 'typescript' | 'javascript'; +interface TsUserConfigLangMap { + typescript?: TSUserConfig; + javascript?: TSUserConfig; + 'js/ts'?: TsJsSharedConfig; +} + /** * The config as the vscode-css-languageservice understands it */ @@ -434,10 +444,11 @@ export class LSConfigManager { ); } - updateTsJsUserPreferences(config: Record): void { + updateTsJsUserPreferences(config: TsUserConfigLangMap): void { + const shared = config['js/ts']; (['typescript', 'javascript'] as const).forEach((lang) => { if (config[lang]) { - this._updateTsUserPreferences(lang, config[lang]); + this._updateTsUserPreferences(lang, config[lang], shared); this.rawTsUserConfig[lang] = config[lang]; } }); @@ -458,7 +469,11 @@ export class LSConfigManager { this.notifyListeners(); } - private _updateTsUserPreferences(lang: TsUserConfigLang, config: TSUserConfig) { + private _updateTsUserPreferences( + lang: TsUserConfigLang, + config: TSUserConfig, + shared?: TsJsSharedConfig + ) { const { inlayHints } = config; this.tsUserPreferences[lang] = { @@ -482,6 +497,7 @@ export class LSConfigManager { includeCompletionsWithObjectLiteralMethodSnippets: config.suggest?.objectLiteralMethodSnippets?.enabled ?? true, preferTypeOnlyAutoImports: config.preferences?.preferTypeOnlyAutoImports, + maximumHoverLength: shared?.hover?.maximumLength, // Although we don't support incompletion cache. // But this will make ts resolve the module specifier more aggressively @@ -618,7 +634,7 @@ export class LSConfigManager { return this.htmlConfig; } - updateTsJsFormateConfig(config: Record): void { + updateTsJsFormateConfig(config: TsUserConfigLangMap): void { (['typescript', 'javascript'] as const).forEach((lang) => { if (config[lang]) { this._updateTsFormatConfig(lang, config[lang]); diff --git a/packages/language-server/src/plugins/typescript/features/HoverProvider.ts b/packages/language-server/src/plugins/typescript/features/HoverProvider.ts index ebd9f69c6..bb6635f87 100644 --- a/packages/language-server/src/plugins/typescript/features/HoverProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/HoverProvider.ts @@ -12,7 +12,7 @@ export class HoverProviderImpl implements HoverProvider { constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {} async doHover(document: Document, position: Position): Promise { - const { lang, tsDoc } = await this.getLSAndTSDoc(document); + const { lang, tsDoc, userPreferences } = await this.getLSAndTSDoc(document); const eventHoverInfo = this.getEventHoverInfo(lang, document, tsDoc, position); if (eventHoverInfo) { @@ -20,7 +20,11 @@ export class HoverProviderImpl implements HoverProvider { } const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)); - const info = lang.getQuickInfoAtPosition(tsDoc.filePath, offset); + const info = lang.getQuickInfoAtPosition( + tsDoc.filePath, + offset, + userPreferences.maximumHoverLength + ); if (!info) { return null; } diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json index d36c82693..0aef681fa 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json @@ -12,8 +12,8 @@ "value": "MouseEvent", "location": { "range": { - "start": { "line": 16546, "character": 10 }, - "end": { "line": 16546, "character": 20 } + "start": { "line": 20928, "character": 10 }, + "end": { "line": 20928, "character": 20 } }, "uri": "/typescript/lib/lib.dom.d.ts" } diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json index 0d85aa1e4..07b7976c0 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json @@ -6,8 +6,8 @@ "value": "MouseEvent", "location": { "range": { - "start": { "line": 16546, "character": 10 }, - "end": { "line": 16546, "character": 20 } + "start": { "line": 20928, "character": 10 }, + "end": { "line": 20928, "character": 20 } }, "uri": "/typescript/lib/lib.dom.d.ts" } @@ -21,8 +21,8 @@ "value": "EventTarget", "location": { "range": { - "start": { "line": 8857, "character": 10 }, - "end": { "line": 8857, "character": 21 } + "start": { "line": 11562, "character": 10 }, + "end": { "line": 11562, "character": 21 } }, "uri": "/typescript/lib/lib.dom.d.ts" } @@ -32,8 +32,8 @@ "value": "HTMLButtonElement", "location": { "range": { - "start": { "line": 10333, "character": 10 }, - "end": { "line": 10333, "character": 27 } + "start": { "line": 13261, "character": 10 }, + "end": { "line": 13261, "character": 27 } }, "uri": "/typescript/lib/lib.dom.d.ts" } diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 771bdaf91..14690e918 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -54,7 +54,7 @@ "rollup-plugin-copy": "^3.4.0", "svelte": "^4.2.19", "svelte-language-server": "workspace:*", - "typescript": "^5.8.2", + "typescript": "^5.9.2", "vscode-languageserver": "8.0.2", "vscode-languageserver-protocol": "3.17.2", "vscode-languageserver-types": "3.17.2", diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index f97cf561f..74d761fbb 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -746,7 +746,7 @@ "js-yaml": "^3.14.0", "semver": "^7.7.2", "tslib": "^2.4.0", - "typescript": "^5.8.2", + "typescript": "^5.9.2", "vitest": "^3.2.4", "vscode-tmgrammar-test": "^0.0.11" }, diff --git a/packages/svelte-vscode/src/extension.ts b/packages/svelte-vscode/src/extension.ts index dae135bf8..b8f435a0f 100644 --- a/packages/svelte-vscode/src/extension.ts +++ b/packages/svelte-vscode/src/extension.ts @@ -175,6 +175,7 @@ export function activateSvelteLanguageServer(context: ExtensionContext) { 'emmet', 'javascript', 'typescript', + 'js/ts', 'css', 'less', 'scss', @@ -188,6 +189,7 @@ export function activateSvelteLanguageServer(context: ExtensionContext) { emmet: workspace.getConfiguration('emmet'), typescript: workspace.getConfiguration('typescript'), javascript: workspace.getConfiguration('javascript'), + 'js/ts': workspace.getConfiguration('js/ts'), css: workspace.getConfiguration('css'), less: workspace.getConfiguration('less'), scss: workspace.getConfiguration('scss'), diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index d11e00376..3f9268a83 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -40,7 +40,7 @@ "svelte": "~4.2.19", "tiny-glob": "^0.2.6", "tslib": "^2.4.0", - "typescript": "^5.8.2" + "typescript": "^5.9.2" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", diff --git a/packages/svelte2tsx/test/emitDts/samples/javascript/expected/TestNoScript.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/javascript/expected/TestNoScript.svelte.d.ts index 86d5e6eac..20eb7e090 100644 --- a/packages/svelte2tsx/test/emitDts/samples/javascript/expected/TestNoScript.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/javascript/expected/TestNoScript.svelte.d.ts @@ -4,7 +4,7 @@ export default class TestNoScript extends SvelteComponentTyped<{ [x: string]: never; }, { - click: MouseEvent; + click: PointerEvent; } & { [evt: string]: CustomEvent; }, { @@ -20,7 +20,7 @@ declare const __propDef: { [x: string]: never; }; events: { - click: MouseEvent; + click: PointerEvent; } & { [evt: string]: CustomEvent; }; diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index 25e2016db..fe6d24c04 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -25,7 +25,7 @@ "devDependencies": { "@types/node": "^18.0.0", "svelte": "^4.2.19", - "typescript": "^5.8.2" + "typescript": "^5.9.2" }, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", diff --git a/packages/typescript-plugin/src/language-service/hover.ts b/packages/typescript-plugin/src/language-service/hover.ts index dbf6fa76d..c2abcb1e7 100644 --- a/packages/typescript-plugin/src/language-service/hover.ts +++ b/packages/typescript-plugin/src/language-service/hover.ts @@ -13,13 +13,13 @@ export function decorateHover( ): void { const getQuickInfoAtPosition = ls.getQuickInfoAtPosition; - ls.getQuickInfoAtPosition = (fileName: string, position: number) => { + ls.getQuickInfoAtPosition = (fileName: string, position: number, ...rest) => { const result = getVirtualLS(fileName, info, ts); - if (!result) return getQuickInfoAtPosition(fileName, position); + if (!result) return getQuickInfoAtPosition(fileName, position, ...rest); const { languageService, toOriginalPos, toVirtualPos } = result; const virtualPos = toVirtualPos(position); - const quickInfo = languageService.getQuickInfoAtPosition(fileName, virtualPos); + const quickInfo = languageService.getQuickInfoAtPosition(fileName, virtualPos, ...rest); if (!quickInfo) return quickInfo; const source = languageService.getProgram()?.getSourceFile(fileName); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38c0cb205..fdd9c6d01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.9.2 + version: 5.9.2 devDependencies: cross-env: specifier: ^7.0.2 @@ -20,7 +20,7 @@ importers: version: 3.3.3 ts-node: specifier: ^10.0.0 - version: 10.9.1(@types/node@18.19.46)(typescript@5.8.2) + version: 10.9.1(@types/node@18.19.46)(typescript@5.9.2) packages/language-server: dependencies: @@ -58,8 +58,8 @@ importers: specifier: workspace:~ version: link:../svelte2tsx typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.9.2 + version: 5.9.2 typescript-auto-import-cache: specifier: ^0.3.6 version: 0.3.6 @@ -111,7 +111,7 @@ importers: version: 11.1.2 ts-node: specifier: ^10.0.0 - version: 10.9.1(@types/node@18.19.46)(typescript@5.8.2) + version: 10.9.1(@types/node@18.19.46)(typescript@5.9.2) packages/svelte-check: dependencies: @@ -145,7 +145,7 @@ importers: version: 5.0.2(rollup@3.7.5) '@rollup/plugin-typescript': specifier: ^10.0.0 - version: 10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.8.2) + version: 10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.9.2) '@types/sade': specifier: ^1.7.2 version: 1.7.4 @@ -168,8 +168,8 @@ importers: specifier: workspace:* version: link:../language-server typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.9.2 + version: 5.9.2 vscode-languageserver: specifier: 8.0.2 version: 8.0.2 @@ -223,8 +223,8 @@ importers: specifier: ^2.4.0 version: 2.5.2 typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.9.2 + version: 5.9.2 vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@18.19.46) @@ -258,7 +258,7 @@ importers: version: 15.0.2(rollup@3.7.5) '@rollup/plugin-typescript': specifier: ^10.0.0 - version: 10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.8.2) + version: 10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.9.2) '@types/estree': specifier: ^0.0.42 version: 0.0.42 @@ -308,8 +308,8 @@ importers: specifier: ^2.4.0 version: 2.5.2 typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.9.2 + version: 5.9.2 packages/typescript-plugin: dependencies: @@ -327,8 +327,8 @@ importers: specifier: ^4.2.19 version: 4.2.19 typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.9.2 + version: 5.9.2 packages: @@ -1716,8 +1716,8 @@ packages: typescript-auto-import-cache@0.3.6: resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -2087,11 +2087,11 @@ snapshots: optionalDependencies: rollup: 3.7.5 - '@rollup/plugin-typescript@10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.8.2)': + '@rollup/plugin-typescript@10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.9.2)': dependencies: '@rollup/pluginutils': 5.0.2(rollup@3.7.5) resolve: 1.22.2 - typescript: 5.8.2 + typescript: 5.9.2 optionalDependencies: rollup: 3.7.5 tslib: 2.5.2 @@ -3170,7 +3170,7 @@ snapshots: dependencies: is-number: 7.0.0 - ts-node@10.9.1(@types/node@18.19.46)(typescript@5.8.2): + ts-node@10.9.1(@types/node@18.19.46)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.9 @@ -3184,7 +3184,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.8.2 + typescript: 5.9.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -3196,7 +3196,7 @@ snapshots: dependencies: semver: 7.5.1 - typescript@5.8.2: {} + typescript@5.9.2: {} undici-types@5.26.5: {} From 28cdfb227e831ce54403561419dced2077e9a25b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 14 Aug 2025 04:08:15 -0400 Subject: [PATCH 05/39] Revert "feat: support `` (#2599)" (#2826) This reverts commit cd1758b16f4cb6b176980b2c868efa3c98d30343. --- packages/language-server/src/plugins/html/dataProvider.ts | 6 ------ packages/svelte2tsx/src/htmlxtojsx_v2/index.ts | 2 -- .../test/htmlx2jsx/samples/.svelte-html.v5/expectedv2.js | 1 - .../test/htmlx2jsx/samples/.svelte-html.v5/input.svelte | 1 - 4 files changed, 10 deletions(-) delete mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/expectedv2.js delete mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/input.svelte diff --git a/packages/language-server/src/plugins/html/dataProvider.ts b/packages/language-server/src/plugins/html/dataProvider.ts index a34fbf21f..e4d746bd7 100644 --- a/packages/language-server/src/plugins/html/dataProvider.ts +++ b/packages/language-server/src/plugins/html/dataProvider.ts @@ -270,12 +270,6 @@ const svelteTags: ITagData[] = [ } ] }, - { - name: 'svelte:html', - description: - 'This element allows you to add properties and listeners to events on `document.documentElement`. This is useful for attributes such as `lang` which influence how the browser interprets the content.', - attributes: [] - }, { name: 'svelte:document', description: diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts index adab49df3..d6bb965ea 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts @@ -306,7 +306,6 @@ export function convertHtmlxToJsx( case 'Title': case 'Document': case 'Body': - case 'SvelteHTML': case 'SvelteBoundary': case 'Slot': case 'SlotTemplate': @@ -475,7 +474,6 @@ export function convertHtmlxToJsx( case 'Head': case 'Title': case 'Body': - case 'SvelteHTML': case 'SvelteBoundary': case 'Document': case 'Slot': diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/expectedv2.js deleted file mode 100644 index 23e947d26..000000000 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/expectedv2.js +++ /dev/null @@ -1 +0,0 @@ - { svelteHTML.createElement("svelte:html", { "lang":`de`,}); } \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/input.svelte deleted file mode 100644 index cd4ed850b..000000000 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-html.v5/input.svelte +++ /dev/null @@ -1 +0,0 @@ - From 49d38fcbadc00c52031df565a33e32a3670ef4c9 Mon Sep 17 00:00:00 2001 From: Oleg Polin <140200108+olegpolin@users.noreply.github.com> Date: Thu, 14 Aug 2025 04:31:48 -0400 Subject: [PATCH 06/39] fix: Update +error.svelte creation template in vscode extension (#2811) * Update +error.svelte creation syntax (#2810) * Add Svelte 5 variants to +error.svelte creation syntax template (#2810) * Change withRunes to withProps in +error.svelte template * Add withAppState option * Change withRunes to withAppState in +error.svelte template * Remove trailing comma * Add type for withAppState --- .../generateFiles/templates/error.ts | 26 +++++++++++++++++-- .../src/sveltekit/generateFiles/types.ts | 1 + packages/svelte-vscode/src/sveltekit/utils.ts | 10 ++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/svelte-vscode/src/sveltekit/generateFiles/templates/error.ts b/packages/svelte-vscode/src/sveltekit/generateFiles/templates/error.ts index 1a6407cc6..b8afe9476 100644 --- a/packages/svelte-vscode/src/sveltekit/generateFiles/templates/error.ts +++ b/packages/svelte-vscode/src/sveltekit/generateFiles/templates/error.ts @@ -8,6 +8,14 @@ const defaultScriptTemplate = `

{$page.status}: {$page.error.message}

`; +const jsSv5ScriptTemplateAppState = ` + + +

{page.status}: {page.error.message}

+`; + const tsScriptTemplate = ` + +

{page.status}: {page.error?.message}

+`; + export default async function (config: GenerateConfig): ReturnType { - const { withTs } = config.kind; + const { withTs, withAppState } = config.kind; let template = defaultScriptTemplate; - if (withTs) { + if (withAppState && withTs) { + template = tsSv5ScriptTemplateAppState; + } else if (withAppState && !withTs) { + template = jsSv5ScriptTemplateAppState; + } else if (!withAppState && withTs) { template = tsScriptTemplate; + } else if (!withAppState && !withTs) { + template = defaultScriptTemplate; } return template.trim(); diff --git a/packages/svelte-vscode/src/sveltekit/generateFiles/types.ts b/packages/svelte-vscode/src/sveltekit/generateFiles/types.ts index 978e4dc2c..21d8f5a94 100644 --- a/packages/svelte-vscode/src/sveltekit/generateFiles/types.ts +++ b/packages/svelte-vscode/src/sveltekit/generateFiles/types.ts @@ -39,6 +39,7 @@ export interface GenerateConfig { withSatisfies: boolean; withRunes: boolean; withProps: boolean; + withAppState: boolean; }; pageExtension: string; scriptExtension: string; diff --git a/packages/svelte-vscode/src/sveltekit/utils.ts b/packages/svelte-vscode/src/sveltekit/utils.ts index cce8e2de9..b08740638 100644 --- a/packages/svelte-vscode/src/sveltekit/utils.ts +++ b/packages/svelte-vscode/src/sveltekit/utils.ts @@ -47,6 +47,13 @@ export async function checkProjectKind(path: string): Promise= jsconfig.length); let withSatisfies = false; if (withTs) { @@ -70,7 +77,8 @@ export async function checkProjectKind(path: string): Promise Date: Thu, 14 Aug 2025 01:34:29 -0700 Subject: [PATCH 07/39] chore: add CONTRIBUTING.md (#2816) * chore: add CONTRIBUTING.md * fix: lint-fix CONTRIBUTING.md * slim down, deduplicate * lint --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen --- CONTRIBUTING.md | 185 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 124 +------------------------------- 2 files changed, 187 insertions(+), 122 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..c52021b46 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,185 @@ +# Contributing to Svelte Language Tools + +Svelte Language Tools provides IDE support for Svelte files through the Language Server Protocol (LSP). It powers the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) and enables Svelte support in [numerous other IDEs](https://microsoft.github.io/language-server-protocol/implementors/tools/). + +The [Open Source Guides](https://opensource.guide/) website has a collection of resources for individuals, communities, and companies. These resources help people who want to learn how to run and contribute to open source projects. Contributors and people new to open source alike will find the following guides especially useful: + +- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) +- [Building Welcoming Communities](https://opensource.guide/building-community/) + +## Get involved + +There are many ways to contribute to Svelte Language Tools, and many of them do not involve writing any code. Here's a few ideas to get started: + +- Simply start using Svelte Language Tools in your IDE. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](#reporting-new-issues). +- Look through the [open issues](https://github.com/sveltejs/language-tools/issues). A good starting point would be issues tagged [good first issue](https://github.com/sveltejs/language-tools/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). Provide workarounds, ask for clarification, or suggest labels. Help triage issues. +- If you find an issue you would like to fix, [open a pull request](#pull-requests). +- Read through our [documentation](https://github.com/sveltejs/language-tools/tree/master/docs). If you find anything that is confusing or can be improved, you can make edits by opening a pull request. +- Take a look at the [features requested](https://github.com/sveltejs/language-tools/labels/enhancement) by others in the community and consider opening a pull request if you see something you want to work on. + +Contributions are very welcome. If you think you need help planning your contribution, please reach out to us on [Discord](https://svelte.dev/chat) and let us know you are looking for a bit of help. + +## Development + +This is a monorepo managed with [`pnpm workspaces`](https://pnpm.io/workspaces/). Install pnpm globally with: + +```sh +npm i -g pnpm +``` + +Clone the repository and install dependencies: + +```sh +git clone git@github.com:sveltejs/language-tools.git +cd language-tools +pnpm install +pnpm bootstrap +``` + +### Building + +To build all packages: + +```sh +pnpm build +``` + +To watch for changes: + +```sh +pnpm watch +``` + +### Code structure + +#### Packages + +- [`packages/language-server`](packages/language-server) - The language server for Svelte. +- [`packages/svelte-vscode`](packages/svelte-vscode) - The official VSCode extension for Svelte +- [`packages/svelte2tsx`](packages/svelte2tsx) - Converts `.svelte` files into legal TypeScript/JSX. Want to see how it's transformed? [Check out this REPL](https://embed.plnkr.co/plunk/JPye9tlsqwMrWHGv?show=preview&autoCloseSidebar) +- [`packages/svelte-check`](packages/svelte-check) - CLI tool for type checking and diagnostics + +#### Key entry points + +- `packages/language-server/src/server.ts` - Language server entry point +- `packages/language-server/src/plugins/` - Language service plugins (TypeScript, Svelte, CSS, HTML) +- `packages/svelte2tsx/src/svelte2tsx/index.ts` - Svelte to TSX transformation +- `packages/svelte-vscode/src/extension.ts` - VSCode extension entry point + +#### High Level Overview + +```mermaid +flowchart LR + %% IDEs + VSC[IDE: VSCode + Svelte for VS Code extension] + click VSC "https://github.com/sveltejs/language-tools/tree/master/packages/svelte-vscode" "Svelte for VSCode extension" + %% Tools + CLI[CLI: svelte-check] + click CLI "https://github.com/sveltejs/language-tools/tree/master/packages/svelte-check" "A command line tool to get diagnostics for Svelte code" + %% Svelte - Extensions + VSC_TSSP[typescript-svelte-plugin] + click VSC_TSSP "https://github.com/sveltejs/language-tools/tree/master/packages/typescript-plugin" "A TypeScript plugin for Svelte intellisense" + %% Svelte - Packages + SVELTE_LANGUAGE_SERVER["svelte-language-server"] + SVELTE_COMPILER_SERVICE["svelte2tsx"] + TS_SERVICE["TS/JS intellisense using TypeScript language service"] + SVELTE_SERVICE["Svelte intellisense using Svelte compiler"] + click SVELTE_LANGUAGE_SERVER "https://github.com/sveltejs/language-tools/tree/master/packages/language-server" "A language server adhering to the LSP" + click SVELTE_COMPILER_SERVICE "https://github.com/sveltejs/language-tools/tree/master/packages/language-server/src/plugins/svelte" "Transforms Svelte code into JSX/TSX code" + click TS_SERVICE "https://github.com/sveltejs/language-tools/tree/master/packages/language-server/src/plugins/typescript" + click SVELTE_SERVICE "https://github.com/sveltejs/language-tools/tree/master/packages/language-server/src/plugins/svelte" + %% External Packages + HTML_SERVICE[HTML intellisense using vscode-html-languageservice] + CSS_SERVICE[CSS intellisense using vscode-css-languageservice] + VSC_TS[vscode-typescript-language-features] + click HTML_SERVICE "https://github.com/microsoft/vscode-html-languageservice" + click CSS_SERVICE "https://github.com/microsoft/vscode-css-languageservice" + click VSC_TS "https://github.com/microsoft/vscode/tree/main/extensions/typescript-language-features" + subgraph EMBEDDED_SERVICES[Embedded Language Services] + direction LR + TS_SERVICE + SVELTE_SERVICE + HTML_SERVICE + CSS_SERVICE + end + VSC -- Language Server Protocol --> SVELTE_LANGUAGE_SERVER + CLI -- Only using diagnostics feature --> SVELTE_LANGUAGE_SERVER + VSC -- includes --> VSC_TS + VSC_TS -- loads --> VSC_TSSP + VSC_TSSP -- uses --> SVELTE_COMPILER_SERVICE + TS_SERVICE -- uses --> SVELTE_COMPILER_SERVICE + SVELTE_LANGUAGE_SERVER -- bundles --> EMBEDDED_SERVICES +``` + +More information about the internals can be found [HERE](./docs/internal/overview.md). + +### Running tests + +Run tests for all packages: + +```sh +pnpm test +``` + +Run tests for a specific package: + +```sh +cd packages/[package-name] +pnpm test +``` + +### Testing in VSCode + +To test your changes in VSCode: + +1. Open the repository in VSCode +2. Go to the debug panel (Ctrl+Shift+D / Cmd+Shift+D) +3. Select "Run VSCode Extension" from the dropdown +4. Press F5 to launch a new VSCode instance with your changes + +This launches a new VSCode window and a watcher for your changes. In this dev window you can choose an existing Svelte project to work against. When you make changes to `svelte-vscode` use the command "Reload Window" in the VSCode command palette to see your changes. When you make changes to `language-server` use the command "Svelte: Restart Language Server". When you make changes to `svelte2tsx`, you first need to run `pnpm build` within its folder, then restart the language server. When you make changes to`typescript-plugin`, you need to first run `pnpm build` within its folder and then use the command "TypeScript: Restart Server". + +#### Testing in other editors + +For other editors, you'll need to build the language server and configure your editor to use the local build. See the documentation for your specific editor. + +### Linking local changes + +To test certain local changes in a Svelte project, you might want to use [pnpm `overrides`](https://pnpm.io/package_json#pnpmoverrides) in your project's `package.json`: + +```jsonc +{ + "pnpm": { + "overrides": { + // Test changes to svelte-check: + "svelte-check": "link:../path/to/language-tools/packages/svelte-check", + // You only need this if you're testing the changes with an editor other than VS Code: + "svelte-language-server": "link:../path/to/language-tools/packages/language-server" + } + } +} +``` + +## Pull requests + +Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it. + +Please make sure the following is done when submitting a pull request: + +1. Fork [the repository](https://github.com/sveltejs/language-tools) and create your branch from `master`. +2. Describe your test plan in your pull request description. Make sure to test your changes. +3. Make sure your code lints (`pnpm lint`). +4. Make sure your tests pass (`pnpm test`). +5. Make sure your code is properly formatted (`pnpm format`). + +If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend that you file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. + +All pull requests should be opened against the `master` branch. + +## License + +By contributing to Svelte Language Tools, you agree that your contributions will be licensed under its [MIT license](LICENSE). + +## Questions? + +Join us in the [Svelte Discord](https://svelte.dev/chat) and post your question. diff --git a/README.md b/README.md index 49f42a3b1..10b1db935 100644 --- a/README.md +++ b/README.md @@ -42,129 +42,9 @@ Which is a mix of [HTMLx](https://github.com/htmlx-org/HTMLx) and vanilla JavaSc This repo contains the tools which provide editor integrations for Svelte files like this. -## Packages - -This repo uses [`pnpm workspaces`](https://pnpm.io/workspaces/), which TLDR means if you want to run a command in each project then you can either `cd` to that directory and run the command, or use `pnpm -r [command]`. - -For example `pnpm -r test`. - -#### [`svelte-language-server`](packages/language-server) - -The language server for Svelte. Built from [UnwrittenFun/svelte-language-server](https://github.com/UnwrittenFun/svelte-language-server) and heavily inspired by [Vetur](https://github.com/vuejs/vetur) to become the official language server for the language. - -#### [`svelte-check`](packages/svelte-check) - -A command line tool to check your svelte files for type errors, unused css, and more. Built from [Vetur's VTI](https://github.com/vuejs/vetur/tree/master/vti). - -#### [`svelte-vscode`](packages/svelte-vscode) - -The official vscode extension for Svelte. Built from [UnwrittenFun/svelte-vscode](https://github.com/UnwrittenFun/svelte-vscode) to become the official vscode extension for the language. - -#### [`svelte2tsx`](packages/svelte2tsx) - -Converts a .svelte file into a legal TypeScript file. Built from [halfnelson/svelte2tsx](https://github.com/halfnelson/svelte2tsx) to provide the auto-complete and import mapping inside the language server. - -> Want to see how it's transformed? [Check out this REPL](https://embed.plnkr.co/plunk/JPye9tlsqwMrWHGv?show=preview&autoCloseSidebar) - -## Development - -### High Level Overview - -```mermaid -flowchart LR - %% IDEs - VSC[IDE: VSCode + Svelte for VS Code extension] - click VSC "https://github.com/sveltejs/language-tools/tree/master/packages/svelte-vscode" "Svelte for VSCode extension" - %% Tools - CLI[CLI: svelte-check] - click CLI "https://github.com/sveltejs/language-tools/tree/master/packages/svelte-check" "A command line tool to get diagnostics for Svelte code" - %% Svelte - Extensions - VSC_TSSP[typescript-svelte-plugin] - click VSC_TSSP "https://github.com/sveltejs/language-tools/tree/master/packages/typescript-plugin" "A TypeScript plugin for Svelte intellisense" - %% Svelte - Packages - SVELTE_LANGUAGE_SERVER["svelte-language-server"] - SVELTE_COMPILER_SERVICE["svelte2tsx"] - TS_SERVICE["TS/JS intellisense using TypeScript language service"] - SVELTE_SERVICE["Svelte intellisense using Svelte compiler"] - click SVELTE_LANGUAGE_SERVER "https://github.com/sveltejs/language-tools/tree/master/packages/language-server" "A language server adhering to the LSP" - click SVELTE_COMPILER_SERVICE "https://github.com/sveltejs/language-tools/tree/master/packages/language-server/src/plugins/svelte" "Transforms Svelte code into JSX/TSX code" - click TS_SERVICE "https://github.com/sveltejs/language-tools/tree/master/packages/language-server/src/plugins/typescript" - click SVELTE_SERVICE "https://github.com/sveltejs/language-tools/tree/master/packages/language-server/src/plugins/svelte" - %% External Packages - HTML_SERVICE[HTML intellisense using vscode-html-languageservice] - CSS_SERVICE[CSS intellisense using vscode-css-languageservice] - VSC_TS[vscode-typescript-language-features] - click HTML_SERVICE "https://github.com/microsoft/vscode-html-languageservice" - click CSS_SERVICE "https://github.com/microsoft/vscode-css-languageservice" - click VSC_TS "https://github.com/microsoft/vscode/tree/main/extensions/typescript-language-features" - subgraph EMBEDDED_SERVICES[Embedded Language Services] - direction LR - TS_SERVICE - SVELTE_SERVICE - HTML_SERVICE - CSS_SERVICE - end - VSC -- Language Server Protocol --> SVELTE_LANGUAGE_SERVER - CLI -- Only using diagnostics feature --> SVELTE_LANGUAGE_SERVER - VSC -- includes --> VSC_TS - VSC_TS -- loads --> VSC_TSSP - VSC_TSSP -- uses --> SVELTE_COMPILER_SERVICE - TS_SERVICE -- uses --> SVELTE_COMPILER_SERVICE - SVELTE_LANGUAGE_SERVER -- bundles --> EMBEDDED_SERVICES -``` - -More information about the internals can be found [HERE](./docs/internal/overview.md). - -#### Setup - -Pull requests are encouraged and always welcome. [Pick an issue](https://github.com/sveltejs/language-tools/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) and help us out! - -To install and work on these tools locally: - -> Make sure to uninstall the extension from the marketplace to not have it clash with the local one. - -```bash -git clone https://github.com/sveltejs/language-tools.git svelte-language-tools -cd svelte-language-tools -pnpm install -pnpm bootstrap -``` - -> Do not use npm to install the dependencies, as the specific package versions in `pnpm-lock.yaml` are used to build and test Svelte. - -To build all of the tools, run: - -```bash -pnpm build -``` - -The tools are written in [TypeScript](https://www.typescriptlang.org/), but don't let that put you off — it's basically just JavaScript with type annotations. You'll pick it up in no time. If you're using an editor other than [Visual Studio Code](https://code.visualstudio.com/) you may need to install a plugin in order to get syntax highlighting and code hints etc. - -#### Making Changes - -There are two ways to work on this project: either by working against an existing project or entirely through tests. - -## Running the Dev Language Server Against Your App - -To run the developer version of both the language server and the VSCode extension: - -- open the root of this repo in VSCode -- Go to the debugging panel -- Make sure "Run VSCode Extension" is selected, and hit run - -This launches a new VSCode window and a watcher for your changes. In this dev window you can choose an existing Svelte project to work against. If you don't use pure Javascript and CSS, but languages like Typescript or SCSS, your project will need a [Svelte preprocessor setup](docs#using-with-preprocessors). When you make changes to the extension or language server you can use the command "Reload Window" in the VSCode command palette to see your changes. When you make changes to `svelte2tsx`, you first need to run `pnpm build` within its folder. - -### Running Tests - -You might think that as a language server, you'd need to handle a lot of back and forth between APIs, but actually it's mostly high-level JavaScript objects which are passed to the [npm module vscode-languageserver](https://code.visualstudio.com/api/language-extensions/language-server-extension-guide). - -This means it's easy to write tests for your changes: - -```bash -pnpm test -``` +## Contributing -For trickier issues, you can run the tests with a debugger in VSCode by setting a breakpoint (or adding `debugger` in the code) and launching the test in the [JavaScript Debug Terminal](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_javascript-debug-terminal). +Contributions are encouraged and always welcome. [Read the contribution guide for more info](CONTRIBUTING.md) and help us out! ## Supporting Svelte From 2c99c58a62b9f1d0d6c4736d171692f2ba226c73 Mon Sep 17 00:00:00 2001 From: Edoardo Cavazza Date: Thu, 14 Aug 2025 10:35:47 +0200 Subject: [PATCH 08/39] fix: builtin custom elements check when transforming attributes (#2824) * Fix builtin custom elements check when transforming attributes * Fix lint issue --- .../src/htmlxtojsx_v2/nodes/Attribute.ts | 2 +- .../svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts | 14 ++++++++++++++ .../custom-element-builtin-attribute/expectedv2.js | 1 + .../custom-element-builtin-attribute/input.svelte | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/expectedv2.js create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/input.svelte diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts index 8a4221b88..3f44c6a05 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts @@ -113,7 +113,7 @@ export function handleAttribute( if ( !preserveCase && !svgAttributes.find((x) => x == name) && - !(element instanceof Element && element.tagName.includes('-')) && + !(element instanceof Element && element.isCustomElement()) && !(svelte5Plus && name.startsWith('on')) ) { return name.toLowerCase(); diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts index bd8947c64..84ba4bb9b 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts @@ -255,6 +255,20 @@ export class Element { } } + isCustomElement(): boolean { + if (this.tagName.includes('-')) { + return true; + } + if ( + this.node.attributes + ?.find((a: BaseNode) => a.name === 'is') + ?.value[0]?.data.includes('-') + ) { + return true; + } + return false; + } + private getStartTransformation(): TransformationArray { const createElement = `${this.typingsNamespace}.createElement`; const addActions = () => { diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/expectedv2.js new file mode 100644 index 000000000..615ef7551 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/expectedv2.js @@ -0,0 +1 @@ + { svelteHTML.createElement("div", { "is":`custom-element`,"camelCase":`true`,"kebab-case":`true`,"PascalCase":`true`,"snake_case":`true`,});} diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/input.svelte new file mode 100644 index 000000000..f5f7b1b8d --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-element-builtin-attribute/input.svelte @@ -0,0 +1 @@ +
From 99e29f2b803b5541d3c5243ef54154674fbcc76c Mon Sep 17 00:00:00 2001 From: Drew Date: Tue, 26 Aug 2025 06:30:01 -0700 Subject: [PATCH 09/39] fix: respect tsconfig/jsconfig exclude patterns in file watcher (#2807) * fix: respect tsconfig exclude patterns in file watcher - Add getProjectConfig() to language server to expose parsed tsconfig - Process wildcard directories in svelte-check to determine watch paths - Support both recursive and non-recursive directory watching based on TypeScript's configuration - Handle relative paths correctly for directories outside workspace This ensures svelte-check only watches directories included by the tsconfig, improving performance and avoiding unnecessary file watching. * refactor: separate watcher updates from diagnostics scheduling * prettier fix * fix: discover missed watch directories with snapshots * refactor(check): clarify snapshot callback and simplify directory watching; ignore .crush * fix: order-of-operations for diagnostics and watch directories * fix: update watch directories before initial diagnostics --------- --- .gitignore | 3 + .../plugins/typescript/LSAndTSDocResolver.ts | 19 +++ .../src/plugins/typescript/service.ts | 6 + packages/language-server/src/svelte-check.ts | 29 ++++- packages/svelte-check/src/index.ts | 118 +++++++++++++++--- 5 files changed, 157 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index d64228482..dce9dc14c 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ dist # VSCode history extension .history + +# Ignore AI artifacts +.crush/ diff --git a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts index b62680027..f390c5b37 100644 --- a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts +++ b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts @@ -46,6 +46,12 @@ interface LSAndTSDocResolverOptions { tsSystem?: ts.System; watchDirectory?: (patterns: RelativePattern[]) => void; nonRecursiveWatchPattern?: string; + /** + * Optional callback invoked when a new snapshot is created. + * Passes the absolute file path of the created snapshot. + * Consumers (like svelte-check) can derive the directory as needed. + */ + onFileSnapshotCreated?: (filePath: string) => void; } export class LSAndTSDocResolver { @@ -83,6 +89,19 @@ export class LSAndTSDocResolver { this.tsSystem = this.wrapWithPackageJsonMonitoring(this.options?.tsSystem ?? ts.sys); this.globalSnapshotsManager = new GlobalSnapshotsManager(this.tsSystem); + // Notify when new snapshots are created so external watchers (svelte-check) + // can react dynamically (for example: add parent directories to file watchers). + if (this.options?.onFileSnapshotCreated) { + this.globalSnapshotsManager.onChange((fileName, newDocument) => { + if (newDocument) { + try { + this.options?.onFileSnapshotCreated?.(fileName); + } catch { + // best-effort; ignore errors in callback + } + } + }); + } this.userPreferencesAccessor = { preferences: this.getTsUserPreferences() }; const projectService = createProjectService(this.tsSystem, this.userPreferencesAccessor); diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index c2cf2e02d..cffba1ae0 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -60,6 +60,7 @@ export interface LanguageServiceContainer { getResolvedProjectReferences(): TsConfigInfo[]; openVirtualDocument(document: Document): void; isShimFiles(filePath: string): boolean; + getProjectConfig(): ts.ParsedCommandLine; dispose(): void; } @@ -458,6 +459,7 @@ async function createLanguageService( getResolvedProjectReferences, openVirtualDocument, isShimFiles, + getProjectConfig, dispose }; @@ -1249,6 +1251,10 @@ async function createLanguageService( function isShimFiles(filePath: string) { return svelteTsxFilesToOriginalCasing.has(getCanonicalFileName(normalizePath(filePath))); } + + function getProjectConfig() { + return projectConfig; + } } /** diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 75a62b173..00f0d4abb 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -31,6 +31,11 @@ export interface SvelteCheckOptions { tsconfig?: string; onProjectReload?: () => void; watch?: boolean; + /** + * Optional callback invoked when a new snapshot is created. + * Provides the absolute file path of the snapshot. + */ + onFileSnapshotCreated?: (filePath: string) => void; } /** @@ -91,7 +96,8 @@ export class SvelteCheck { tsconfigPath: options.tsconfig, isSvelteCheck: true, onProjectReloaded: options.onProjectReload, - watch: options.watch + watch: options.watch, + onFileSnapshotCreated: options.onFileSnapshotCreated } ); this.pluginHost.register( @@ -353,4 +359,25 @@ export class SvelteCheck { } return this.lsAndTSDocResolver.getTSService(tsconfigPath); } + + /** + * Gets the watch directories based on the tsconfig include patterns. + * Returns null if no tsconfig is specified. + */ + async getWatchDirectories(): Promise<{ path: string; recursive: boolean }[] | null> { + if (!this.options.tsconfig) { + return null; + } + const lsContainer = await this.getLSContainer(this.options.tsconfig); + const projectConfig = lsContainer.getProjectConfig(); + + if (!projectConfig.wildcardDirectories) { + return null; + } + + return Object.entries(projectConfig.wildcardDirectories).map(([dir, flags]) => ({ + path: dir, + recursive: !!(flags & ts.WatchDirectoryFlags.Recursive) + })); + } } diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index 25749a164..aaeffc422 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -2,7 +2,7 @@ * This code's groundwork is taken from https://github.com/vuejs/vetur/tree/master/vti */ -import { watch } from 'chokidar'; +import { watch, FSWatcher } from 'chokidar'; import * as fs from 'fs'; import { fdir } from 'fdir'; import * as path from 'path'; @@ -143,35 +143,44 @@ async function getDiagnostics( } } +const FILE_ENDING_REGEX = /\.(svelte|d\.ts|ts|js|jsx|tsx|mjs|cjs|mts|cts)$/; +const VITE_CONFIG_REGEX = /vite\.config\.(js|ts)\.timestamp-/; + class DiagnosticsWatcher { private updateDiagnostics: any; + private watcher: FSWatcher; + private currentWatchedDirs = new Set(); + private userIgnored: Array<(path: string) => boolean>; + private pendingWatcherUpdate: any; constructor( private workspaceUri: URI, private svelteCheck: SvelteCheck, private writer: Writer, filePathsToIgnore: string[], - ignoreInitialAdd: boolean + private ignoreInitialAdd: boolean ) { - const fileEnding = /\.(svelte|d\.ts|ts|js|jsx|tsx|mjs|cjs|mts|cts)$/; - const viteConfigRegex = /vite\.config\.(js|ts)\.timestamp-/; - const userIgnored = createIgnored(filePathsToIgnore); - const offset = workspaceUri.fsPath.length + 1; + this.userIgnored = createIgnored(filePathsToIgnore); - watch(workspaceUri.fsPath, { + // Create watcher with initial paths + this.watcher = watch([], { ignored: (path, stats) => { if ( path.includes('node_modules') || path.includes('.git') || - (stats?.isFile() && (!fileEnding.test(path) || viteConfigRegex.test(path))) + (stats?.isFile() && + (!FILE_ENDING_REGEX.test(path) || VITE_CONFIG_REGEX.test(path))) ) { return true; } - if (userIgnored.length !== 0) { - path = path.slice(offset); - for (const i of userIgnored) { - if (i(path)) { + if (this.userIgnored.length !== 0) { + // Make path relative to workspace for user ignores + const workspaceRelative = path.startsWith(this.workspaceUri.fsPath) + ? path.slice(this.workspaceUri.fsPath.length + 1) + : path; + for (const i of this.userIgnored) { + if (i(workspaceRelative)) { return true; } } @@ -179,15 +188,76 @@ class DiagnosticsWatcher { return false; }, - ignoreInitial: ignoreInitialAdd + ignoreInitial: this.ignoreInitialAdd }) .on('add', (path) => this.updateDocument(path, true)) .on('unlink', (path) => this.removeDocument(path)) .on('change', (path) => this.updateDocument(path, false)); - if (ignoreInitialAdd) { - this.scheduleDiagnostics(); + this.updateWatchedDirectories(); + if (this.ignoreInitialAdd) { + getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck); + } + } + + private isSubdir(candidate: string, parent: string) { + const c = path.resolve(candidate); + const p = path.resolve(parent); + return c === p || c.startsWith(p + path.sep); + } + + private minimizeDirs(dirs: string[]): string[] { + const sorted = [...new Set(dirs.map((d) => path.resolve(d)))].sort(); + const result: string[] = []; + for (const dir of sorted) { + if (!result.some((p) => this.isSubdir(dir, p))) { + result.push(dir); + } + } + return result; + } + + addWatchDirectory(dir: string) { + if (!dir) return; + // Skip if already covered by an existing watched directory + for (const existing of this.currentWatchedDirs) { + if (this.isSubdir(dir, existing)) { + return; + } } + // If new dir is a parent of existing ones, unwatch children + const toRemove: string[] = []; + for (const existing of this.currentWatchedDirs) { + if (this.isSubdir(existing, dir)) { + toRemove.push(existing); + } + } + if (toRemove.length) { + this.watcher.unwatch(toRemove); + for (const r of toRemove) this.currentWatchedDirs.delete(r); + } + this.watcher.add(dir); + this.currentWatchedDirs.add(dir); + } + + private async updateWatchedDirectories() { + const watchDirs = await this.svelteCheck.getWatchDirectories(); + const desired = this.minimizeDirs( + (watchDirs?.map((d) => d.path) || [this.workspaceUri.fsPath]).map((p) => + path.resolve(p) + ) + ); + + const current = new Set([...this.currentWatchedDirs].map((p) => path.resolve(p))); + const desiredSet = new Set(desired); + + const toAdd = desired.filter((d) => !current.has(d)); + const toRemove = [...current].filter((d) => !desiredSet.has(d)); + + if (toAdd.length) this.watcher.add(toAdd); + if (toRemove.length) this.watcher.unwatch(toRemove); + + this.currentWatchedDirs = new Set(desired); } private async updateDocument(path: string, isNew: boolean) { @@ -210,6 +280,11 @@ class DiagnosticsWatcher { this.scheduleDiagnostics(); } + updateWatchers() { + clearTimeout(this.pendingWatcherUpdate); + this.pendingWatcherUpdate = setTimeout(() => this.updateWatchedDirectories(), 1000); + } + scheduleDiagnostics() { clearTimeout(this.updateDiagnostics); this.updateDiagnostics = setTimeout( @@ -264,8 +339,17 @@ parseOptions(async (opts) => { }; if (opts.watch) { - svelteCheckOptions.onProjectReload = () => watcher.scheduleDiagnostics(); - const watcher = new DiagnosticsWatcher( + // Wire callbacks that can reference the watcher instance created below + let watcher: DiagnosticsWatcher; + svelteCheckOptions.onProjectReload = () => { + watcher.updateWatchers(); + watcher.scheduleDiagnostics(); + }; + svelteCheckOptions.onFileSnapshotCreated = (filePath: string) => { + const dirPath = path.dirname(filePath); + watcher.addWatchDirectory(dirPath); + }; + watcher = new DiagnosticsWatcher( opts.workspaceUri, new SvelteCheck(opts.workspaceUri.fsPath, svelteCheckOptions), writer, From 469f52695c18ac3a22f55f53dea4b05c017c7b8d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:32:30 +0200 Subject: [PATCH 10/39] chore: setup changesets + oidc (#2844) - setup changesets: all packages are now released through changesets, only the extension is released via tag as before - remove pkg deploy flows in favor of one - that flow will use oidc, so no npm token anymore - also harden the workflows --- .changeset/README.md | 8 + .changeset/config.json | 15 + .github/workflows/CI.yml | 5 + .github/workflows/DeployExtensionsProd.yml | 8 +- .github/workflows/DeploySvelte2tsxProd.yml | 41 -- .github/workflows/DeploySvelteCheckProd.yml | 42 -- .../DeploySvelteLanguageServerProd.yml | 41 -- .../workflows/DeployTypescriptPluginProd.yaml | 41 -- .github/workflows/release.yml | 44 ++ package.json | 5 +- pnpm-lock.yaml | 518 ++++++++++++++++++ 11 files changed, 601 insertions(+), 167 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json delete mode 100644 .github/workflows/DeploySvelte2tsxProd.yml delete mode 100644 .github/workflows/DeploySvelteCheckProd.yml delete mode 100644 .github/workflows/DeploySvelteLanguageServerProd.yml delete mode 100644 .github/workflows/DeployTypescriptPluginProd.yaml create mode 100644 .github/workflows/release.yml diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..e5b6d8d6a --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..9d62a2143 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": [ + "@svitejs/changesets-changelog-github-compact", + { "repo": "sveltejs/language-tools" } + ], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "master", + "updateInternalDependencies": "minor", + "bumpVersionsWithWorkspaceProtocolOnly": true, + "ignore": [] +} diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2da0b03ac..c42f693a7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,8 +1,11 @@ name: CI on: pull_request +permissions: + contents: read # to fetch code (actions/checkout) jobs: test: + permissions: {} runs-on: ubuntu-latest steps: @@ -24,6 +27,7 @@ jobs: CI: true test-svelte5: + permissions: {} runs-on: ubuntu-latest steps: @@ -49,6 +53,7 @@ jobs: CI: true lint: + permissions: {} runs-on: ubuntu-latest steps: diff --git a/.github/workflows/DeployExtensionsProd.yml b/.github/workflows/DeployExtensionsProd.yml index 6f89f669c..4cd72b7ee 100644 --- a/.github/workflows/DeployExtensionsProd.yml +++ b/.github/workflows/DeployExtensionsProd.yml @@ -5,8 +5,14 @@ on: tags: - "extensions-*" +permissions: {} + jobs: deploy: + # prevents this action from running on forks + if: github.repository == 'sveltejs/language-tools' + permissions: + contents: write # to create release (adjust package.json etc) runs-on: ubuntu-latest steps: @@ -14,7 +20,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: "20.x" + node-version: "22.x" registry-url: "https://registry.npmjs.org" cache: pnpm diff --git a/.github/workflows/DeploySvelte2tsxProd.yml b/.github/workflows/DeploySvelte2tsxProd.yml deleted file mode 100644 index 100316e6b..000000000 --- a/.github/workflows/DeploySvelte2tsxProd.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Tagged Production Deploys for svelte2tsx - -on: - push: - tags: - - "svelte2tsx-*" - -jobs: - deploy: - permissions: - id-token: write # OpenID Connect token needed for provenance - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: "20.x" - registry-url: "https://registry.npmjs.org" - cache: pnpm - - # Ensure everything is compiling - - run: "pnpm install" - - run: "pnpm build" - - # Lets us use one-liner JSON manipulations the package.json files - - run: "npm install -g json" - - # Setup the environment - - run: 'json -I -f packages/svelte2tsx/package.json -e "this.version=\`${{ github.ref }}\`.split(\`-\`).pop()"' - - # Ship it - - run: | - cd packages/svelte2tsx - pnpm install - pnpm publish --provenance --no-git-checks - - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/DeploySvelteCheckProd.yml b/.github/workflows/DeploySvelteCheckProd.yml deleted file mode 100644 index f5fc54cad..000000000 --- a/.github/workflows/DeploySvelteCheckProd.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Tagged Production Deploys for svelte-check - -on: - push: - tags: - - "svelte-check-*" - -jobs: - deploy: - permissions: - id-token: write # OpenID Connect token needed for provenance - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: "20.x" - registry-url: "https://registry.npmjs.org" - cache: pnpm - - # Ensure everything is compiling - - run: "pnpm install" - - run: "pnpm build" - - run: "pnpm bootstrap" - - # Lets us use one-liner JSON manipulations the package.json files - - run: "npm install -g json" - - # Setup the environment - - run: 'json -I -f packages/svelte-check/package.json -e "this.version=\`${{ github.ref }}\`.split(\`-\`).pop()"' - - # Ship it - - run: | - cd packages/svelte-check - pnpm install - pnpm publish --provenance --no-git-checks - - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/DeploySvelteLanguageServerProd.yml b/.github/workflows/DeploySvelteLanguageServerProd.yml deleted file mode 100644 index 768448427..000000000 --- a/.github/workflows/DeploySvelteLanguageServerProd.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Tagged Production Deploys For svelte-language-server - -on: - push: - tags: - - "language-server-*" - -jobs: - deploy: - permissions: - id-token: write # OpenID Connect token needed for provenance - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: "20.x" - registry-url: "https://registry.npmjs.org" - cache: pnpm - - # Ensure everything is compiling - - run: "pnpm install" - - run: "pnpm build" - - # Lets us use one-liner JSON manipulations the package.json files - - run: "npm install -g json" - - # Setup the environment - - run: 'json -I -f packages/language-server/package.json -e "this.version=\`${{ github.ref }}\`.split(\`-\`).pop()"' - - # Ship it - - run: | - cd packages/language-server - pnpm install - pnpm publish --provenance --no-git-checks - - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/DeployTypescriptPluginProd.yaml b/.github/workflows/DeployTypescriptPluginProd.yaml deleted file mode 100644 index a93e95a29..000000000 --- a/.github/workflows/DeployTypescriptPluginProd.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: Tagged Production Deploys for typescript-svelte-plugin - -on: - push: - tags: - - "typescript-plugin-*" - -jobs: - deploy: - permissions: - id-token: write # OpenID Connect token needed for provenance - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: "20.x" - registry-url: "https://registry.npmjs.org" - cache: pnpm - - # Ensure everything is compiling - - run: "pnpm install" - - run: "pnpm build" - - # Lets us use one-liner JSON manipulations the package.json files - - run: "npm install -g json" - - # Setup the environment - - run: 'json -I -f packages/typescript-plugin/package.json -e "this.version=\`${{ github.ref }}\`.split(\`-\`).pop()"' - - # Ship it - - run: | - cd packages/typescript-plugin - pnpm install - pnpm publish --provenance --no-git-checks - - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..2c24dbabb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,44 @@ +name: Release + +on: + push: + branches: + - main + +permissions: {} + +jobs: + release: + # prevents this action from running on forks + if: github.repository == 'sveltejs/language-tools' + permissions: + contents: write # to create release (changesets/action) + id-token: write # OpenID Connect token needed for provenance + pull-requests: write # to create pull request (changesets/action) + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits + fetch-depth: 0 + - uses: pnpm/action-setup@v4.1.0 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24.x + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + # This expects you to have a script called release which does a build for your packages and calls changeset publish + publish: pnpm changeset:release + version: pnpm changeset:version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_CONFIG_PROVENANCE: true diff --git a/package.json b/package.json index e2eeff50c..cffee1d62 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,15 @@ "test": "cross-env CI=true pnpm test -r", "watch": "tsc -b -watch", "format": "prettier --write .", - "lint": "prettier --check ." + "lint": "prettier --check .", + "changeset:version": "changeset version && git add --all", + "changeset:publish": "changeset publish" }, "dependencies": { "typescript": "^5.9.2" }, "devDependencies": { + "@changesets/cli": "^2.29.7", "cross-env": "^7.0.2", "prettier": "~3.3.3", "ts-node": "^10.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdd9c6d01..7d98c2940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,9 @@ importers: specifier: ^5.9.2 version: 5.9.2 devDependencies: + '@changesets/cli': + specifier: ^2.29.7 + version: 2.29.7(@types/node@18.19.46) cross-env: specifier: ^7.0.2 version: 7.0.3 @@ -336,6 +339,65 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@7.0.13': + resolution: {integrity: sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg==} + + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/cli@2.29.7': + resolution: {integrity: sha512-R7RqWoaksyyKXbKXBTbT4REdy22yH81mcFK6sWtqSanxUCbUi9Uf+6aqxZtDQouIqPdem2W56CdxXgsxdq7FLQ==} + hasBin: true + + '@changesets/config@3.1.1': + resolution: {integrity: sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + + '@changesets/get-release-plan@4.0.13': + resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.1': + resolution: {integrity: sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.5': + resolution: {integrity: sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -505,6 +567,15 @@ packages: cpu: [x64] os: [win32] + '@inquirer/external-editor@1.0.1': + resolution: {integrity: sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -529,6 +600,12 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -768,6 +845,9 @@ packages: '@types/mri@1.1.1': resolution: {integrity: sha512-nJOuiTlsvmClSr3+a/trTSx4DTuY/VURsWGKSf/eeavh0LRMqdsK60ti0TlwM5iHiGOK3/Ibkxsbr7i9rzGreA==} + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@18.19.46': resolution: {integrity: sha512-vnRgMS7W6cKa1/0G3/DTtQYpVrZ8c0Xm6UkLaVFrb9jtcVC3okokW09Ki1Qdrj9ISokszD69nY4WDLRlvHlhAA==} @@ -856,6 +936,10 @@ packages: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} engines: {node: '>=6'} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -899,6 +983,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -913,6 +1001,10 @@ packages: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} @@ -943,6 +1035,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chardet@2.1.0: + resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -955,6 +1050,10 @@ packages: resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} engines: {node: '>= 14.16.0'} + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -1002,6 +1101,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + css-tree@2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -1047,6 +1150,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -1069,6 +1176,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -1107,6 +1218,9 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} @@ -1134,6 +1248,14 @@ packages: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1142,6 +1264,10 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} @@ -1186,6 +1312,10 @@ packages: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} engines: {node: '>=8'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -1212,6 +1342,14 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + human-id@4.1.1: + resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} + hasBin: true + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -1278,10 +1416,18 @@ packages: is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + isarray@0.0.1: resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} @@ -1315,6 +1461,10 @@ packages: locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1322,6 +1472,9 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1366,6 +1519,10 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1415,18 +1572,44 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + p-map@3.0.0: resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} engines: {node: '>=8'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} @@ -1483,6 +1666,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -1493,17 +1680,29 @@ packages: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + prettier@3.3.3: resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} engines: {node: '>=14'} hasBin: true + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -1516,6 +1715,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve@1.22.2: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} hasBin: true @@ -1565,6 +1768,9 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.5.1: resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} engines: {node: '>=10'} @@ -1589,6 +1795,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + sinon@11.1.2: resolution: {integrity: sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==} @@ -1619,6 +1829,9 @@ packages: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -1636,6 +1849,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1663,6 +1880,10 @@ packages: resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} engines: {node: '>=16'} + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} @@ -1924,6 +2145,152 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@babel/runtime@7.28.4': {} + + '@changesets/apply-release-plan@7.0.13': + dependencies: + '@changesets/config': 3.1.1 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.2 + + '@changesets/assemble-release-plan@6.0.9': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.2 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.29.7(@types/node@18.19.46)': + dependencies: + '@changesets/apply-release-plan': 7.0.13 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.1 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.13 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.1(@types/node@18.19.46) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.7.2 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.1': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.3': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.7.2 + + '@changesets/get-release-plan@4.0.13': + dependencies: + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.1': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 3.14.1 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.5': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.1 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.1.1 + prettier: 2.8.8 + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -2016,6 +2383,13 @@ snapshots: '@esbuild/win32-x64@0.25.6': optional: true + '@inquirer/external-editor@1.0.1(@types/node@18.19.46)': + dependencies: + chardet: 2.1.0 + iconv-lite: 0.6.3 + optionalDependencies: + '@types/node': 18.19.46 + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -2040,6 +2414,22 @@ snapshots: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.5.0 + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.28.4 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.28.4 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2231,6 +2621,8 @@ snapshots: '@types/mri@1.1.1': {} + '@types/node@12.20.55': {} + '@types/node@18.19.46': dependencies: undici-types: 5.26.5 @@ -2327,6 +2719,8 @@ snapshots: ansi-colors@4.1.1: {} + ansi-colors@4.1.3: {} + ansi-regex@5.0.1: {} ansi-styles@3.2.1: @@ -2362,6 +2756,10 @@ snapshots: balanced-match@1.0.2: {} + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + binary-extensions@2.2.0: {} brace-expansion@1.1.11: @@ -2377,6 +2775,10 @@ snapshots: dependencies: fill-range: 7.0.1 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + browser-stdout@1.3.1: {} buffer-from@1.1.2: {} @@ -2406,6 +2808,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chardet@2.1.0: {} + check-error@2.1.1: {} chokidar@3.5.3: @@ -2424,6 +2828,8 @@ snapshots: dependencies: readdirp: 4.0.1 + ci-info@3.9.0: {} + clean-stack@2.2.0: {} cliui@7.0.4: @@ -2472,6 +2878,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css-tree@2.3.1: dependencies: mdn-data: 2.0.30 @@ -2508,6 +2920,8 @@ snapshots: dequal@2.0.3: {} + detect-indent@6.1.0: {} + diff@4.0.2: {} diff@5.0.0: {} @@ -2525,6 +2939,11 @@ snapshots: emoji-regex@8.0.0: {} + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + es-module-lexer@1.7.0: {} esbuild@0.25.6: @@ -2574,6 +2993,8 @@ snapshots: expect-type@1.2.2: {} + extendable-error@0.1.7: {} + fast-glob@3.2.12: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2598,6 +3019,15 @@ snapshots: dependencies: to-regex-range: 5.0.1 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2605,6 +3035,12 @@ snapshots: flat@5.0.2: {} + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + fs-extra@8.1.0: dependencies: graceful-fs: 4.2.11 @@ -2674,6 +3110,15 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + globrex@0.1.2: {} graceful-fs@4.2.11: {} @@ -2690,6 +3135,12 @@ snapshots: he@1.2.0: {} + human-id@4.1.1: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.2.4: {} indent-string@4.0.0: {} @@ -2741,8 +3192,14 @@ snapshots: dependencies: '@types/estree': 0.0.42 + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + is-unicode-supported@0.1.0: {} + is-windows@1.0.2: {} + isarray@0.0.1: {} isexe@2.0.0: {} @@ -2774,12 +3231,18 @@ snapshots: locate-character@3.0.0: {} + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash.get@4.4.2: {} + lodash.startcase@4.4.0: {} + lodash@4.17.21: {} log-symbols@4.1.0: @@ -2824,6 +3287,11 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -2892,18 +3360,40 @@ snapshots: dependencies: wrappy: 1.0.2 + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-map@2.1.0: {} + p-map@3.0.0: dependencies: aggregate-error: 3.1.0 + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + pascal-case@3.1.2: dependencies: no-case: 3.0.4 @@ -2948,6 +3438,8 @@ snapshots: picomatch@4.0.3: {} + pify@4.0.1: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -2959,14 +3451,25 @@ snapshots: prettier: 3.3.3 svelte: 4.2.19 + prettier@2.8.8: {} + prettier@3.3.3: {} + quansync@0.2.11: {} + queue-microtask@1.2.3: {} randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -2975,6 +3478,8 @@ snapshots: require-directory@2.1.1: {} + resolve-from@5.0.0: {} + resolve@1.22.2: dependencies: is-core-module: 2.12.1 @@ -3049,6 +3554,8 @@ snapshots: safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} + semver@7.5.1: dependencies: lru-cache: 6.0.0 @@ -3067,6 +3574,8 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + sinon@11.1.2: dependencies: '@sinonjs/commons': 1.8.6 @@ -3093,6 +3602,11 @@ snapshots: sourcemap-codec@1.4.8: {} + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + sprintf-js@1.0.3: {} stackback@0.0.2: {} @@ -3109,6 +3623,8 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-bom@3.0.0: {} + strip-json-comments@3.1.1: {} strip-literal@3.0.0: @@ -3146,6 +3662,8 @@ snapshots: magic-string: 0.30.11 periscopic: 3.1.0 + term-size@2.2.1: {} + tiny-glob@0.2.9: dependencies: globalyzer: 0.1.0 From 0c059aa347df8ca64ab5f3f81721ecbf60f020f6 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 11 Sep 2025 15:09:40 +0200 Subject: [PATCH 11/39] chore: bump package versions to latest so changesets knows where to start from. also removes vscode from the list of packages to create changesets for --- .changeset/config.json | 2 +- packages/language-server/package.json | 2 +- packages/svelte-check/package.json | 2 +- packages/svelte2tsx/package.json | 2 +- packages/typescript-plugin/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 9d62a2143..ae36fb5a8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,5 +11,5 @@ "baseBranch": "master", "updateInternalDependencies": "minor", "bumpVersionsWithWorkspaceProtocolOnly": true, - "ignore": [] + "ignore": ["svelte-vscode"] } diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 668cdc65b..6cde35762 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,6 +1,6 @@ { "name": "svelte-language-server", - "version": "0.17.0", + "version": "0.17.19", "description": "A language server for Svelte", "main": "dist/src/index.js", "typings": "dist/src/index", diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 14690e918..a8dfb69e6 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -1,7 +1,7 @@ { "name": "svelte-check", "description": "Svelte Code Checker Terminal Interface", - "version": "4.0.0", + "version": "4.3.1", "main": "./dist/src/index.js", "bin": "./bin/svelte-check", "author": "The Svelte Community", diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 3f9268a83..7768fd9c6 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -1,6 +1,6 @@ { "name": "svelte2tsx", - "version": "0.7.35", + "version": "0.7.42", "description": "Convert Svelte components to TSX for type checking", "author": "The Svelte Community", "license": "MIT", diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index fe6d24c04..1091e3f24 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -1,6 +1,6 @@ { "name": "typescript-svelte-plugin", - "version": "0.3.0", + "version": "0.3.50", "description": "A TypeScript Plugin providing Svelte intellisense", "main": "dist/src/index.js", "scripts": { From 225c43c4679d7d98a517019b006822539fb1fa4d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:20:59 +0200 Subject: [PATCH 12/39] fix: respect moduleResolution setting in emitDts (#2845) see #2837 for more info --------- Co-authored-by: Mattias Granlund --- .changeset/witty-kiwis-divide.md | 5 +++++ .gitignore | 2 +- packages/svelte2tsx/.gitignore | 3 ++- packages/svelte2tsx/src/emitDts.ts | 7 +++++-- .../expected/consumer.d.ts | 13 ++++++++++++ .../expected/index.d.ts | 1 + .../test-package/not-at-root/index.d.ts | 4 ++++ .../node_modules/test-package/package.json | 10 +++++++++ .../cross-package-generic-types/package.json | 8 +++++++ .../src/consumer.ts | 21 +++++++++++++++++++ .../cross-package-generic-types/src/index.ts | 2 ++ .../cross-package-generic-types/tsconfig.json | 14 +++++++++++++ 12 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 .changeset/witty-kiwis-divide.md create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/consumer.d.ts create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/index.d.ts create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/not-at-root/index.d.ts create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/package.json create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/package.json create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/consumer.ts create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/index.ts create mode 100644 packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/tsconfig.json diff --git a/.changeset/witty-kiwis-divide.md b/.changeset/witty-kiwis-divide.md new file mode 100644 index 000000000..dbdf36c19 --- /dev/null +++ b/.changeset/witty-kiwis-divide.md @@ -0,0 +1,5 @@ +--- +'svelte2tsx': patch +--- + +fix: respect moduleResolution setting in emitDts diff --git a/.gitignore b/.gitignore index dce9dc14c..c9b203571 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,7 @@ bower_components build/Release # Dependency directories -node_modules/ +/node_modules/ jspm_packages/ # TypeScript v1 declaration files diff --git a/packages/svelte2tsx/.gitignore b/packages/svelte2tsx/.gitignore index 0603455f4..2aff21825 100644 --- a/packages/svelte2tsx/.gitignore +++ b/packages/svelte2tsx/.gitignore @@ -1,5 +1,6 @@ .DS_Store -node_modules +/node_modules +/test/emitDts/samples/cross-package-generic-types/node_modules/.svelte2tsx-language-server-files /index.js /index.js.map /index.mjs diff --git a/packages/svelte2tsx/src/emitDts.ts b/packages/svelte2tsx/src/emitDts.ts index fb312a36d..9c41a4e60 100644 --- a/packages/svelte2tsx/src/emitDts.ts +++ b/packages/svelte2tsx/src/emitDts.ts @@ -112,8 +112,11 @@ function loadTsconfig(config: EmitDtsConfig, svelteMap: SvelteMap) { ...options, noEmit: false, // Set to true in case of jsconfig, force false, else nothing is emitted moduleResolution: - // NodeJS: up to 4.9, Node10: since 5.0 - (ts.ModuleResolutionKind as any).NodeJs ?? ts.ModuleResolutionKind.Node10, // Classic if not set, which gives wrong results + options.moduleResolution && + options.moduleResolution !== ts.ModuleResolutionKind.Classic + ? options.moduleResolution + : // NodeJS: up to 4.9, Node10: since 5.0 + ((ts.ModuleResolutionKind as any).NodeJs ?? ts.ModuleResolutionKind.Node10), // Classic if not set, which gives wrong results declaration: true, // Needed for d.ts file generation emitDeclarationOnly: true, // We only want d.ts file generation declarationDir: config.declarationDir, // Where to put the declarations diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/consumer.d.ts b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/consumer.d.ts new file mode 100644 index 000000000..1645e48b6 --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/consumer.d.ts @@ -0,0 +1,13 @@ +import { GenericToken } from 'test-package'; +export declare class MyService { + private name; + constructor(name: string); + getName(): string; +} +export declare const SERVICE_TOKEN: GenericToken; +export declare const ANNOTATED_TOKEN: GenericToken; +export declare const ASSERTION_TOKEN: GenericToken; +export declare class AnotherService { + value: number; +} +export declare const ANOTHER_TOKEN: GenericToken; diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/index.d.ts b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/index.d.ts new file mode 100644 index 000000000..7baccd565 --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/expected/index.d.ts @@ -0,0 +1 @@ +export * from './consumer.js'; diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/not-at-root/index.d.ts b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/not-at-root/index.d.ts new file mode 100644 index 000000000..5e1c39b8e --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/not-at-root/index.d.ts @@ -0,0 +1,4 @@ +export class GenericToken { + constructor(name: string); + t: T +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/package.json b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/package.json new file mode 100644 index 000000000..6889da7bc --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/node_modules/test-package/package.json @@ -0,0 +1,10 @@ +{ + "name": "test-package", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "types": "./not-at-root/index.d.ts" + } + } +} diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/package.json b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/package.json new file mode 100644 index 000000000..057c14fd3 --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-cross-package-generics", + "version": "0.0.1", + "type": "module", + "dependencies": { + "test-package": "1.0.0" + } +} diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/consumer.ts b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/consumer.ts new file mode 100644 index 000000000..8422175e5 --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/consumer.ts @@ -0,0 +1,21 @@ +import { GenericToken } from 'test-package'; + +export class MyService { + constructor(private name: string) {} + getName() { return this.name; } +} + +// This should preserve the generic type as GenericToken +// Before the fix, this would be compiled as 'any' due to module resolution issues +export const SERVICE_TOKEN = new GenericToken('MyService'); + +// These explicit annotations should work regardless +export const ANNOTATED_TOKEN: GenericToken = new GenericToken('MyService'); +export const ASSERTION_TOKEN = new GenericToken('MyService') as GenericToken; + +// Test with different generic types +export class AnotherService { + value = 42; +} + +export const ANOTHER_TOKEN = new GenericToken('AnotherService'); diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/index.ts b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/index.ts new file mode 100644 index 000000000..420f99b48 --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/src/index.ts @@ -0,0 +1,2 @@ +export * from './consumer.js'; + diff --git a/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/tsconfig.json b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/tsconfig.json new file mode 100644 index 000000000..e6d389435 --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/cross-package-generic-types/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ESNext", + "moduleResolution": "bundler", + "declaration": true, + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"] +} From 95b81bb54624f2416c2ef7745a19a60333a0065c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 11 Sep 2025 15:44:37 +0200 Subject: [PATCH 13/39] chore: fix branch name trigger --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c24dbabb..082a9aa67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: branches: - - main + - master permissions: {} From c73e4f5019ac2bd442d0c3d9618fa80032c3ccab Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 11 Sep 2025 15:48:21 +0200 Subject: [PATCH 14/39] chore: add missing changeset formatting package --- package.json | 1 + pnpm-lock.yaml | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/package.json b/package.json index cffee1d62..b4a56d9a6 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@changesets/cli": "^2.29.7", + "@svitejs/changesets-changelog-github-compact": "^1.1.0", "cross-env": "^7.0.2", "prettier": "~3.3.3", "ts-node": "^10.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d98c2940..e2ad95700 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ importers: '@changesets/cli': specifier: ^2.29.7 version: 2.29.7(@types/node@18.19.46) + '@svitejs/changesets-changelog-github-compact': + specifier: ^1.1.0 + version: 1.2.0 cross-env: specifier: ^7.0.2 version: 7.0.3 @@ -365,6 +368,9 @@ packages: '@changesets/get-dependents-graph@2.1.3': resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + '@changesets/get-github-info@0.6.0': + resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} + '@changesets/get-release-plan@4.0.13': resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} @@ -797,6 +803,10 @@ packages: '@sinonjs/text-encoding@0.7.2': resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==} + '@svitejs/changesets-changelog-github-compact@1.2.0': + resolution: {integrity: sha512-08eKiDAjj4zLug1taXSIJ0kGL5cawjVCyJkBb6EWSg5fEPX6L+Wtr0CH2If4j5KYylz85iaZiFlUItvgJvll5g==} + engines: {node: ^14.13.1 || ^16.0.0 || >=18} + '@tsconfig/node10@1.0.9': resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -1109,6 +1119,9 @@ packages: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dataloader@1.4.0: + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + debug@4.3.3: resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} engines: {node: '>=6.0'} @@ -1170,6 +1183,10 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + emmet@2.4.4: resolution: {integrity: sha512-v8Mwpjym55CS3EjJgiCLWUB3J2HSR93jhzXW325720u8KvYxdI2voYLstW3pHBxFz54H6jFjayR9G4LfTG0q+g==} @@ -1565,6 +1582,15 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1913,6 +1939,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-node@10.9.1: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -2091,6 +2120,12 @@ packages: vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2230,6 +2265,13 @@ snapshots: picocolors: 1.1.1 semver: 7.7.2 + '@changesets/get-github-info@0.6.0': + dependencies: + dataloader: 1.4.0 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + '@changesets/get-release-plan@4.0.13': dependencies: '@changesets/assemble-release-plan': 6.0.9 @@ -2582,6 +2624,13 @@ snapshots: '@sinonjs/text-encoding@0.7.2': {} + '@svitejs/changesets-changelog-github-compact@1.2.0': + dependencies: + '@changesets/get-github-info': 0.6.0 + dotenv: 16.6.1 + transitivePeerDependencies: + - encoding + '@tsconfig/node10@1.0.9': {} '@tsconfig/node12@1.0.11': {} @@ -2889,6 +2938,8 @@ snapshots: mdn-data: 2.0.30 source-map-js: 1.2.0 + dataloader@1.4.0: {} + debug@4.3.3(supports-color@8.1.1): dependencies: ms: 2.1.2 @@ -2932,6 +2983,8 @@ snapshots: dependencies: path-type: 4.0.0 + dotenv@16.6.1: {} + emmet@2.4.4: dependencies: '@emmetio/abbreviation': 2.3.3 @@ -3354,6 +3407,10 @@ snapshots: lower-case: 2.0.2 tslib: 2.5.2 + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + normalize-path@3.0.0: {} once@1.4.0: @@ -3688,6 +3745,8 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + ts-node@10.9.1(@types/node@18.19.46)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -3872,6 +3931,13 @@ snapshots: vscode-uri@3.1.0: {} + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which@2.0.2: dependencies: isexe: 2.0.0 From b9bf2577133568674188209ea559c1ad92084f45 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:01:14 +0200 Subject: [PATCH 15/39] Version Packages (#2846) * Version Packages * Update packages/svelte2tsx/CHANGELOG.md --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/witty-kiwis-divide.md | 5 ----- packages/svelte2tsx/CHANGELOG.md | 8 +++++++- packages/svelte2tsx/package.json | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/witty-kiwis-divide.md diff --git a/.changeset/witty-kiwis-divide.md b/.changeset/witty-kiwis-divide.md deleted file mode 100644 index dbdf36c19..000000000 --- a/.changeset/witty-kiwis-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte2tsx': patch ---- - -fix: respect moduleResolution setting in emitDts diff --git a/packages/svelte2tsx/CHANGELOG.md b/packages/svelte2tsx/CHANGELOG.md index c8c4cda62..6e1df3059 100644 --- a/packages/svelte2tsx/CHANGELOG.md +++ b/packages/svelte2tsx/CHANGELOG.md @@ -1,3 +1,9 @@ # Changelog -See https://github.com/sveltejs/language-tools/releases +## 0.7.43 + +### Patch Changes + +- fix: respect moduleResolution setting in emitDts ([#2845](https://github.com/sveltejs/language-tools/pull/2845)) + +See https://github.com/sveltejs/language-tools/releases for older release notes diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 7768fd9c6..0bd55bea3 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -1,6 +1,6 @@ { "name": "svelte2tsx", - "version": "0.7.42", + "version": "0.7.43", "description": "Convert Svelte components to TSX for type checking", "author": "The Svelte Community", "license": "MIT", From 8269ff7aaf94442592800a02ed3ad975b2b58336 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 11 Sep 2025 17:17:30 +0200 Subject: [PATCH 16/39] chore: fix release script --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 082a9aa67..824160f6e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,9 +36,8 @@ jobs: id: changesets uses: changesets/action@v1 with: - # This expects you to have a script called release which does a build for your packages and calls changeset publish - publish: pnpm changeset:release version: pnpm changeset:version + publish: pnpm changeset:publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_CONFIG_PROVENANCE: true From 0d8e5ae1b17e920e212e5b3d99f38d824915da8e Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 11 Sep 2025 17:21:25 +0200 Subject: [PATCH 17/39] chore: set vscode package to private because it's not a real npm pkg --- packages/svelte-vscode/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index 74d761fbb..c0deb928c 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -1,6 +1,7 @@ { "name": "svelte-vscode", "version": "0.5.0", + "private": true, "description": "Svelte language support for VS Code", "main": "dist/src/extension.js", "scripts": { From f799839a5a5dfc5dcffdc42fb34b5b10b4345be5 Mon Sep 17 00:00:00 2001 From: "jyc.dev" Date: Thu, 11 Sep 2025 20:31:57 +0200 Subject: [PATCH 18/39] (deps) Replace `pascal-case` with `scule` (Maintained & Lighter) (#2842) * (deps) Replace pascal-case with change-case (Maintained & Lighter) * add changeset --- .changeset/fuzzy-points-stick.md | 5 +++ packages/svelte2tsx/package.json | 2 +- packages/svelte2tsx/rollup.config.mjs | 2 +- .../src/svelte2tsx/addComponentExport.ts | 4 +-- pnpm-lock.yaml | 34 +++++-------------- 5 files changed, 17 insertions(+), 30 deletions(-) create mode 100644 .changeset/fuzzy-points-stick.md diff --git a/.changeset/fuzzy-points-stick.md b/.changeset/fuzzy-points-stick.md new file mode 100644 index 000000000..dc3bcc98c --- /dev/null +++ b/.changeset/fuzzy-points-stick.md @@ -0,0 +1,5 @@ +--- +'svelte2tsx': patch +--- + +chore(deps): Replace `pascal-case` with `scule` diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 0bd55bea3..f214ffe38 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -66,6 +66,6 @@ ], "dependencies": { "dedent-js": "^1.0.1", - "pascal-case": "^3.1.1" + "scule": "^1.3.0" } } diff --git a/packages/svelte2tsx/rollup.config.mjs b/packages/svelte2tsx/rollup.config.mjs index ccd67376d..b0908cbdd 100644 --- a/packages/svelte2tsx/rollup.config.mjs +++ b/packages/svelte2tsx/rollup.config.mjs @@ -107,7 +107,7 @@ export default [ 'svelte', 'svelte/compiler', 'dedent-js', - 'pascal-case' + 'scule' ] } ]; diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts index e273af035..4c56a1a17 100644 --- a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts +++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts @@ -1,4 +1,4 @@ -import { pascalCase } from 'pascal-case'; +import { pascalCase } from 'scule'; import path from 'path'; import MagicString from 'magic-string'; import { ExportedNames } from './nodes/ExportedNames'; @@ -380,7 +380,7 @@ function classNameFromFilename(filename: string, appendSuffix: boolean): string const withoutInvalidCharacters = withoutExtensions .split('') // Although "-" is invalid, we leave it in, pascal-case-handling will throw it out later - .filter((char) => /[A-Za-z$_\d-]/.test(char)) + .filter((char) => /[A-Za-z_\d-]/.test(char)) .join(''); const firstValidCharIdx = withoutInvalidCharacters .split('') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2ad95700..c4534fd55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,9 +243,9 @@ importers: dedent-js: specifier: ^1.0.1 version: 1.0.1 - pascal-case: - specifier: ^3.1.1 - version: 3.1.2 + scule: + specifier: ^1.3.0 + version: 1.3.0 devDependencies: '@jridgewell/sourcemap-codec': specifier: ^1.5.0 @@ -1502,9 +1502,6 @@ packages: loupe@3.1.4: resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1579,9 +1576,6 @@ packages: nise@5.1.4: resolution: {integrity: sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==} - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -1636,9 +1630,6 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1797,6 +1788,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + semver@7.5.1: resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} engines: {node: '>=10'} @@ -3305,10 +3299,6 @@ snapshots: loupe@3.1.4: {} - lower-case@2.0.2: - dependencies: - tslib: 2.5.2 - lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -3402,11 +3392,6 @@ snapshots: just-extend: 4.2.1 path-to-regexp: 1.8.0 - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.5.2 - node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -3451,11 +3436,6 @@ snapshots: dependencies: quansync: 0.2.11 - pascal-case@3.1.2: - dependencies: - no-case: 3.0.4 - tslib: 2.5.2 - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -3613,6 +3593,8 @@ snapshots: safer-buffer@2.1.2: {} + scule@1.3.0: {} + semver@7.5.1: dependencies: lru-cache: 6.0.0 From dec37eabe44370615d98af2d19ae6ed7feafc297 Mon Sep 17 00:00:00 2001 From: axel7083 <42176370+axel7083@users.noreply.github.com> Date: Tue, 16 Sep 2025 00:25:36 +0200 Subject: [PATCH 19/39] fix(svelte2tsx): support for runes={false} in svelte:options (#2847) Description The were not checking for the value, so it was always true. When using the props exported were Record which is incorrect. Related issues Fixes #2843 --------- Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/green-news-find.md | 6 +++++ .../svelte2tsx/src/htmlxtojsx_v2/index.ts | 8 +++++- .../expected-svelte5.ts | 26 +++++++++++++++++++ .../ts-export-list-runes-false/expectedv2.ts | 26 +++++++++++++++++++ .../ts-export-list-runes-false/input.svelte | 19 ++++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .changeset/green-news-find.md create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expected-svelte5.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expectedv2.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/input.svelte diff --git a/.changeset/green-news-find.md b/.changeset/green-news-find.md new file mode 100644 index 000000000..44d75ae3a --- /dev/null +++ b/.changeset/green-news-find.md @@ -0,0 +1,6 @@ +--- +'svelte2tsx': patch +'svelte-check': patch +--- + +fix: properly handle `runes={false}` in `` diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts index d6bb965ea..960b9dfd3 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts @@ -123,7 +123,13 @@ export function convertHtmlxToJsx( } break; case 'runes': - isRunes = true; + if (Array.isArray(optionValue)) { + if (optionValue[0].type === 'MustacheTag') { + isRunes = optionValue[0].expression.value; + } + } else { + isRunes = true; + } break; } } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expected-svelte5.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expected-svelte5.ts new file mode 100644 index 000000000..0bebd57d0 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expected-svelte5.ts @@ -0,0 +1,26 @@ +/// +;function $$render() { + + let name1: string = "world"/*Ωignore_startΩ*/;name1 = __sveltets_2_any(name1);/*Ωignore_endΩ*/ + let name2: string/*Ωignore_startΩ*/;name2 = __sveltets_2_any(name2);/*Ωignore_endΩ*/; + let name3: string = ''/*Ωignore_startΩ*/;name3 = __sveltets_2_any(name3);/*Ωignore_endΩ*/;let name4: string/*Ωignore_startΩ*/;name4 = __sveltets_2_any(name4);/*Ωignore_endΩ*/; + + let rename1: string = ''/*Ωignore_startΩ*/;rename1 = __sveltets_2_any(rename1);/*Ωignore_endΩ*/; + let rename2: string/*Ωignore_startΩ*/;rename2 = __sveltets_2_any(rename2);/*Ωignore_endΩ*/; + + class Foo {} + function bar() {} + const baz: string = ''; + + class RenameFoo {} + function renamebar() {} + const renamebaz: string = ''; + + +; +async () => { { svelteHTML.createElement("svelte:options", { "runes":false,});} +}; +return { props: {name1: name1 , name2: name2 , name3: name3 , name4: name4 , renamed1: rename1 , renamed2: rename2 , Foo: Foo , bar: bar , baz: baz , RenamedFoo: RenameFoo , renamedbar: renamebar , renamedbaz: renamebaz} as {name1?: string, name2: string, name3?: string, name4: string, renamed1?: string, renamed2: string, Foo?: typeof Foo, bar?: typeof bar, baz?: string, RenamedFoo?: typeof RenameFoo, renamedbar?: typeof renamebar, renamedbaz?: string}, exports: {} as any as { Foo: typeof Foo,bar: typeof bar,baz: string,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: string }, bindings: "", slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event($$render())); +/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; +/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expectedv2.ts new file mode 100644 index 000000000..d86af2d5f --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/expectedv2.ts @@ -0,0 +1,26 @@ +/// +;function $$render() { + + let name1: string = "world"/*Ωignore_startΩ*/;name1 = __sveltets_2_any(name1);/*Ωignore_endΩ*/ + let name2: string/*Ωignore_startΩ*/;name2 = __sveltets_2_any(name2);/*Ωignore_endΩ*/; + let name3: string = ''/*Ωignore_startΩ*/;name3 = __sveltets_2_any(name3);/*Ωignore_endΩ*/;let name4: string/*Ωignore_startΩ*/;name4 = __sveltets_2_any(name4);/*Ωignore_endΩ*/; + + let rename1: string = ''/*Ωignore_startΩ*/;rename1 = __sveltets_2_any(rename1);/*Ωignore_endΩ*/; + let rename2: string/*Ωignore_startΩ*/;rename2 = __sveltets_2_any(rename2);/*Ωignore_endΩ*/; + + class Foo {} + function bar() {} + const baz: string = ''; + + class RenameFoo {} + function renamebar() {} + const renamebaz: string = ''; + + +; +async () => { { svelteHTML.createElement("svelte:options", { "runes":false,});} +}; +return { props: {name1: name1 , name2: name2 , name3: name3 , name4: name4 , renamed1: rename1 , renamed2: rename2 , Foo: Foo , bar: bar , baz: baz , RenamedFoo: RenameFoo , renamedbar: renamebar , renamedbaz: renamebaz} as {name1?: string, name2: string, name3?: string, name4: string, renamed1?: string, renamed2: string, Foo?: typeof Foo, bar?: typeof bar, baz?: string, RenamedFoo?: typeof RenameFoo, renamedbar?: typeof renamebar, renamedbaz?: string}, slots: {}, events: {} }} + +export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event($$render())) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/input.svelte new file mode 100644 index 000000000..0b805718d --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes-false/input.svelte @@ -0,0 +1,19 @@ + + From ec7be4bf74e85e36cbcf269aba5bf78a266fdd87 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:25:26 +0800 Subject: [PATCH 20/39] perf: use failed path to invalidate module cache (#2853) * perf: use failed path to invalidate module cache * Don't emit both document events in open This makes the test do extra work after everything is done and creates weird race conditions, which I have already tried to fix many times * changeset * remove unnecessary Array.from --- .changeset/dark-falcons-pump.md | 6 ++ packages/language-server/package.json | 2 +- .../src/lib/documents/DocumentManager.ts | 3 +- .../src/plugins/css/CSSPlugin.ts | 8 ++- .../src/plugins/html/HTMLPlugin.ts | 6 +- .../src/plugins/typescript/module-loader.ts | 72 +++++++++++-------- .../src/plugins/typescript/service.ts | 12 ++-- .../lib/documents/DocumentManager.test.ts | 13 ---- .../features/DiagnosticsProvider.test.ts | 22 ++++++ .../diagnostics/unresolvedimport2.svelte | 4 ++ 10 files changed, 94 insertions(+), 54 deletions(-) create mode 100644 .changeset/dark-falcons-pump.md create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/unresolvedimport2.svelte diff --git a/.changeset/dark-falcons-pump.md b/.changeset/dark-falcons-pump.md new file mode 100644 index 000000000..6350244c8 --- /dev/null +++ b/.changeset/dark-falcons-pump.md @@ -0,0 +1,6 @@ +--- +'svelte-language-server': patch +'svelte-check': patch +--- + +perf: more precise module cache invalidation diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 6cde35762..9574450ec 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -10,7 +10,7 @@ "./bin/server.js": "./bin/server.js" }, "scripts": { - "test": "cross-env TS_NODE_TRANSPILE_ONLY=true mocha --require ts-node/register \"test/**/*.test.ts\"", + "test": "cross-env TS_NODE_TRANSPILE_ONLY=true mocha --require ts-node/register \"test/**/*.test.ts\" --no-experimental-strip-types", "build": "tsc", "prepublishOnly": "npm run build", "watch": "tsc -w" diff --git a/packages/language-server/src/lib/documents/DocumentManager.ts b/packages/language-server/src/lib/documents/DocumentManager.ts index 1c2df541b..28eda09ce 100644 --- a/packages/language-server/src/lib/documents/DocumentManager.ts +++ b/packages/language-server/src/lib/documents/DocumentManager.ts @@ -50,6 +50,7 @@ export class DocumentManager { // open state should only be updated when the document is closed document.openedByClient ||= openedByClient; document.setText(textDocument.text); + this.notify('documentChange', document); } else { document = this.createDocument(textDocument); document.openedByClient = openedByClient; @@ -57,8 +58,6 @@ export class DocumentManager { this.notify('documentOpen', document); } - this.notify('documentChange', document); - return document; } diff --git a/packages/language-server/src/plugins/css/CSSPlugin.ts b/packages/language-server/src/plugins/css/CSSPlugin.ts index f89a6285e..edcaae746 100644 --- a/packages/language-server/src/plugins/css/CSSPlugin.ts +++ b/packages/language-server/src/plugins/css/CSSPlugin.ts @@ -99,9 +99,11 @@ export class CSSPlugin this.updateConfigs(); }); - docManager.on('documentChange', (document) => - this.cssDocuments.set(document, new CSSDocument(document, this.cssLanguageServices)) - ); + const sync = (document: Document) => { + this.cssDocuments.set(document, new CSSDocument(document, this.cssLanguageServices)); + }; + docManager.on('documentChange', sync); + docManager.on('documentOpen', sync); docManager.on('documentClose', (document) => this.cssDocuments.delete(document)); } diff --git a/packages/language-server/src/plugins/html/HTMLPlugin.ts b/packages/language-server/src/plugins/html/HTMLPlugin.ts index 2e900f626..f94cbe483 100644 --- a/packages/language-server/src/plugins/html/HTMLPlugin.ts +++ b/packages/language-server/src/plugins/html/HTMLPlugin.ts @@ -76,9 +76,11 @@ export class HTMLPlugin configManager.onChange(() => this.lang.setDataProviders(false, this.getCustomDataProviders()) ); - docManager.on('documentChange', (document) => { + const sync = (document: Document) => { this.documents.set(document, document.html); - }); + }; + docManager.on('documentChange', sync); + docManager.on('documentOpen', sync); } doHover(document: Document, position: Position): Hover | null { diff --git a/packages/language-server/src/plugins/typescript/module-loader.ts b/packages/language-server/src/plugins/typescript/module-loader.ts index a681c658a..8c293643c 100644 --- a/packages/language-server/src/plugins/typescript/module-loader.ts +++ b/packages/language-server/src/plugins/typescript/module-loader.ts @@ -7,7 +7,8 @@ import { ensureRealSvelteFilePath, getExtensionFromScriptKind, isSvelteFilePath, - isVirtualSvelteFilePath + isVirtualSvelteFilePath, + toVirtualSvelteFilePath } from './utils'; const CACHE_KEY_SEPARATOR = ':::'; @@ -15,7 +16,7 @@ const CACHE_KEY_SEPARATOR = ':::'; * Caches resolved modules. */ class ModuleResolutionCache { - private cache = new FileMap(); + private cache = new FileMap(); private pendingInvalidations = new FileSet(); private getCanonicalFileName = createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames); @@ -23,7 +24,10 @@ class ModuleResolutionCache { * Tries to get a cached module. * Careful: `undefined` can mean either there's no match found, or that the result resolved to `undefined`. */ - get(moduleName: string, containingFile: string): ts.ResolvedModule | undefined { + get( + moduleName: string, + containingFile: string + ): ts.ResolvedModuleWithFailedLookupLocations | undefined { return this.cache.get(this.getKey(moduleName, containingFile)); } @@ -37,7 +41,11 @@ class ModuleResolutionCache { /** * Caches resolved module (or undefined). */ - set(moduleName: string, containingFile: string, resolvedModule: ts.ResolvedModule | undefined) { + set( + moduleName: string, + containingFile: string, + resolvedModule: ts.ResolvedModuleWithFailedLookupLocations + ) { this.cache.set(this.getKey(moduleName, containingFile), resolvedModule); } @@ -48,7 +56,11 @@ class ModuleResolutionCache { delete(resolvedModuleName: string): void { resolvedModuleName = this.getCanonicalFileName(resolvedModuleName); this.cache.forEach((val, key) => { - if (val && this.getCanonicalFileName(val.resolvedFileName) === resolvedModuleName) { + if ( + val.resolvedModule && + this.getCanonicalFileName(val.resolvedModule.resolvedFileName) === + resolvedModuleName + ) { this.cache.delete(key); this.pendingInvalidations.add(key.split(CACHE_KEY_SEPARATOR).shift() || ''); } @@ -59,17 +71,10 @@ class ModuleResolutionCache { * Deletes everything from cache that resolved to `undefined` * and which might match the path. */ - deleteUnresolvedResolutionsFromCache(path: string): void { - const fileNameWithoutEnding = - getLastPartOfPath(this.getCanonicalFileName(path)).split('.').shift() || ''; + deleteByValues(list: ts.ResolvedModuleWithFailedLookupLocations[]): void { this.cache.forEach((val, key) => { - if (val) { - return; - } - const [containingFile, moduleName = ''] = key.split(CACHE_KEY_SEPARATOR); - if (moduleName.includes(fileNameWithoutEnding)) { + if (list.includes(val)) { this.cache.delete(key); - this.pendingInvalidations.add(containingFile); } }); } @@ -181,13 +186,13 @@ export function createSvelteModuleLoader( svelteSys.deleteFromCache(path); moduleCache.delete(path); }, - deleteUnresolvedResolutionsFromCache: (path: string) => { + scheduleResolutionFailedLocationCheck: (path: string) => { svelteSys.deleteFromCache(path); - moduleCache.deleteUnresolvedResolutionsFromCache(path); - pendingFailedLocationCheck.add(path); - - tsModuleCache.clear(); - typeReferenceCache.clear(); + if (isSvelteFilePath(path)) { + pendingFailedLocationCheck.add(toVirtualSvelteFilePath(path)); + } else { + pendingFailedLocationCheck.add(path); + } }, resolveModuleNames, resolveTypeReferenceDirectiveReferences, @@ -206,8 +211,9 @@ export function createSvelteModuleLoader( containingSourceFile?: ts.SourceFile | undefined ): Array { return moduleNames.map((moduleName, index) => { - if (moduleCache.has(moduleName, containingFile)) { - return moduleCache.get(moduleName, containingFile); + const cached = moduleCache.get(moduleName, containingFile); + if (cached) { + return cached.resolvedModule; } const resolvedModule = resolveModuleName( @@ -221,7 +227,7 @@ export function createSvelteModuleLoader( cacheResolutionWithFailedLookup(resolvedModule, containingFile); - moduleCache.set(moduleName, containingFile, resolvedModule?.resolvedModule); + moduleCache.set(moduleName, containingFile, resolvedModule); return resolvedModule?.resolvedModule; }); } @@ -347,17 +353,15 @@ export function createSvelteModuleLoader( } function invalidateFailedLocationResolution() { + const toRemoves: ts.ResolvedModuleWithFailedLookupLocations[] = []; resolutionWithFailedLookup.forEach((resolvedModule) => { - if ( - !resolvedModule.resolvedModule || - !resolvedModule.files || - !resolvedModule.failedLookupLocations - ) { + if (!resolvedModule.failedLookupLocations) { return; } + for (const location of resolvedModule.failedLookupLocations) { if (pendingFailedLocationCheck.has(location)) { - moduleCache.delete(resolvedModule.resolvedModule.resolvedFileName); + toRemoves.push(resolvedModule); resolvedModule.files?.forEach((file) => { failedLocationInvalidated.add(file); }); @@ -366,6 +370,16 @@ export function createSvelteModuleLoader( } }); + if (toRemoves.length) { + moduleCache.deleteByValues(toRemoves); + resolutionWithFailedLookup.forEach((r) => { + if (toRemoves.includes(r)) { + resolutionWithFailedLookup.delete(r); + } + }); + tsModuleCache.clear(); + tsTypeReferenceDirectiveCache.clear(); + } pendingFailedLocationCheck.clear(); } } diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index cffba1ae0..5271a5224 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -531,7 +531,7 @@ async function createLanguageService( for (const filePath of filePaths) { const normalizedPath = normalizePath(filePath); svelteModuleLoader.deleteFromModuleCache(normalizedPath); - svelteModuleLoader.deleteUnresolvedResolutionsFromCache(normalizedPath); + svelteModuleLoader.scheduleResolutionFailedLocationCheck(normalizedPath); scheduleUpdate(normalizedPath); } @@ -559,7 +559,7 @@ async function createLanguageService( const newSnapshot = DocumentSnapshot.fromDocument(document, transformationConfig); if (!prevSnapshot) { - svelteModuleLoader.deleteUnresolvedResolutionsFromCache(filePath); + svelteModuleLoader.scheduleResolutionFailedLocationCheck(filePath); if (configFileForOpenFiles.get(filePath) === '' && services.size > 1) { configFileForOpenFiles.delete(filePath); } @@ -580,6 +580,7 @@ async function createLanguageService( return prevSnapshot; } + svelteModuleLoader.scheduleResolutionFailedLocationCheck(filePath); return createSnapshot(filePath); } @@ -600,6 +601,8 @@ async function createLanguageService( return undefined; } + // don't invalidate the module cache here + // this only get called if we already know the file exists return createSnapshot( svelteModuleLoader.svelteFileExists(fileName) ? svelteFileName : fileName ); @@ -613,11 +616,12 @@ async function createLanguageService( return doc; } + // don't invalidate the module cache here + // this only get called if we already know the file exists return createSnapshot(fileName); } function createSnapshot(fileName: string) { - svelteModuleLoader.deleteUnresolvedResolutionsFromCache(fileName); const doc = DocumentSnapshot.fromFilePath( fileName, docContext.createDocument, @@ -730,7 +734,7 @@ async function createLanguageService( function updateTsOrJsFile(fileName: string, changes?: TextDocumentContentChangeEvent[]): void { if (!snapshotManager.has(fileName)) { - svelteModuleLoader.deleteUnresolvedResolutionsFromCache(fileName); + svelteModuleLoader.scheduleResolutionFailedLocationCheck(fileName); } snapshotManager.updateTsOrJsFile(fileName, changes); } diff --git a/packages/language-server/test/lib/documents/DocumentManager.test.ts b/packages/language-server/test/lib/documents/DocumentManager.test.ts index db5ad798e..833118cb0 100644 --- a/packages/language-server/test/lib/documents/DocumentManager.test.ts +++ b/packages/language-server/test/lib/documents/DocumentManager.test.ts @@ -68,19 +68,6 @@ describe('Document Manager', () => { assert.throws(() => manager.updateDocument(textDocument, [])); }); - it('emits a document change event on open and update', () => { - const manager = new DocumentManager(createTextDocument); - const cb = sinon.spy(); - - manager.on('documentChange', cb); - - manager.openClientDocument(textDocument); - sinon.assert.calledOnce(cb); - - manager.updateDocument(textDocument, []); - sinon.assert.calledTwice(cb); - }); - it('update document in case-insensitive fs with different casing', () => { const textDocument: TextDocumentItem = { uri: 'file:///hello2.svelte', diff --git a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts index b6227bfb7..1d1a32f7c 100644 --- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts @@ -92,6 +92,28 @@ describe('DiagnosticsProvider', function () { } }).timeout(this.timeout() * 2.5); + it('notices changes of module resolution because of new svelte file', async () => { + const { plugin, document, lsAndTsDocResolver } = setup('unresolvedimport2.svelte'); + + const diagnostics1 = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics1.length, 1); + + // back-and-forth-conversion normalizes slashes + const newSvelteFilePath = normalizePath(path.join(testDir, 'doesntexistyet.svelte')) || ''; + + writeFileSync(newSvelteFilePath, ''); + assert.ok(existsSync(newSvelteFilePath)); + await lsAndTsDocResolver.invalidateModuleCache([newSvelteFilePath]); + + try { + const diagnostics2 = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics2.length, 0); + await lsAndTsDocResolver.deleteSnapshot(newSvelteFilePath); + } finally { + unlinkSync(newSvelteFilePath); + } + }).timeout(this.timeout() * 2.5); + it('notices update of imported module', async () => { const { plugin, document, lsAndTsDocResolver } = setup( 'diagnostics-imported-js-update.svelte' diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/unresolvedimport2.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/unresolvedimport2.svelte new file mode 100644 index 000000000..66ba21346 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/unresolvedimport2.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file From 69e258e0fb03fee7c890d20fc03a4f6ffe2bf69d Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 20 Sep 2025 11:18:45 +0100 Subject: [PATCH 21/39] perf: rework snapshot hot paths (#2852) * perf: rework snapshot hot paths **`fromNonSveltePath`** This function was originally using `replace` to unix-ify paths before then testing against them with `endsWith`. We can instead use static patterns on the unaltered path regardless of separator. **`getLastPartOfPath`** This now uses `slice` on the last separator rather than splitting only to retrieve the last part. * perf: avoid garbage collection on diagnostics Reverts the `getKey` change and updates diagnostics iteration to avoid allocating an array we later discard. * Tweak snapshot hot paths for performance * chore: make linter happy --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/cool-chefs-compete.md | 6 + .../plugins/typescript/DocumentSnapshot.ts | 20 ++-- packages/language-server/src/svelte-check.ts | 111 +++++++++--------- packages/language-server/src/utils.ts | 5 +- 4 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 .changeset/cool-chefs-compete.md diff --git a/.changeset/cool-chefs-compete.md b/.changeset/cool-chefs-compete.md new file mode 100644 index 000000000..1ec9e832e --- /dev/null +++ b/.changeset/cool-chefs-compete.md @@ -0,0 +1,6 @@ +--- +'svelte-language-server': patch +'svelte-check': patch +--- + +perf: tweak some snapshot hot paths diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index aaf2a11e6..72e7eba76 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -73,6 +73,11 @@ export interface SvelteSnapshotOptions { typingsNamespace: string; } +const ambientPathPattern = /node_modules[\/\\]svelte[\/\\]types[\/\\]ambient\.d\.ts$/; +const svelteTypesPattern = /node_modules[\/\\]svelte[\/\\]types[\/\\]index\.d\.ts$/; +const shimsPattern = + /svelte2tsx[\/\\]svelte-shims\.d\.ts$|svelte-check[\/\\]dist[\/\\]src[\/\\]svelte-shims\.d\.ts$/; + export namespace DocumentSnapshot { /** * Returns a svelte snapshot from a svelte document. @@ -121,30 +126,25 @@ export namespace DocumentSnapshot { * @param options options that apply in case it's a svelte file */ export function fromNonSvelteFilePath(filePath: string, tsSystem: ts.System) { - let originalText = ''; - // The following (very hacky) code makes sure that the ambient module definitions // that tell TS "every import ending with .svelte is a valid module" are removed. // They exist in svelte2tsx and svelte to make sure that people don't // get errors in their TS files when importing Svelte files and not using our TS plugin. // If someone wants to get back the behavior they can add an ambient module definition // on their own. - const normalizedPath = filePath.replace(/\\/g, '/'); - if (!normalizedPath.endsWith('node_modules/svelte/types/runtime/ambient.d.ts')) { + let originalText = ''; + if (!ambientPathPattern.test(filePath)) { originalText = tsSystem.readFile(filePath) || ''; } - if (normalizedPath.endsWith('node_modules/svelte/types/index.d.ts')) { + if (svelteTypesPattern.test(filePath)) { const startIdx = originalText.indexOf(`declare module '*.svelte' {`); const endIdx = originalText.indexOf(`\n}`, startIdx + 1) + 2; originalText = originalText.substring(0, startIdx) + ' '.repeat(endIdx - startIdx) + originalText.substring(endIdx); - } else if ( - normalizedPath.endsWith('svelte2tsx/svelte-shims.d.ts') || - normalizedPath.endsWith('svelte-check/dist/src/svelte-shims.d.ts') - ) { + } else if (shimsPattern.test(filePath)) { // If not present, the LS uses an older version of svelte2tsx if (originalText.includes('// -- start svelte-ls-remove --')) { originalText = @@ -157,7 +157,7 @@ export namespace DocumentSnapshot { } const declarationExtensions = [ts.Extension.Dcts, ts.Extension.Dts, ts.Extension.Dmts]; - if (declarationExtensions.some((ext) => normalizedPath.endsWith(ext))) { + if (declarationExtensions.some((ext) => filePath.endsWith(ext))) { return new DtsDocumentSnapshot(INITIAL_VERSION, filePath, originalText, tsSystem); } diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 00f0d4abb..c83547a44 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -252,66 +252,67 @@ export class SvelteCheck { const isKitFile = snapshot?.kitFile ?? false; const diagnostics: Diagnostic[] = []; if (!skipDiagnosticsForFile) { - const originalDiagnostics = [ - ...lang.getSyntacticDiagnostics(file.fileName), - ...lang.getSuggestionDiagnostics(file.fileName), - ...lang.getSemanticDiagnostics(file.fileName) - ]; - - for (let diagnostic of originalDiagnostics) { - if (!diagnostic.start || !diagnostic.length || !isKitFile) { - diagnostics.push(map(diagnostic)); - continue; - } + const diagnosticSources = [ + 'getSyntacticDiagnostics', + 'getSuggestionDiagnostics', + 'getSemanticDiagnostics' + ] as const; + for (const diagnosticSource of diagnosticSources) { + for (let diagnostic of lang[diagnosticSource](file.fileName)) { + if (!diagnostic.start || !diagnostic.length || !isKitFile) { + diagnostics.push(map(diagnostic)); + continue; + } - let range: Range | undefined = undefined; - const inGenerated = isInGeneratedCode( - file.text, - diagnostic.start, - diagnostic.start + diagnostic.length - ); - if (inGenerated && snapshot) { - const pos = snapshot.getOriginalPosition( - snapshot.positionAt(diagnostic.start) + let range: Range | undefined = undefined; + const inGenerated = isInGeneratedCode( + file.text, + diagnostic.start, + diagnostic.start + diagnostic.length ); - range = { - start: pos, - end: { - line: pos.line, - // adjust length so it doesn't spill over to the next line - character: pos.character + 1 - } - }; - // If not one of the specific error messages then filter out - if (diagnostic.code === 2307) { - diagnostic = { - ...diagnostic, - messageText: - typeof diagnostic.messageText === 'string' && - diagnostic.messageText.includes('./$types') - ? diagnostic.messageText + - ` (this likely means that SvelteKit's type generation didn't run yet - try running it by executing 'npm run dev' or 'npm run build')` - : diagnostic.messageText + if (inGenerated && snapshot) { + const pos = snapshot.getOriginalPosition( + snapshot.positionAt(diagnostic.start) + ); + range = { + start: pos, + end: { + line: pos.line, + // adjust length so it doesn't spill over to the next line + character: pos.character + 1 + } }; - } else if (diagnostic.code === 2694) { - diagnostic = { - ...diagnostic, - messageText: - typeof diagnostic.messageText === 'string' && - diagnostic.messageText.includes('/$types') - ? diagnostic.messageText + - ` (this likely means that SvelteKit's generated types are out of date - try rerunning it by executing 'npm run dev' or 'npm run build')` - : diagnostic.messageText - }; - } else if ( - diagnostic.code !== - 2355 /* A function whose declared type is neither 'void' nor 'any' must return a value */ - ) { - continue; + // If not one of the specific error messages then filter out + if (diagnostic.code === 2307) { + diagnostic = { + ...diagnostic, + messageText: + typeof diagnostic.messageText === 'string' && + diagnostic.messageText.includes('./$types') + ? diagnostic.messageText + + ` (this likely means that SvelteKit's type generation didn't run yet - try running it by executing 'npm run dev' or 'npm run build')` + : diagnostic.messageText + }; + } else if (diagnostic.code === 2694) { + diagnostic = { + ...diagnostic, + messageText: + typeof diagnostic.messageText === 'string' && + diagnostic.messageText.includes('/$types') + ? diagnostic.messageText + + ` (this likely means that SvelteKit's generated types are out of date - try rerunning it by executing 'npm run dev' or 'npm run build')` + : diagnostic.messageText + }; + } else if ( + diagnostic.code !== + 2355 /* A function whose declared type is neither 'void' nor 'any' must return a value */ + ) { + continue; + } } - } - diagnostics.push(map(diagnostic, range)); + diagnostics.push(map(diagnostic, range)); + } } } diff --git a/packages/language-server/src/utils.ts b/packages/language-server/src/utils.ts index 1a136ed0e..9a1ba01d2 100644 --- a/packages/language-server/src/utils.ts +++ b/packages/language-server/src/utils.ts @@ -60,7 +60,10 @@ export function normalizeUri(uri: string): string { * (bar or bar.svelte in this example). */ export function getLastPartOfPath(path: string): string { - return path.replace(/\\/g, '/').split('/').pop() || ''; + const lastSlash = path.lastIndexOf('/'); + const lastBackslash = path.lastIndexOf('\\'); + const lastSeparator = Math.max(lastSlash, lastBackslash); + return lastSeparator === -1 ? path : path.slice(lastSeparator + 1); } export function flatten(arr: Array): T[] { From 62b31ffce11887f01d7483fe72c346e0dfe69e01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:05:56 +0200 Subject: [PATCH 22/39] Version Packages (#2848) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/cool-chefs-compete.md | 6 ------ .changeset/dark-falcons-pump.md | 6 ------ .changeset/fuzzy-points-stick.md | 5 ----- .changeset/green-news-find.md | 6 ------ packages/language-server/CHANGELOG.md | 11 +++++++++++ packages/language-server/package.json | 2 +- packages/svelte-check/CHANGELOG.md | 10 ++++++++++ packages/svelte-check/package.json | 2 +- packages/svelte2tsx/CHANGELOG.md | 8 ++++++++ packages/svelte2tsx/package.json | 2 +- 10 files changed, 32 insertions(+), 26 deletions(-) delete mode 100644 .changeset/cool-chefs-compete.md delete mode 100644 .changeset/dark-falcons-pump.md delete mode 100644 .changeset/fuzzy-points-stick.md delete mode 100644 .changeset/green-news-find.md diff --git a/.changeset/cool-chefs-compete.md b/.changeset/cool-chefs-compete.md deleted file mode 100644 index 1ec9e832e..000000000 --- a/.changeset/cool-chefs-compete.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'svelte-language-server': patch -'svelte-check': patch ---- - -perf: tweak some snapshot hot paths diff --git a/.changeset/dark-falcons-pump.md b/.changeset/dark-falcons-pump.md deleted file mode 100644 index 6350244c8..000000000 --- a/.changeset/dark-falcons-pump.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'svelte-language-server': patch -'svelte-check': patch ---- - -perf: more precise module cache invalidation diff --git a/.changeset/fuzzy-points-stick.md b/.changeset/fuzzy-points-stick.md deleted file mode 100644 index dc3bcc98c..000000000 --- a/.changeset/fuzzy-points-stick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte2tsx': patch ---- - -chore(deps): Replace `pascal-case` with `scule` diff --git a/.changeset/green-news-find.md b/.changeset/green-news-find.md deleted file mode 100644 index 44d75ae3a..000000000 --- a/.changeset/green-news-find.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'svelte2tsx': patch -'svelte-check': patch ---- - -fix: properly handle `runes={false}` in `` diff --git a/packages/language-server/CHANGELOG.md b/packages/language-server/CHANGELOG.md index c8c4cda62..4192734ae 100644 --- a/packages/language-server/CHANGELOG.md +++ b/packages/language-server/CHANGELOG.md @@ -1,3 +1,14 @@ # Changelog +## 0.17.20 + +### Patch Changes + +- perf: tweak some snapshot hot paths ([#2852](https://github.com/sveltejs/language-tools/pull/2852)) + +- perf: more precise module cache invalidation ([#2853](https://github.com/sveltejs/language-tools/pull/2853)) + +- Updated dependencies [[`f799839`](https://github.com/sveltejs/language-tools/commit/f799839a5a5dfc5dcffdc42fb34b5b10b4345be5), [`dec37ea`](https://github.com/sveltejs/language-tools/commit/dec37eabe44370615d98af2d19ae6ed7feafc297)]: + - svelte2tsx@0.7.44 + See https://github.com/sveltejs/language-tools/releases diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 9574450ec..22079734a 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,6 +1,6 @@ { "name": "svelte-language-server", - "version": "0.17.19", + "version": "0.17.20", "description": "A language server for Svelte", "main": "dist/src/index.js", "typings": "dist/src/index", diff --git a/packages/svelte-check/CHANGELOG.md b/packages/svelte-check/CHANGELOG.md index c8c4cda62..22e4e2432 100644 --- a/packages/svelte-check/CHANGELOG.md +++ b/packages/svelte-check/CHANGELOG.md @@ -1,3 +1,13 @@ # Changelog +## 4.3.2 + +### Patch Changes + +- perf: tweak some snapshot hot paths ([#2852](https://github.com/sveltejs/language-tools/pull/2852)) + +- perf: more precise module cache invalidation ([#2853](https://github.com/sveltejs/language-tools/pull/2853)) + +- fix: properly handle `runes={false}` in `` ([#2847](https://github.com/sveltejs/language-tools/pull/2847)) + See https://github.com/sveltejs/language-tools/releases diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index a8dfb69e6..7cda8e700 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -1,7 +1,7 @@ { "name": "svelte-check", "description": "Svelte Code Checker Terminal Interface", - "version": "4.3.1", + "version": "4.3.2", "main": "./dist/src/index.js", "bin": "./bin/svelte-check", "author": "The Svelte Community", diff --git a/packages/svelte2tsx/CHANGELOG.md b/packages/svelte2tsx/CHANGELOG.md index 6e1df3059..a7c77c15f 100644 --- a/packages/svelte2tsx/CHANGELOG.md +++ b/packages/svelte2tsx/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.7.44 + +### Patch Changes + +- chore(deps): Replace `pascal-case` with `scule` ([#2842](https://github.com/sveltejs/language-tools/pull/2842)) + +- fix: properly handle `runes={false}` in `` ([#2847](https://github.com/sveltejs/language-tools/pull/2847)) + ## 0.7.43 ### Patch Changes diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index f214ffe38..082a0139e 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -1,6 +1,6 @@ { "name": "svelte2tsx", - "version": "0.7.43", + "version": "0.7.44", "description": "Convert Svelte components to TSX for type checking", "author": "The Svelte Community", "license": "MIT", From b46704549331172845b814c63c4020e16eb2ccb1 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 22 Sep 2025 14:14:16 +0200 Subject: [PATCH 23/39] chore: fix svelte-check build --- packages/svelte-check/package.json | 6 +++--- packages/svelte-check/src/options.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 7cda8e700..b01aad527 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -37,9 +37,9 @@ "typescript": ">=5.0.0" }, "scripts": { - "build": "rollup -c && node ./dist/src/index.js --workspace ./test --tsconfig ./tsconfig.json", - "prepublishOnly": "npm run build", - "test": "npm run build" + "build": "cd ../svelte2tsx && pnpm build && cd ../language-server && pnpm build && cd ../svelte-check && rollup -c && node ./dist/src/index.js --workspace ./test --tsconfig ./tsconfig.json", + "prepublishOnly": "pnpm build", + "test": "pnpm build" }, "devDependencies": { "@rollup/plugin-commonjs": "^24.0.0", diff --git a/packages/svelte-check/src/options.ts b/packages/svelte-check/src/options.ts index faf3f6080..b8ceca8f2 100644 --- a/packages/svelte-check/src/options.ts +++ b/packages/svelte-check/src/options.ts @@ -180,8 +180,8 @@ function getCompilerWarnings(opts: Record) { } } -const diagnosticSources = ['js', 'css', 'svelte'] as const; -type DiagnosticSource = (typeof diagnosticSources)[number]; +type DiagnosticSource = 'js' | 'css' | 'svelte'; +const diagnosticSources: DiagnosticSource[] = ['js', 'css', 'svelte']; function getDiagnosticSources(opts: Record): DiagnosticSource[] { const sources = opts['diagnostic-sources']; From 71c0d988ad45222d25677d5d1a8f3ad1d19ea1a2 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:59:03 +0800 Subject: [PATCH 24/39] fix: prevent file watcher issue (#2859) * fix: prevent file watcher issue * perf: check if file content changed in tsconfig file watch --- .changeset/green-adults-hammer.md | 5 ++ .changeset/thirty-seas-post.md | 6 +++ .../src/plugins/typescript/service.ts | 29 ++++++++-- packages/svelte-check/src/index.ts | 54 ++++++++++--------- 4 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 .changeset/green-adults-hammer.md create mode 100644 .changeset/thirty-seas-post.md diff --git a/.changeset/green-adults-hammer.md b/.changeset/green-adults-hammer.md new file mode 100644 index 000000000..ed327704f --- /dev/null +++ b/.changeset/green-adults-hammer.md @@ -0,0 +1,5 @@ +--- +'svelte-check': patch +--- + +fix: prevent file watcher issue diff --git a/.changeset/thirty-seas-post.md b/.changeset/thirty-seas-post.md new file mode 100644 index 000000000..8aadd734e --- /dev/null +++ b/.changeset/thirty-seas-post.md @@ -0,0 +1,6 @@ +--- +'svelte-language-server': patch +'svelte-check': patch +--- + +perf: check if file content changed in tsconfig file watch diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 5271a5224..0161a4628 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -965,7 +965,11 @@ async function createLanguageService( ) { if ( kind === ts.FileWatcherEventKind.Changed && - !configFileModified(fileName, modifiedTime ?? tsSystem.getModifiedTime?.(fileName)) + !configFileModified( + fileName, + modifiedTime ?? tsSystem.getModifiedTime?.(fileName), + docContext + ) ) { return; } @@ -1328,7 +1332,8 @@ function createWatchDependedConfigCallback(docContext: LanguageServiceDocumentCo kind === ts.FileWatcherEventKind.Changed && !configFileModified( fileName, - modifiedTime ?? docContext.tsSystem.getModifiedTime?.(fileName) + modifiedTime ?? docContext.tsSystem.getModifiedTime?.(fileName), + docContext ) ) { return; @@ -1360,7 +1365,11 @@ function createWatchDependedConfigCallback(docContext: LanguageServiceDocumentCo /** * check if file content is modified instead of attributes changed */ -function configFileModified(fileName: string, modifiedTime: Date | undefined) { +function configFileModified( + fileName: string, + modifiedTime: Date | undefined, + docContext: LanguageServiceDocumentContext +) { const previousModifiedTime = configFileModifiedTime.get(fileName); if (!modifiedTime || !previousModifiedTime) { return true; @@ -1371,6 +1380,20 @@ function configFileModified(fileName: string, modifiedTime: Date | undefined) { } configFileModifiedTime.set(fileName, modifiedTime); + + const oldSourceFile = + parsedTsConfigInfo.get(fileName)?.parsedCommandLine?.options.configFile ?? + docContext.extendedConfigCache.get(fileName)?.extendedResult; + + if ( + oldSourceFile && + typeof oldSourceFile === 'object' && + 'kind' in oldSourceFile && + typeof oldSourceFile.text === 'string' && + oldSourceFile.text === docContext.tsSystem.readFile(fileName) + ) { + return false; + } return true; } diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index aaeffc422..b803050f7 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -194,13 +194,17 @@ class DiagnosticsWatcher { .on('unlink', (path) => this.removeDocument(path)) .on('change', (path) => this.updateDocument(path, false)); - this.updateWatchedDirectories(); - if (this.ignoreInitialAdd) { - getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck); - } + this.updateWildcardWatcher().then(() => { + // ensuring the typescript program is built after wildcard watchers are added + // so that individual file watchers added from onFileSnapshotCreated + // run after the wildcard ones + if (this.ignoreInitialAdd) { + getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck); + } + }); } - private isSubdir(candidate: string, parent: string) { + private isSubDir(candidate: string, parent: string) { const c = path.resolve(candidate); const p = path.resolve(parent); return c === p || c.startsWith(p + path.sep); @@ -210,7 +214,7 @@ class DiagnosticsWatcher { const sorted = [...new Set(dirs.map((d) => path.resolve(d)))].sort(); const result: string[] = []; for (const dir of sorted) { - if (!result.some((p) => this.isSubdir(dir, p))) { + if (!result.some((p) => this.isSubDir(dir, p))) { result.push(dir); } } @@ -218,29 +222,29 @@ class DiagnosticsWatcher { } addWatchDirectory(dir: string) { - if (!dir) return; + if (!dir) { + return; + } + // Skip if already covered by an existing watched directory for (const existing of this.currentWatchedDirs) { - if (this.isSubdir(dir, existing)) { + if (this.isSubDir(dir, existing)) { return; } } - // If new dir is a parent of existing ones, unwatch children - const toRemove: string[] = []; + + // Don't remove existing watchers, chokidar `unwatch` ignores future events from that path instead of closing the watcher in some cases for (const existing of this.currentWatchedDirs) { - if (this.isSubdir(existing, dir)) { - toRemove.push(existing); + if (this.isSubDir(existing, dir)) { + this.currentWatchedDirs.delete(existing); } } - if (toRemove.length) { - this.watcher.unwatch(toRemove); - for (const r of toRemove) this.currentWatchedDirs.delete(r); - } + this.watcher.add(dir); this.currentWatchedDirs.add(dir); } - private async updateWatchedDirectories() { + private async updateWildcardWatcher() { const watchDirs = await this.svelteCheck.getWatchDirectories(); const desired = this.minimizeDirs( (watchDirs?.map((d) => d.path) || [this.workspaceUri.fsPath]).map((p) => @@ -249,15 +253,13 @@ class DiagnosticsWatcher { ); const current = new Set([...this.currentWatchedDirs].map((p) => path.resolve(p))); - const desiredSet = new Set(desired); const toAdd = desired.filter((d) => !current.has(d)); - const toRemove = [...current].filter((d) => !desiredSet.has(d)); - - if (toAdd.length) this.watcher.add(toAdd); - if (toRemove.length) this.watcher.unwatch(toRemove); + if (toAdd.length) { + this.watcher.add(toAdd); + } - this.currentWatchedDirs = new Set(desired); + this.currentWatchedDirs = new Set([...current, ...toAdd]); } private async updateDocument(path: string, isNew: boolean) { @@ -280,9 +282,9 @@ class DiagnosticsWatcher { this.scheduleDiagnostics(); } - updateWatchers() { + updateWildcardWatchers() { clearTimeout(this.pendingWatcherUpdate); - this.pendingWatcherUpdate = setTimeout(() => this.updateWatchedDirectories(), 1000); + this.pendingWatcherUpdate = setTimeout(() => this.updateWildcardWatcher(), 1000); } scheduleDiagnostics() { @@ -342,7 +344,7 @@ parseOptions(async (opts) => { // Wire callbacks that can reference the watcher instance created below let watcher: DiagnosticsWatcher; svelteCheckOptions.onProjectReload = () => { - watcher.updateWatchers(); + watcher.updateWildcardWatchers(); watcher.scheduleDiagnostics(); }; svelteCheckOptions.onFileSnapshotCreated = (filePath: string) => { From 7468286afd56b886f5490adebe6f667306d0fe08 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:09:33 +0200 Subject: [PATCH 25/39] fix: allow `undefined` and `null` values for `#each` in Svelte 5 (#2863) * fix: allow `undefined` and `null` values for `#each` in Svelte 5 * switch --- .changeset/short-pugs-kick.md | 6 ++++++ .../fixtures/each/expected_svelte_4.json | 17 +++++++++++++++++ .../fixtures/each/expected_svelte_5.json | 4 ++-- .../diagnostics/fixtures/each/input.svelte | 7 ++++++- packages/svelte2tsx/svelte-shims-v4.d.ts | 5 ++++- 5 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .changeset/short-pugs-kick.md diff --git a/.changeset/short-pugs-kick.md b/.changeset/short-pugs-kick.md new file mode 100644 index 000000000..69d998182 --- /dev/null +++ b/.changeset/short-pugs-kick.md @@ -0,0 +1,6 @@ +--- +'svelte-check': patch +'svelte2tsx': patch +--- + +fix: allow `undefined` and `null` values for `#each` in Svelte 5 diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_4.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_4.json index 6c90ac01b..bde447ad6 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_4.json +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_4.json @@ -32,5 +32,22 @@ "message": "Argument of type 'number' is not assignable to parameter of type 'ArrayLike | Iterable'.", "code": 2345, "tags": [] + }, + { + "range": { + "start": { + "line": 38, + "character": 7 + }, + "end": { + "line": 38, + "character": 21 + } + }, + "severity": 1, + "source": "ts", + "message": "Argument of type 'number[] | null | undefined' is not assignable to parameter of type 'ArrayLike | Iterable'.\n Type 'undefined' is not assignable to type 'ArrayLike | Iterable'.", + "code": 2345, + "tags": [] } ] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_5.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_5.json index 6c90ac01b..0124b9634 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_5.json +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/expected_svelte_5.json @@ -12,7 +12,7 @@ }, "severity": 1, "source": "ts", - "message": "Argument of type '{}' is not assignable to parameter of type 'ArrayLike | Iterable'.", + "message": "Argument of type '{}' is not assignable to parameter of type 'ArrayLike | Iterable | null | undefined'.", "code": 2345, "tags": [] }, @@ -29,7 +29,7 @@ }, "severity": 1, "source": "ts", - "message": "Argument of type 'number' is not assignable to parameter of type 'ArrayLike | Iterable'.", + "message": "Argument of type 'number' is not assignable to parameter of type 'ArrayLike | Iterable | null | undefined'.", "code": 2345, "tags": [] } diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/input.svelte index 56899d006..931647931 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/input.svelte +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/each/input.svelte @@ -6,7 +6,7 @@ interface OptionObject { const simpleOptions: number[] = []; const complexOptions: string[] | OptionObject[] = []; - +const maybeUndefined: number[] | undefined | null = null as any; const badOptions = { object: {}, number: 1, @@ -34,3 +34,8 @@ const badOptions = { {#each badOptions.number as option, i}
{option} {i}
{/each} + + +{#each maybeUndefined as option, i (i)} +
{option}, {i}
+{/each} diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index fcaf92f65..29d84a2d9 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -250,7 +250,10 @@ declare function __sveltets_2_ensureComponent< : never >; -declare function __sveltets_2_ensureArray | Iterable>(array: T): T extends ArrayLike ? U[] : T extends Iterable ? Iterable : any[]; +declare function __sveltets_2_ensureArray | Iterable>( + // Svelte 5 allows undefined or null here, Svelte 4 doesn't + array: T | (typeof import('svelte') extends { mount: any } ? (undefined | null) : never) +): T extends ArrayLike ? U[] : T extends Iterable ? Iterable : any[]; type __sveltets_2_PropsWithChildren = Props & (Slots extends { default: any } From edcb4ed8e840305d90fdb1084abab6ee91832a51 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:13:07 +0200 Subject: [PATCH 26/39] Version Packages (#2861) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/green-adults-hammer.md | 5 ----- .changeset/short-pugs-kick.md | 6 ------ .changeset/thirty-seas-post.md | 6 ------ packages/language-server/CHANGELOG.md | 9 +++++++++ packages/language-server/package.json | 2 +- packages/svelte-check/CHANGELOG.md | 10 ++++++++++ packages/svelte-check/package.json | 2 +- packages/svelte2tsx/CHANGELOG.md | 6 ++++++ packages/svelte2tsx/package.json | 2 +- 9 files changed, 28 insertions(+), 20 deletions(-) delete mode 100644 .changeset/green-adults-hammer.md delete mode 100644 .changeset/short-pugs-kick.md delete mode 100644 .changeset/thirty-seas-post.md diff --git a/.changeset/green-adults-hammer.md b/.changeset/green-adults-hammer.md deleted file mode 100644 index ed327704f..000000000 --- a/.changeset/green-adults-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-check': patch ---- - -fix: prevent file watcher issue diff --git a/.changeset/short-pugs-kick.md b/.changeset/short-pugs-kick.md deleted file mode 100644 index 69d998182..000000000 --- a/.changeset/short-pugs-kick.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'svelte-check': patch -'svelte2tsx': patch ---- - -fix: allow `undefined` and `null` values for `#each` in Svelte 5 diff --git a/.changeset/thirty-seas-post.md b/.changeset/thirty-seas-post.md deleted file mode 100644 index 8aadd734e..000000000 --- a/.changeset/thirty-seas-post.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'svelte-language-server': patch -'svelte-check': patch ---- - -perf: check if file content changed in tsconfig file watch diff --git a/packages/language-server/CHANGELOG.md b/packages/language-server/CHANGELOG.md index 4192734ae..b4b93fc3f 100644 --- a/packages/language-server/CHANGELOG.md +++ b/packages/language-server/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.17.21 + +### Patch Changes + +- perf: check if file content changed in tsconfig file watch ([#2859](https://github.com/sveltejs/language-tools/pull/2859)) + +- Updated dependencies [[`7468286`](https://github.com/sveltejs/language-tools/commit/7468286afd56b886f5490adebe6f667306d0fe08)]: + - svelte2tsx@0.7.45 + ## 0.17.20 ### Patch Changes diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 22079734a..7411f4d73 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,6 +1,6 @@ { "name": "svelte-language-server", - "version": "0.17.20", + "version": "0.17.21", "description": "A language server for Svelte", "main": "dist/src/index.js", "typings": "dist/src/index", diff --git a/packages/svelte-check/CHANGELOG.md b/packages/svelte-check/CHANGELOG.md index 22e4e2432..7e0e6ec60 100644 --- a/packages/svelte-check/CHANGELOG.md +++ b/packages/svelte-check/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 4.3.3 + +### Patch Changes + +- fix: prevent file watcher issue ([#2859](https://github.com/sveltejs/language-tools/pull/2859)) + +- fix: allow `undefined` and `null` values for `#each` in Svelte 5 ([#2863](https://github.com/sveltejs/language-tools/pull/2863)) + +- perf: check if file content changed in tsconfig file watch ([#2859](https://github.com/sveltejs/language-tools/pull/2859)) + ## 4.3.2 ### Patch Changes diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index b01aad527..e7aa84255 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -1,7 +1,7 @@ { "name": "svelte-check", "description": "Svelte Code Checker Terminal Interface", - "version": "4.3.2", + "version": "4.3.3", "main": "./dist/src/index.js", "bin": "./bin/svelte-check", "author": "The Svelte Community", diff --git a/packages/svelte2tsx/CHANGELOG.md b/packages/svelte2tsx/CHANGELOG.md index a7c77c15f..f4a5bfd89 100644 --- a/packages/svelte2tsx/CHANGELOG.md +++ b/packages/svelte2tsx/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.7.45 + +### Patch Changes + +- fix: allow `undefined` and `null` values for `#each` in Svelte 5 ([#2863](https://github.com/sveltejs/language-tools/pull/2863)) + ## 0.7.44 ### Patch Changes diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 082a0139e..15b242f11 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -1,6 +1,6 @@ { "name": "svelte2tsx", - "version": "0.7.44", + "version": "0.7.45", "description": "Convert Svelte components to TSX for type checking", "author": "The Svelte Community", "license": "MIT", From 6ad05e5b10d14ffe37124059f74a564d61da8045 Mon Sep 17 00:00:00 2001 From: farfromrefuge Date: Fri, 17 Oct 2025 15:28:55 +0200 Subject: [PATCH 27/39] fix: support for @nativescript-community/svelte-native (#2867) * fix: support for @nativescript-community/svelte-native We moved the svelte-native plugin to @nativescript-community. This supports it while still supporting the old package * Add changeset for svelte-language-server patch Add a changeset for patching svelte-language-server. --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/kind-carrots-pull.md | 5 +++++ packages/language-server/src/plugins/typescript/service.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/kind-carrots-pull.md diff --git a/.changeset/kind-carrots-pull.md b/.changeset/kind-carrots-pull.md new file mode 100644 index 000000000..1310d9269 --- /dev/null +++ b/.changeset/kind-carrots-pull.md @@ -0,0 +1,5 @@ +--- +"svelte-language-server": patch +--- + +fix: support for @nativescript-community/svelte-native diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 0161a4628..38334ccdc 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -794,7 +794,7 @@ async function createLanguageService( //override if we detect svelte-native if (workspacePath) { try { - const svelteNativePkgInfo = getPackageInfo('svelte-native', workspacePath); + const svelteNativePkgInfo = getPackageInfo('@nativescript-community/svelte-native', workspacePath) || getPackageInfo('svelte-native', workspacePath); if (svelteNativePkgInfo.path) { // For backwards compatibility parsedConfig.raw.svelteOptions = parsedConfig.raw.svelteOptions || {}; From cadf62e54eec9fd352a7a81193a4a9062f492437 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:00:01 +0800 Subject: [PATCH 28/39] fix: restrict emmet completion with emmet specific triggerCharacter (#2873) * wip * test * changeset * format --- .changeset/kind-carrots-pull.md | 2 +- .changeset/silent-cases-sing.md | 5 +++++ .../language-server/src/plugins/html/HTMLPlugin.ts | 12 +++++++++++- .../src/plugins/typescript/service.ts | 4 +++- .../test/plugins/html/HTMLPlugin.test.ts | 10 ++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 .changeset/silent-cases-sing.md diff --git a/.changeset/kind-carrots-pull.md b/.changeset/kind-carrots-pull.md index 1310d9269..8b779b1e7 100644 --- a/.changeset/kind-carrots-pull.md +++ b/.changeset/kind-carrots-pull.md @@ -1,5 +1,5 @@ --- -"svelte-language-server": patch +'svelte-language-server': patch --- fix: support for @nativescript-community/svelte-native diff --git a/.changeset/silent-cases-sing.md b/.changeset/silent-cases-sing.md new file mode 100644 index 000000000..9d6eab83b --- /dev/null +++ b/.changeset/silent-cases-sing.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +fix: restrict emmet completion with emmet specific triggerCharacter diff --git a/packages/language-server/src/plugins/html/HTMLPlugin.ts b/packages/language-server/src/plugins/html/HTMLPlugin.ts index f94cbe483..68e760376 100644 --- a/packages/language-server/src/plugins/html/HTMLPlugin.ts +++ b/packages/language-server/src/plugins/html/HTMLPlugin.ts @@ -147,7 +147,17 @@ export class HTMLPlugin completionContext?.triggerCharacter && !this.htmlTriggerCharacters.includes(completionContext?.triggerCharacter) ) { - return doEmmetCompleteInner() ?? null; + const node = html.findNodeAt(document.offsetAt(position)); + const offset = document.offsetAt(position); + if ( + !node?.tag || + (offset > (node.startTagEnd ?? node.end) && + (node.endTagStart == null || offset <= node.endTagStart)) + ) { + return doEmmetCompleteInner() ?? null; + } + + return null; } const results = this.isInComponentTag(html, document, position) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 38334ccdc..644a39708 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -794,7 +794,9 @@ async function createLanguageService( //override if we detect svelte-native if (workspacePath) { try { - const svelteNativePkgInfo = getPackageInfo('@nativescript-community/svelte-native', workspacePath) || getPackageInfo('svelte-native', workspacePath); + const svelteNativePkgInfo = + getPackageInfo('@nativescript-community/svelte-native', workspacePath) || + getPackageInfo('svelte-native', workspacePath); if (svelteNativePkgInfo.path) { // For backwards compatibility parsedConfig.raw.svelteOptions = parsedConfig.raw.svelteOptions || {}; diff --git a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts index eff038a11..eef08bd12 100644 --- a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts +++ b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts @@ -190,6 +190,16 @@ describe('HTML Plugin', () => { assert.strictEqual(completions?.items[0]?.label, 'div>'); }); + it('skip emmet completions right after start tag close', async () => { + const { plugin, document } = setup('Test.a>'); + + const completions = await plugin.getCompletions(document, Position.create(0, 5), { + triggerCharacter: '>', + triggerKind: CompletionTriggerKind.TriggerCharacter + }); + assert.strictEqual(completions, null); + }); + it('does not provide rename for element being uppercase', async () => { const { plugin, document } = setup('
'); From 1e01c991cc3314aa617aa9e3cdd76579aa215cd4 Mon Sep 17 00:00:00 2001 From: Craig Jennings <1683368+craig-jennings@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:08:20 -0500 Subject: [PATCH 29/39] feat: implement 'source.removeUnusedImports' code action (#2875) * feat: implement 'source.removeUnusedImports' code action * add source action kind to server capability; revert code action removal * add changeset --- .changeset/wide-emus-turn.md | 5 + .../features/CodeActionsProvider.ts | 53 ++++++++--- packages/language-server/src/server.ts | 4 +- .../features/CodeActionsProvider.test.ts | 91 +++++++++++++++++++ 4 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 .changeset/wide-emus-turn.md diff --git a/.changeset/wide-emus-turn.md b/.changeset/wide-emus-turn.md new file mode 100644 index 000000000..dc805f443 --- /dev/null +++ b/.changeset/wide-emus-turn.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': minor +--- + +feat: implement 'source.removeUnusedImports' code action diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 6887e2514..693c2581a 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -1,5 +1,5 @@ import { internalHelpers } from 'svelte2tsx'; -import ts from 'typescript'; +import ts, { OrganizeImportsMode } from 'typescript'; import { CancellationToken, CodeAction, @@ -65,6 +65,7 @@ import { */ export const SORT_IMPORT_CODE_ACTION_KIND = 'source.sortImports'; export const ADD_MISSING_IMPORTS_CODE_ACTION_KIND = 'source.addMissingImports'; +export const REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND = 'source.removeUnusedImports'; interface RefactorArgs { type: 'refactor'; @@ -120,7 +121,15 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return await this.organizeImports( document, cancellationToken, - /**skipDestructiveCodeActions */ true + OrganizeImportsMode.SortAndCombine + ); + } + + if (context.only?.[0] === REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND) { + return await this.organizeImports( + document, + cancellationToken, + OrganizeImportsMode.RemoveUnused ); } @@ -136,7 +145,12 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ...(await this.organizeImports( document, cancellationToken, - /**skipDestructiveCodeActions */ true + OrganizeImportsMode.SortAndCombine + )), + ...(await this.organizeImports( + document, + cancellationToken, + OrganizeImportsMode.RemoveUnused )), ...(await this.addMissingImports(document, cancellationToken)) ]; @@ -406,7 +420,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { private async organizeImports( document: Document, cancellationToken: CancellationToken | undefined, - skipDestructiveCodeActions = false + mode: OrganizeImportsMode = OrganizeImportsMode.All ): Promise { if (!document.scriptInfo && !document.moduleScriptInfo) { return []; @@ -425,7 +439,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { { fileName: tsDoc.filePath, type: 'file', - skipDestructiveCodeActions + mode }, { ...(await this.configManager.getFormatCodeSettingsForFile( @@ -475,15 +489,26 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { this.checkIndentLeftover(change, document); } - return [ - CodeAction.create( - skipDestructiveCodeActions ? 'Sort Imports' : 'Organize Imports', - { documentChanges }, - skipDestructiveCodeActions - ? SORT_IMPORT_CODE_ACTION_KIND - : CodeActionKind.SourceOrganizeImports - ) - ]; + let kind: CodeActionKind; + let title: string; + + switch (mode) { + case OrganizeImportsMode.SortAndCombine: + kind = SORT_IMPORT_CODE_ACTION_KIND; + title = 'Sort Imports'; + break; + + case OrganizeImportsMode.RemoveUnused: + kind = REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND; + title = 'Remove Unused Imports'; + break; + + default: + kind = CodeActionKind.SourceOrganizeImports; + title = 'Organize Imports'; + } + + return [CodeAction.create(title, { documentChanges }, kind)]; } private fixIndentationOfImports(edit: TextEdit, document: Document): TextEdit { diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index ab5475840..b3fbf6853 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -47,7 +47,8 @@ import { configLoader } from './lib/documents/configLoader'; import { setIsTrusted } from './importPackage'; import { SORT_IMPORT_CODE_ACTION_KIND, - ADD_MISSING_IMPORTS_CODE_ACTION_KIND + ADD_MISSING_IMPORTS_CODE_ACTION_KIND, + REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND } from './plugins/typescript/features/CodeActionsProvider'; import { createLanguageServices } from './plugins/css/service'; import { FileSystemProvider } from './plugins/css/FileSystemProvider'; @@ -274,6 +275,7 @@ export function startServer(options?: LSOptions) { CodeActionKind.SourceOrganizeImports, SORT_IMPORT_CODE_ACTION_KIND, ADD_MISSING_IMPORTS_CODE_ACTION_KIND, + REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND, ...(clientSupportApplyEditCommand ? [CodeActionKind.Refactor] : []) ].filter( clientSupportedCodeActionKinds && diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts index 3b2061139..5c0af619b 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts @@ -16,6 +16,7 @@ import { LSConfigManager } from '../../../../src/ls-config'; import { ADD_MISSING_IMPORTS_CODE_ACTION_KIND, CodeActionsProviderImpl, + REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND, SORT_IMPORT_CODE_ACTION_KIND } from '../../../../src/plugins/typescript/features/CodeActionsProvider'; import { CompletionsProviderImpl } from '../../../../src/plugins/typescript/features/CompletionProvider'; @@ -1550,6 +1551,96 @@ describe('CodeActionsProvider', function () { ]); }); + it('removes unused imports', async () => { + const { provider, document } = setup('codeactions.svelte'); + + const codeActions = await provider.getCodeActions( + document, + Range.create(Position.create(1, 4), Position.create(1, 5)), + { + diagnostics: [], + only: [REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND] + } + ); + (codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach( + (edit) => (edit.newText = harmonizeNewLines(edit.newText)) + ); + + assert.deepStrictEqual(codeActions, [ + { + edit: { + documentChanges: [ + { + edits: [ + { + newText: + "import { C } from 'blubb';\n" + + "import { A } from 'bla';\n", + + range: { + start: { + character: 0, + line: 1 + }, + end: { + character: 0, + line: 2 + } + } + }, + { + newText: '', + range: { + start: { + character: 0, + line: 2 + }, + end: { + character: 0, + line: 3 + } + } + }, + { + newText: '', + range: { + start: { + character: 0, + line: 3 + }, + end: { + character: 0, + line: 4 + } + } + }, + { + newText: '', + range: { + start: { + character: 0, + line: 4 + }, + end: { + character: 0, + line: 5 + } + } + } + ], + textDocument: { + uri: getUri('codeactions.svelte'), + version: null + } + } + ] + }, + kind: REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND, + title: 'Remove Unused Imports' + } + ]); + }); + it('organizes imports with module script', async () => { const { provider, document } = setup('organize-imports-with-module.svelte'); From d0858307af729f11ff5acb3038a61e0faa2c0c7f Mon Sep 17 00:00:00 2001 From: Muhammad Afif Ramadhan <31378187+ramadhanafif@users.noreply.github.com> Date: Wed, 5 Nov 2025 05:23:39 +0100 Subject: [PATCH 30/39] docs: update command for setting up TailwindCSS (#2880) Remove deprecated svelte-add and change it to sv add --- docs/preprocessors/other-css-preprocessors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/preprocessors/other-css-preprocessors.md b/docs/preprocessors/other-css-preprocessors.md index 3ed75a69f..db488452b 100644 --- a/docs/preprocessors/other-css-preprocessors.md +++ b/docs/preprocessors/other-css-preprocessors.md @@ -42,7 +42,7 @@ export default { ## TailwindCSS -We assume you already have setup TailwindCSS within your Svelte project. If not, you can run `npx svelte-add tailwindcss` to set it up automatically or visit [the Tailwind docs](https://tailwindcss.com/docs/guides/sveltekit) which explain how to manually set it up. +We assume you already have setup TailwindCSS within your Svelte project. If not, you can run `npx sv add tailwindcss` to set it up automatically or visit [the Tailwind docs](https://tailwindcss.com/docs/guides/sveltekit) which explain how to manually set it up. To use TailwindCSS with the VSCode extension: From 04d52b7a3f5ca72ca2e89f5d15d283c33813d332 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:39:27 +0800 Subject: [PATCH 31/39] fix: treat a script tag as top-level if it's the first tag in file (#2886) --- .changeset/plenty-moments-grab.md | 5 +++++ .../language-server/src/lib/documents/utils.ts | 9 +++++++++ .../test/lib/documents/utils.test.ts | 16 +++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .changeset/plenty-moments-grab.md diff --git a/.changeset/plenty-moments-grab.md b/.changeset/plenty-moments-grab.md new file mode 100644 index 000000000..91cfd4c42 --- /dev/null +++ b/.changeset/plenty-moments-grab.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +fix: always treat a script tag as top-level if it's the first tag in the file diff --git a/packages/language-server/src/lib/documents/utils.ts b/packages/language-server/src/lib/documents/utils.ts index 374fdb07a..d37987244 100644 --- a/packages/language-server/src/lib/documents/utils.ts +++ b/packages/language-server/src/lib/documents/utils.ts @@ -72,6 +72,15 @@ function extractTags( * If that is BEFORE `{#X`, we are inside a moustache tag. */ function isNotInsideControlFlowTag(tag: Node) { + const tagIndex = rootNodes.indexOf(tag); + // Quick check: if the tag has nothing before it, it can't be inside a control flow tag + // This also works around a case where the tag is treated as under a control flow tag when vscode-html-languageservice parses something wrong + if (tagIndex === 0) { + const startContent = text.substring(0, tag.start); + if (startContent.trim() === '') { + return true; + } + } const nodes = rootNodes.slice(rootNodes.indexOf(tag)); const rootContentAfterTag = nodes .map((node, idx) => { diff --git a/packages/language-server/test/lib/documents/utils.test.ts b/packages/language-server/test/lib/documents/utils.test.ts index a54b8b53e..509212020 100644 --- a/packages/language-server/test/lib/documents/utils.test.ts +++ b/packages/language-server/test/lib/documents/utils.test.ts @@ -50,7 +50,7 @@ describe('document/utils', () => { assert.deepStrictEqual(extractStyleTag(text), null); }); - it('is canse sensitive to style/script', () => { + it('is case sensitive to style/script', () => { const text = ` @@ -344,6 +344,20 @@ describe('document/utils', () => { container: { start: 151, end: 181 } }); }); + + it('extract tag correctly if nothing is before the tag', () => { + const text = ` + {/if}`; + assert.deepStrictEqual(extractScriptTags(text)?.script, { + content: 'let value = 2', + attributes: {}, + start: 8, + end: 21, + startPos: Position.create(0, 8), + endPos: Position.create(0, 21), + container: { start: 0, end: 30 } + }); + }); }); describe('#getLineAtPosition', () => { From 2a31d04b869c2f482471841922a483b4612daeb6 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Tue, 11 Nov 2025 12:49:53 +0800 Subject: [PATCH 32/39] chore: slight optimize forgot to reuse the variable --- packages/language-server/src/lib/documents/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-server/src/lib/documents/utils.ts b/packages/language-server/src/lib/documents/utils.ts index d37987244..b5eebe271 100644 --- a/packages/language-server/src/lib/documents/utils.ts +++ b/packages/language-server/src/lib/documents/utils.ts @@ -81,7 +81,7 @@ function extractTags( return true; } } - const nodes = rootNodes.slice(rootNodes.indexOf(tag)); + const nodes = rootNodes.slice(tagIndex); const rootContentAfterTag = nodes .map((node, idx) => { const start = node.startTagEnd ? node.end : node.start + (node.tag?.length || 0); From 6a8ef1aac4427ae837b537163049c9312504b4b1 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:42:54 +0800 Subject: [PATCH 33/39] fix: use moustache for svelte5 onhandler completion (#2883) --- .changeset/fast-rivers-tan.md | 5 ++++ .../src/plugins/html/HTMLPlugin.ts | 8 ++++-- .../test/plugins/html/HTMLPlugin.test.ts | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 .changeset/fast-rivers-tan.md diff --git a/.changeset/fast-rivers-tan.md b/.changeset/fast-rivers-tan.md new file mode 100644 index 000000000..976ce91e3 --- /dev/null +++ b/.changeset/fast-rivers-tan.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +fix: use moustache for svelte5 onhandler completion diff --git a/packages/language-server/src/plugins/html/HTMLPlugin.ts b/packages/language-server/src/plugins/html/HTMLPlugin.ts index 68e760376..89ed1b72f 100644 --- a/packages/language-server/src/plugins/html/HTMLPlugin.ts +++ b/packages/language-server/src/plugins/html/HTMLPlugin.ts @@ -198,16 +198,18 @@ export class HTMLPlugin return; } - if (item.label.startsWith('on:')) { + if (item.label.startsWith('on')) { + const isLegacyDirective = item.label.startsWith('on:'); + const modifierTabStop = isLegacyDirective ? '$2' : ''; item.textEdit = { ...item.textEdit, newText: item.textEdit.newText.replace( attributeValuePlaceHolder, - `$2=${startQuote}$1${endQuote}` + `${modifierTabStop}=${startQuote}$1${endQuote}` ) }; // In Svelte 5, people should use `onclick` instead of `on:click` - if (document.isSvelte5) { + if (isLegacyDirective && document.isSvelte5) { item.sortText = 'z' + (item.sortText ?? item.label); } } diff --git a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts index eef08bd12..db0b366be 100644 --- a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts +++ b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts @@ -381,4 +381,32 @@ describe('HTML Plugin', () => { } }); }); + + if (!isSvelte5Plus) { + return; + } + + it('provide event handler completions (svelte 5+)', async () => { + const { plugin, document } = setup('
item.label === 'onclick'); + + const expected: CompletionItem = { + label: 'onclick', + kind: CompletionItemKind.Value, + documentation: { + kind: 'markdown', + value: 'A pointing device button has been pressed and released on an element.' + }, + textEdit: TextEdit.replace( + Range.create(Position.create(0, 5), Position.create(0, 7)), + 'onclick={$1}' + ), + insertTextFormat: InsertTextFormat.Snippet, + command: undefined + }; + + assert.deepStrictEqual(onClick, expected); + }); }); From 16d845400e8b9f9d068d01f6eb46c027a1149873 Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Tue, 11 Nov 2025 09:34:51 +0100 Subject: [PATCH 34/39] feat: use machine format when run by Claude Code (#2870) * feat: use machine format when run by Claude Code Fixes #2868 Co-Authored-By: Claude * Change commit type from feat to chore --------- Co-authored-by: Claude Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/old-carrots-rescue.md | 5 +++++ packages/svelte-check/src/options.ts | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/old-carrots-rescue.md diff --git a/.changeset/old-carrots-rescue.md b/.changeset/old-carrots-rescue.md new file mode 100644 index 000000000..5c3f5acad --- /dev/null +++ b/.changeset/old-carrots-rescue.md @@ -0,0 +1,5 @@ +--- +"svelte-check": patch +--- + +chore: use machine format when run by Claude Code diff --git a/packages/svelte-check/src/options.ts b/packages/svelte-check/src/options.ts index b8ceca8f2..a8a347db8 100644 --- a/packages/svelte-check/src/options.ts +++ b/packages/svelte-check/src/options.ts @@ -99,7 +99,14 @@ const outputFormats = ['human', 'human-verbose', 'machine', 'machine-verbose'] a type OutputFormat = (typeof outputFormats)[number]; function getOutputFormat(opts: Record): OutputFormat { - return outputFormats.includes(opts.output) ? opts.output : 'human-verbose'; + if (outputFormats.includes(opts.output)) { + return opts.output; + } else if (process.env.CLAUDECODE === '1') { + // https://github.com/sveltejs/language-tools/issues/2868 + return 'machine'; + } else { + return 'human-verbose'; + } } function getWorkspaceUri(opts: Record) { From 574c34d8064be7e144cb9a51d4827a8af67d3db7 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 11 Nov 2025 09:55:54 +0100 Subject: [PATCH 35/39] chore: turn changeset into patch a minor would be like a major for language-server since we're in 0.x range --- .changeset/wide-emus-turn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wide-emus-turn.md b/.changeset/wide-emus-turn.md index dc805f443..b9762ba16 100644 --- a/.changeset/wide-emus-turn.md +++ b/.changeset/wide-emus-turn.md @@ -1,5 +1,5 @@ --- -'svelte-language-server': minor +'svelte-language-server': patch --- feat: implement 'source.removeUnusedImports' code action From ba9185bdabaaab1ba9c18933554608dac81db55e Mon Sep 17 00:00:00 2001 From: "D. Firmansyah" Date: Tue, 11 Nov 2025 16:17:50 +0700 Subject: [PATCH 36/39] feat: support hierarchical document symbols (#2817) Closes #1519 Return hierarchical DocumentSymbol, useful for breadcrumb feature in various clients. --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen --- .changeset/dull-bees-hang.md | 5 + .changeset/old-carrots-rescue.md | 2 +- .../language-server/src/plugins/PluginHost.ts | 61 +++++++- packages/language-server/src/server.ts | 13 +- .../test/plugins/PluginHost.test.ts | 147 +++++++++++++++++- 5 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 .changeset/dull-bees-hang.md diff --git a/.changeset/dull-bees-hang.md b/.changeset/dull-bees-hang.md new file mode 100644 index 000000000..37aabe47a --- /dev/null +++ b/.changeset/dull-bees-hang.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +feat: support hierarchical document symbols diff --git a/.changeset/old-carrots-rescue.md b/.changeset/old-carrots-rescue.md index 5c3f5acad..4f1567cef 100644 --- a/.changeset/old-carrots-rescue.md +++ b/.changeset/old-carrots-rescue.md @@ -1,5 +1,5 @@ --- -"svelte-check": patch +'svelte-check': patch --- chore: use machine format when run by Claude Code diff --git a/packages/language-server/src/plugins/PluginHost.ts b/packages/language-server/src/plugins/PluginHost.ts index f251582c0..949108808 100644 --- a/packages/language-server/src/plugins/PluginHost.ts +++ b/packages/language-server/src/plugins/PluginHost.ts @@ -35,7 +35,8 @@ import { TextEdit, WorkspaceEdit, InlayHint, - WorkspaceSymbol + WorkspaceSymbol, + DocumentSymbol } from 'vscode-languageserver'; import { DocumentManager, getNodeIfIsInHTMLStartTag } from '../lib/documents'; import { Logger } from '../logger'; @@ -307,6 +308,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { if (cancellationToken.isCancellationRequested) { return []; } + return flatten( await this.execute( 'getDocumentSymbols', @@ -317,6 +319,63 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { ); } + private comparePosition(pos1: Position, pos2: Position) { + if (pos1.line < pos2.line) return -1; + if (pos1.line > pos2.line) return 1; + if (pos1.character < pos2.character) return -1; + if (pos1.character > pos2.character) return 1; + return 0; + } + + private rangeContains(parent: Range, child: Range) { + return ( + this.comparePosition(parent.start, child.start) <= 0 && + this.comparePosition(child.end, parent.end) <= 0 + ); + } + + async getHierarchicalDocumentSymbols( + textDocument: TextDocumentIdentifier, + cancellationToken: CancellationToken + ): Promise { + const flat = await this.getDocumentSymbols(textDocument, cancellationToken); + const symbols = flat + .map((s) => + DocumentSymbol.create( + s.name, + undefined, + s.kind, + s.location.range, + s.location.range, + [] + ) + ) + .sort((a, b) => { + const start = this.comparePosition(a.range.start, b.range.start); + if (start !== 0) return start; + return this.comparePosition(b.range.end, a.range.end); + }); + + const stack: DocumentSymbol[] = []; + const roots: DocumentSymbol[] = []; + + for (const node of symbols) { + while (stack.length > 0 && !this.rangeContains(stack.at(-1)!.range, node.range)) { + stack.pop(); + } + + if (stack.length > 0) { + stack.at(-1)!.children!.push(node); + } else { + roots.push(node); + } + + stack.push(node); + } + + return roots; + } + async getDefinitions( textDocument: TextDocumentIdentifier, position: Position diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index b3fbf6853..bc00206bd 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -429,9 +429,16 @@ export function startServer(options?: LSOptions) { connection.onColorPresentation((evt) => pluginHost.getColorPresentations(evt.textDocument, evt.range, evt.color) ); - connection.onDocumentSymbol((evt, cancellationToken) => - pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken) - ); + connection.onDocumentSymbol((evt, cancellationToken) => { + if ( + configManager.getClientCapabilities()?.textDocument?.documentSymbol + ?.hierarchicalDocumentSymbolSupport + ) { + return pluginHost.getHierarchicalDocumentSymbols(evt.textDocument, cancellationToken); + } else { + return pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken); + } + }); connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position)); connection.onReferences((evt, cancellationToken) => pluginHost.findReferences(evt.textDocument, evt.position, evt.context, cancellationToken) diff --git a/packages/language-server/test/plugins/PluginHost.test.ts b/packages/language-server/test/plugins/PluginHost.test.ts index c7b1807c5..ed4fa7806 100644 --- a/packages/language-server/test/plugins/PluginHost.test.ts +++ b/packages/language-server/test/plugins/PluginHost.test.ts @@ -1,15 +1,18 @@ import sinon from 'sinon'; import { CompletionItem, + DocumentSymbol, Location, LocationLink, Position, Range, + SymbolInformation, + SymbolKind, TextDocumentItem } from 'vscode-languageserver-types'; import { DocumentManager, Document } from '../../src/lib/documents'; import { LSPProviderConfig, PluginHost } from '../../src/plugins'; -import { CompletionTriggerKind } from 'vscode-languageserver'; +import { CompletionTriggerKind, CancellationToken } from 'vscode-languageserver'; import assert from 'assert'; describe('PluginHost', () => { @@ -187,4 +190,146 @@ describe('PluginHost', () => { ]); }); }); + + describe('getHierarchicalDocumentSymbols', () => { + it('converts flat symbols to hierarchical structure', async () => { + const cancellation_token: CancellationToken = { + isCancellationRequested: false, + onCancellationRequested: () => ({ dispose: () => {} }) + }; + + const flat_symbols: SymbolInformation[] = [ + // Root level class (lines 0-10) + SymbolInformation.create( + 'MyClass', + SymbolKind.Class, + Range.create(Position.create(0, 0), Position.create(10, 0)), + 'file:///hello.svelte' + ), + // Method inside class (lines 1-5) + SymbolInformation.create( + 'myMethod', + SymbolKind.Method, + Range.create(Position.create(1, 0), Position.create(5, 0)), + 'file:///hello.svelte' + ), + // Variable inside method (lines 2-3) + SymbolInformation.create( + 'localVar', + SymbolKind.Variable, + Range.create(Position.create(2, 0), Position.create(3, 0)), + 'file:///hello.svelte' + ), + // Another method in class (lines 6-8) + SymbolInformation.create( + 'anotherMethod', + SymbolKind.Method, + Range.create(Position.create(6, 0), Position.create(8, 0)), + 'file:///hello.svelte' + ), + // Root level function (lines 12-15) + SymbolInformation.create( + 'topLevelFunction', + SymbolKind.Function, + Range.create(Position.create(12, 0), Position.create(15, 0)), + 'file:///hello.svelte' + ) + ]; + + const { docManager, pluginHost } = setup({ + getDocumentSymbols: sinon.stub().returns(flat_symbols) + }); + docManager.openClientDocument(textDocument); + + const result = await pluginHost.getHierarchicalDocumentSymbols( + textDocument, + cancellation_token + ); + + // Should have 2 root symbols: MyClass and topLevelFunction + assert.strictEqual(result.length, 2); + + // Check first root symbol (MyClass) + assert.strictEqual(result[0].name, 'MyClass'); + assert.strictEqual(result[0].kind, SymbolKind.Class); + assert.strictEqual(result[0].children?.length, 2); + + // Check children of MyClass + assert.strictEqual(result[0].children![0].name, 'myMethod'); + assert.strictEqual(result[0].children![0].kind, SymbolKind.Method); + assert.strictEqual(result[0].children![0].children?.length, 1); + + // Check nested child (localVar inside myMethod) + assert.strictEqual(result[0].children![0].children![0].name, 'localVar'); + assert.strictEqual(result[0].children![0].children![0].kind, SymbolKind.Variable); + assert.strictEqual(result[0].children![0].children![0].children?.length, 0); + + // Check second child of MyClass + assert.strictEqual(result[0].children![1].name, 'anotherMethod'); + assert.strictEqual(result[0].children![1].kind, SymbolKind.Method); + assert.strictEqual(result[0].children![1].children?.length, 0); + + // Check second root symbol (topLevelFunction) + assert.strictEqual(result[1].name, 'topLevelFunction'); + assert.strictEqual(result[1].kind, SymbolKind.Function); + assert.strictEqual(result[1].children?.length, 0); + }); + + it('handles empty symbol list', async () => { + const cancellation_token: CancellationToken = { + isCancellationRequested: false, + onCancellationRequested: () => ({ dispose: () => {} }) + }; + + const { docManager, pluginHost } = setup({ + getDocumentSymbols: sinon.stub().returns([]) + }); + docManager.openClientDocument(textDocument); + + const result = await pluginHost.getHierarchicalDocumentSymbols( + textDocument, + cancellation_token + ); + + assert.deepStrictEqual(result, []); + }); + + it('handles symbols with same start position', async () => { + const cancellation_token: CancellationToken = { + isCancellationRequested: false, + onCancellationRequested: () => ({ dispose: () => {} }) + }; + + const flat_symbols: SymbolInformation[] = [ + // Two symbols starting at same position, longer one should be parent + SymbolInformation.create( + 'outer', + SymbolKind.Class, + Range.create(Position.create(0, 0), Position.create(10, 0)), + 'file:///hello.svelte' + ), + SymbolInformation.create( + 'inner', + SymbolKind.Method, + Range.create(Position.create(0, 0), Position.create(5, 0)), + 'file:///hello.svelte' + ) + ]; + + const { docManager, pluginHost } = setup({ + getDocumentSymbols: sinon.stub().returns(flat_symbols) + }); + docManager.openClientDocument(textDocument); + + const result = await pluginHost.getHierarchicalDocumentSymbols( + textDocument, + cancellation_token + ); + + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].name, 'outer'); + assert.strictEqual(result[0].children?.length, 1); + assert.strictEqual(result[0].children![0].name, 'inner'); + }); + }); }); From 654692354292fadfbcdec9d72e551b423dd2bfd8 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 11 Nov 2025 19:10:35 +0800 Subject: [PATCH 37/39] feat: quick fix for adding lang="ts" (#2882) #2876 --- .changeset/four-papers-learn.md | 5 + .../features/CodeActionsProvider.ts | 217 +++++++++--------- .../features/CodeActionsProvider.test.ts | 121 ++++++++++ .../codeaction-add-lang-ts-no-script.svelte | 1 + .../codeaction-add-lang-ts.svelte | 7 + packages/svelte2tsx/package.json | 2 +- 6 files changed, 245 insertions(+), 108 deletions(-) create mode 100644 .changeset/four-papers-learn.md create mode 100644 packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts-no-script.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts.svelte diff --git a/.changeset/four-papers-learn.md b/.changeset/four-papers-learn.md new file mode 100644 index 000000000..f5838bc91 --- /dev/null +++ b/.changeset/four-papers-learn.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +feat: quick fix for adding lang="ts" diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 693c2581a..d7624cdf5 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -59,6 +59,7 @@ import { isTextSpanInGeneratedCode, SnapshotMap } from './utils'; +import { Node } from 'vscode-html-languageservice'; /** * TODO change this to protocol constant if it's part of the protocol @@ -701,10 +702,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ), ...this.getSvelteQuickFixes( lang, - document, cannotFindNameDiagnostic, tsDoc, - formatCodeBasis, userPreferences, formatCodeSettings ) @@ -760,8 +759,18 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { lang ); + const addLangCodeAction = this.getAddLangTSCodeAction( + document, + context, + tsDoc, + formatCodeBasis + ); + // filter out empty code action - return codeActionsNotFilteredOut.map(({ codeAction }) => codeAction).concat(fixAllActions); + const result = codeActionsNotFilteredOut + .map(({ codeAction }) => codeAction) + .concat(fixAllActions); + return addLangCodeAction ? [addLangCodeAction].concat(result) : result; } private async convertAndFixCodeFixAction({ @@ -1128,10 +1137,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { private getSvelteQuickFixes( lang: ts.LanguageService, - document: Document, cannotFindNameDiagnostics: Diagnostic[], tsDoc: DocumentSnapshot, - formatCodeBasis: FormatCodeBasis, userPreferences: ts.UserPreferences, formatCodeSettings: ts.FormatCodeSettings ): CustomFixCannotFindNameInfo[] { @@ -1141,14 +1148,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return []; } - const typeChecker = program.getTypeChecker(); const results: CustomFixCannotFindNameInfo[] = []; - const quote = getQuotePreference(sourceFile, userPreferences); const getGlobalCompletion = memoize(() => lang.getCompletionsAtPosition(tsDoc.filePath, 0, userPreferences, formatCodeSettings) ); - const [tsMajorStr] = ts.version.split('.'); - const tsSupportHandlerQuickFix = parseInt(tsMajorStr) >= 5; for (const diagnostic of cannotFindNameDiagnostics) { const identifier = this.findIdentifierForDiagnostic(tsDoc, diagnostic, sourceFile); @@ -1173,24 +1176,6 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ); } - if (!tsSupportHandlerQuickFix) { - const isQuickFixTargetEventHandler = this.isQuickFixForEventHandler( - document, - diagnostic - ); - if (isQuickFixTargetEventHandler) { - fixes.push( - ...this.getEventHandlerQuickFixes( - identifier, - tsDoc, - typeChecker, - quote, - formatCodeBasis - ) - ); - } - } - if (!fixes.length) { continue; } @@ -1225,8 +1210,6 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return identifier; } - // TODO: Remove this in late 2023 - // when most users have upgraded to TS 5.0+ private getSvelteStoreQuickFixes( identifier: ts.Identifier, lang: ts.LanguageService, @@ -1275,101 +1258,121 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return flatten(completion.entries.filter((c) => c.name === storeIdentifier).map(toFix)); } - /** - * Workaround for TypeScript doesn't provide a quick fix if the signature is typed as union type, like `(() => void) | null` - * We can remove this once TypeScript doesn't have this limitation. - */ - private getEventHandlerQuickFixes( - identifier: ts.Identifier, + private getAddLangTSCodeAction( + document: Document, + context: CodeActionContext, tsDoc: DocumentSnapshot, - typeChecker: ts.TypeChecker, - quote: string, formatCodeBasis: FormatCodeBasis - ): ts.CodeFixAction[] { - const type = identifier && typeChecker.getContextualType(identifier); + ) { + if (tsDoc.scriptKind !== ts.ScriptKind.JS) { + return; + } - // if it's not union typescript should be able to do it. no need to enhance - if (!type || !type.isUnion()) { - return []; + let hasTSOnlyDiagnostic = false; + for (const diagnostic of context.diagnostics) { + const num = Number(diagnostic.code); + const canOnlyBeUsedInTS = num >= 8004 && num <= 8017; + if (canOnlyBeUsedInTS) { + hasTSOnlyDiagnostic = true; + break; + } + } + if (!hasTSOnlyDiagnostic) { + return; } - const nonNullable = type.getNonNullableType(); + if (!document.scriptInfo && !document.moduleScriptInfo) { + const hasNonTopLevelLang = document.html.roots.some((node) => + this.hasLangTsScriptTag(node) + ); + // Might be because issue with parsing the script tag, so don't suggest adding a new one + if (hasNonTopLevelLang) { + return; + } - if ( - !( - nonNullable.flags & ts.TypeFlags.Object && - (nonNullable as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous - ) - ) { - return []; + return CodeAction.create( + 'Add ' + formatCodeBasis.newLine + } + ] + } + ] + }, + CodeActionKind.QuickFix + ); } - const signature = typeChecker.getSignaturesOfType(nonNullable, ts.SignatureKind.Call)[0]; + const edits = [document.scriptInfo, document.moduleScriptInfo] + .map((info) => { + if (!info) { + return; + } - const parameters = signature.parameters.map((p) => { - const declaration = p.valueDeclaration ?? p.declarations?.[0]; - const typeString = declaration - ? typeChecker.typeToString(typeChecker.getTypeOfSymbolAtLocation(p, declaration)) - : ''; + const startTagNameEnd = document.positionAt(info.container.start + 7); // \n', + range: { + start: { + character: 0, + line: 0 + }, + end: { + character: 0, + line: 0 + } + } + } + ], + textDocument: { + uri: getUri('codeaction-add-lang-ts-no-script.svelte'), + version: null + } + } + ] + } + } + ]); + }); }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts-no-script.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts-no-script.svelte new file mode 100644 index 000000000..3a462a520 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts-no-script.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts.svelte new file mode 100644 index 000000000..0c39d5de6 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-lang-ts.svelte @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 15b242f11..833c8e36a 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -50,7 +50,7 @@ "build": "rollup -c", "prepublishOnly": "npm run build", "dev": "rollup -c -w", - "test": "mocha test/test.ts" + "test": "mocha test/test.ts --no-experimental-strip-types" }, "files": [ "index.mjs", From 6f08d6665ebe1a6a1f86c1a405894ce9bec3b1ec Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 11 Nov 2025 19:11:06 +0800 Subject: [PATCH 38/39] fix: support experimental feature in "Show compiled Code" (#2884) #2857 --- .changeset/wet-lamps-dress.md | 5 +++++ .../language-server/src/plugins/svelte/SveltePlugin.ts | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changeset/wet-lamps-dress.md diff --git a/.changeset/wet-lamps-dress.md b/.changeset/wet-lamps-dress.md new file mode 100644 index 000000000..7bcb03fc7 --- /dev/null +++ b/.changeset/wet-lamps-dress.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +fix: support experimental feature in "Show compiled Code" diff --git a/packages/language-server/src/plugins/svelte/SveltePlugin.ts b/packages/language-server/src/plugins/svelte/SveltePlugin.ts index 70e00e9a0..3ac797f4d 100644 --- a/packages/language-server/src/plugins/svelte/SveltePlugin.ts +++ b/packages/language-server/src/plugins/svelte/SveltePlugin.ts @@ -110,8 +110,13 @@ export class SveltePlugin async getCompiledResult(document: Document): Promise { try { const svelteDoc = await this.getSvelteDoc(document); - // @ts-ignore is 'client' in Svelte 5 - return svelteDoc.getCompiledWith({ generate: 'dom' }); + const options: any = { generate: 'dom' }; // 'client' in Svelte 5 + // @ts-ignore Svelte 5 only; we gotta write it like this else Svelte 4 fails on unknown key + if (document.config?.compilerOptions?.experimental) { + // @ts-ignore Svelte 5 only + options.experimental = document.config.compilerOptions.experimental; + } + return await svelteDoc.getCompiledWith(options); } catch (error) { return null; } From 253b87213c330061fab1279b82241e728746db82 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 12:12:25 +0100 Subject: [PATCH 39/39] Version Packages (#2869) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/dull-bees-hang.md | 5 ----- .changeset/fast-rivers-tan.md | 5 ----- .changeset/four-papers-learn.md | 5 ----- .changeset/kind-carrots-pull.md | 5 ----- .changeset/old-carrots-rescue.md | 5 ----- .changeset/plenty-moments-grab.md | 5 ----- .changeset/silent-cases-sing.md | 5 ----- .changeset/wet-lamps-dress.md | 5 ----- .changeset/wide-emus-turn.md | 5 ----- packages/language-server/CHANGELOG.md | 20 ++++++++++++++++++++ packages/language-server/package.json | 2 +- packages/svelte-check/CHANGELOG.md | 6 ++++++ packages/svelte-check/package.json | 2 +- 13 files changed, 28 insertions(+), 47 deletions(-) delete mode 100644 .changeset/dull-bees-hang.md delete mode 100644 .changeset/fast-rivers-tan.md delete mode 100644 .changeset/four-papers-learn.md delete mode 100644 .changeset/kind-carrots-pull.md delete mode 100644 .changeset/old-carrots-rescue.md delete mode 100644 .changeset/plenty-moments-grab.md delete mode 100644 .changeset/silent-cases-sing.md delete mode 100644 .changeset/wet-lamps-dress.md delete mode 100644 .changeset/wide-emus-turn.md diff --git a/.changeset/dull-bees-hang.md b/.changeset/dull-bees-hang.md deleted file mode 100644 index 37aabe47a..000000000 --- a/.changeset/dull-bees-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -feat: support hierarchical document symbols diff --git a/.changeset/fast-rivers-tan.md b/.changeset/fast-rivers-tan.md deleted file mode 100644 index 976ce91e3..000000000 --- a/.changeset/fast-rivers-tan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -fix: use moustache for svelte5 onhandler completion diff --git a/.changeset/four-papers-learn.md b/.changeset/four-papers-learn.md deleted file mode 100644 index f5838bc91..000000000 --- a/.changeset/four-papers-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -feat: quick fix for adding lang="ts" diff --git a/.changeset/kind-carrots-pull.md b/.changeset/kind-carrots-pull.md deleted file mode 100644 index 8b779b1e7..000000000 --- a/.changeset/kind-carrots-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -fix: support for @nativescript-community/svelte-native diff --git a/.changeset/old-carrots-rescue.md b/.changeset/old-carrots-rescue.md deleted file mode 100644 index 4f1567cef..000000000 --- a/.changeset/old-carrots-rescue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-check': patch ---- - -chore: use machine format when run by Claude Code diff --git a/.changeset/plenty-moments-grab.md b/.changeset/plenty-moments-grab.md deleted file mode 100644 index 91cfd4c42..000000000 --- a/.changeset/plenty-moments-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -fix: always treat a script tag as top-level if it's the first tag in the file diff --git a/.changeset/silent-cases-sing.md b/.changeset/silent-cases-sing.md deleted file mode 100644 index 9d6eab83b..000000000 --- a/.changeset/silent-cases-sing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -fix: restrict emmet completion with emmet specific triggerCharacter diff --git a/.changeset/wet-lamps-dress.md b/.changeset/wet-lamps-dress.md deleted file mode 100644 index 7bcb03fc7..000000000 --- a/.changeset/wet-lamps-dress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -fix: support experimental feature in "Show compiled Code" diff --git a/.changeset/wide-emus-turn.md b/.changeset/wide-emus-turn.md deleted file mode 100644 index b9762ba16..000000000 --- a/.changeset/wide-emus-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte-language-server': patch ---- - -feat: implement 'source.removeUnusedImports' code action diff --git a/packages/language-server/CHANGELOG.md b/packages/language-server/CHANGELOG.md index b4b93fc3f..1d6b3a472 100644 --- a/packages/language-server/CHANGELOG.md +++ b/packages/language-server/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.17.22 + +### Patch Changes + +- feat: support hierarchical document symbols ([#2817](https://github.com/sveltejs/language-tools/pull/2817)) + +- fix: use moustache for svelte5 onhandler completion ([#2883](https://github.com/sveltejs/language-tools/pull/2883)) + +- feat: quick fix for adding lang="ts" ([#2882](https://github.com/sveltejs/language-tools/pull/2882)) + +- fix: support for @nativescript-community/svelte-native ([#2867](https://github.com/sveltejs/language-tools/pull/2867)) + +- fix: always treat a script tag as top-level if it's the first tag in the file ([#2886](https://github.com/sveltejs/language-tools/pull/2886)) + +- fix: restrict emmet completion with emmet specific triggerCharacter ([#2873](https://github.com/sveltejs/language-tools/pull/2873)) + +- fix: support experimental feature in "Show compiled Code" ([#2884](https://github.com/sveltejs/language-tools/pull/2884)) + +- feat: implement 'source.removeUnusedImports' code action ([#2875](https://github.com/sveltejs/language-tools/pull/2875)) + ## 0.17.21 ### Patch Changes diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 7411f4d73..174e556e5 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,6 +1,6 @@ { "name": "svelte-language-server", - "version": "0.17.21", + "version": "0.17.22", "description": "A language server for Svelte", "main": "dist/src/index.js", "typings": "dist/src/index", diff --git a/packages/svelte-check/CHANGELOG.md b/packages/svelte-check/CHANGELOG.md index 7e0e6ec60..1cdfc9de8 100644 --- a/packages/svelte-check/CHANGELOG.md +++ b/packages/svelte-check/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 4.3.4 + +### Patch Changes + +- chore: use machine format when run by Claude Code ([#2870](https://github.com/sveltejs/language-tools/pull/2870)) + ## 4.3.3 ### Patch Changes diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index e7aa84255..38ec1a73e 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -1,7 +1,7 @@ { "name": "svelte-check", "description": "Svelte Code Checker Terminal Interface", - "version": "4.3.3", + "version": "4.3.4", "main": "./dist/src/index.js", "bin": "./bin/svelte-check", "author": "The Svelte Community",