From 73125a6fd698a76fa13b58ec735ce242cdbdd094 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 18 Sep 2024 14:54:27 +0200 Subject: [PATCH 1/9] fix: silence type error in old d.ts files svelte-jsx and svelte-shims are used for Svelte 3 only, and are likely out of date, yet we don't want to invest time into adjusting them anymore, therefore silence any type errors in it. Helps with #2498 --- packages/svelte2tsx/svelte-jsx.d.ts | 2 ++ packages/svelte2tsx/svelte-shims.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/svelte2tsx/svelte-jsx.d.ts b/packages/svelte2tsx/svelte-jsx.d.ts index 68307ae47..5c576f715 100644 --- a/packages/svelte2tsx/svelte-jsx.d.ts +++ b/packages/svelte2tsx/svelte-jsx.d.ts @@ -1,4 +1,6 @@ /// +// @ts-nocheck +// nocheck because we don't want to adjust this anymore (only used for Svelte 3) declare namespace svelteHTML { diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts index 3b96603e2..b7be2cbcc 100644 --- a/packages/svelte2tsx/svelte-shims.d.ts +++ b/packages/svelte2tsx/svelte-shims.d.ts @@ -1,3 +1,5 @@ +// @ts-nocheck +// nocheck because we don't want to adjust this anymore (only used for Svelte 3) // Whenever a ambient declaration changes, its number should be increased // This way, we avoid the situation where multiple ambient versions of svelte2tsx // are loaded and their declarations conflict each other From 94a7352d96e0a7d512cde0de1e73ebb25347b7c7 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Wed, 18 Sep 2024 22:45:59 +0800 Subject: [PATCH 2/9] fix: include files indirectly belonging to a project into correct project (#2488) Fixes #2486 Fixes #2485 --- .../plugins/typescript/DocumentSnapshot.ts | 7 +- .../src/plugins/typescript/service.ts | 100 ++++++++++++++---- packages/language-server/src/svelte-check.ts | 28 +++-- .../test/plugins/typescript/service.test.ts | 96 ++++++++++++++++- .../different-ts-service/tsconfig.json | 1 + 5 files changed, 194 insertions(+), 38 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index 00c902554..aaf2a11e6 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -109,7 +109,7 @@ export namespace DocumentSnapshot { tsSystem: ts.System ) { if (isSvelteFilePath(filePath)) { - return DocumentSnapshot.fromSvelteFilePath(filePath, createDocument, options); + return DocumentSnapshot.fromSvelteFilePath(filePath, createDocument, options, tsSystem); } else { return DocumentSnapshot.fromNonSvelteFilePath(filePath, tsSystem); } @@ -173,9 +173,10 @@ export namespace DocumentSnapshot { export function fromSvelteFilePath( filePath: string, createDocument: (filePath: string, text: string) => Document, - options: SvelteSnapshotOptions + options: SvelteSnapshotOptions, + tsSystem: ts.System ) { - const originalText = ts.sys.readFile(filePath) ?? ''; + const originalText = tsSystem.readFile(filePath) ?? ''; return fromDocument(createDocument(filePath, originalText), options); } } diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 8f5720bb0..f1b70aa09 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -101,6 +101,10 @@ export interface TsConfigInfo { extendedConfigPaths?: Set; } +enum TsconfigSvelteDiagnostics { + NO_SVELTE_INPUT = 100_001 +} + const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; // 20 MB const services = new FileMap>(); const serviceSizeMap = new FileMap(); @@ -173,12 +177,23 @@ export async function getService( return service; } + // First try to find a service whose includes config matches our file const defaultService = await findDefaultServiceForFile(service, triedTsConfig); if (defaultService) { configFileForOpenFiles.set(path, defaultService.tsconfigPath); return defaultService; } + // If no such service found, see if the file is part of any existing service indirectly. + // This can happen if the includes doesn't match the file but it was imported from one of the included files. + for (const configPath of triedTsConfig) { + const service = await getConfiguredService(configPath); + const ls = service.getService(); + if (ls.getProgram()?.getSourceFile(path)) { + return service; + } + } + tsconfigPath = ''; } @@ -217,6 +232,8 @@ export async function getService( return; } + triedTsConfig.add(service.tsconfigPath); + // TODO: maybe add support for ts 5.6's ancestor searching return findDefaultFromProjectReferences(service, triedTsConfig); } @@ -315,6 +332,8 @@ async function createLanguageService( const projectConfig = getParsedConfig(); const { options: compilerOptions, raw, errors: configErrors } = projectConfig; + const allowJs = compilerOptions.allowJs ?? !!compilerOptions.checkJs; + const virtualDocuments = new FileMap(tsSystem.useCaseSensitiveFileNames); const getCanonicalFileName = createGetCanonicalFileName(tsSystem.useCaseSensitiveFileNames); watchWildCardDirectories(projectConfig); @@ -360,6 +379,7 @@ async function createLanguageService( let languageServiceReducedMode = false; let projectVersion = 0; let dirty = projectConfig.fileNames.length > 0; + let skipSvelteInputCheck = !tsconfigPath; const host: ts.LanguageServiceHost = { log: (message) => Logger.debug(`[ts] ${message}`), @@ -529,12 +549,19 @@ async function createLanguageService( return prevSnapshot; } + const newSnapshot = DocumentSnapshot.fromDocument(document, transformationConfig); + if (!prevSnapshot) { svelteModuleLoader.deleteUnresolvedResolutionsFromCache(filePath); + if (configFileForOpenFiles.get(filePath) === '' && services.size > 1) { + configFileForOpenFiles.delete(filePath); + } + } else if (prevSnapshot.scriptKind !== newSnapshot.scriptKind && !allowJs) { + // if allowJs is false, we need to invalid the cache so that js svelte files can be loaded through module resolution + svelteModuleLoader.deleteFromModuleCache(filePath); + configFileForOpenFiles.delete(filePath); } - const newSnapshot = DocumentSnapshot.fromDocument(document, transformationConfig); - snapshotManager.set(filePath, newSnapshot); return newSnapshot; @@ -640,14 +667,22 @@ async function createLanguageService( : snapshotManager.getProjectFileNames(); const canonicalProjectFileNames = new Set(projectFiles.map(getCanonicalFileName)); + // We only assign project files (i.e. those found through includes config) and virtual files to getScriptFileNames. + // We don't to include other client files otherwise they stay in the program and are never removed + const clientFiles = tsconfigPath + ? Array.from(virtualDocuments.values()) + .map((v) => v.getFilePath()) + .filter(isNotNullOrUndefined) + : snapshotManager.getClientFileNames(); + return Array.from( new Set([ ...projectFiles, // project file is read from the file system so it's more likely to have // the correct casing - ...snapshotManager - .getClientFileNames() - .filter((file) => !canonicalProjectFileNames.has(getCanonicalFileName(file))), + ...clientFiles.filter( + (file) => !canonicalProjectFileNames.has(getCanonicalFileName(file)) + ), ...svelteTsxFiles ]) ); @@ -736,20 +771,6 @@ async function createLanguageService( } } - const svelteConfigDiagnostics = checkSvelteInput(parsedConfig); - if (svelteConfigDiagnostics.length > 0) { - docContext.reportConfigError?.({ - uri: pathToUrl(tsconfigPath), - diagnostics: svelteConfigDiagnostics.map((d) => ({ - message: d.messageText as string, - range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, - severity: ts.DiagnosticCategory.Error, - source: 'svelte' - })) - }); - parsedConfig.errors.push(...svelteConfigDiagnostics); - } - return { ...parsedConfig, fileNames: parsedConfig.fileNames.map(normalizePath), @@ -758,22 +779,32 @@ async function createLanguageService( }; } - function checkSvelteInput(config: ts.ParsedCommandLine) { + function checkSvelteInput(program: ts.Program | undefined, config: ts.ParsedCommandLine) { if (!tsconfigPath || config.raw.references || config.raw.files) { return []; } - const svelteFiles = config.fileNames.filter(isSvelteFilePath); - if (svelteFiles.length > 0) { + const configFileName = basename(tsconfigPath); + // Only report to possible nearest config file since referenced project might not be a svelte project + if (configFileName !== 'tsconfig.json' && configFileName !== 'jsconfig.json') { + return []; + } + + const hasSvelteFiles = + config.fileNames.some(isSvelteFilePath) || + program?.getSourceFiles().some((file) => isSvelteFilePath(file.fileName)); + + if (hasSvelteFiles) { return []; } + const { include, exclude } = config.raw; const inputText = JSON.stringify(include); const excludeText = JSON.stringify(exclude); const svelteConfigDiagnostics: ts.Diagnostic[] = [ { category: ts.DiagnosticCategory.Error, - code: 0, + code: TsconfigSvelteDiagnostics.NO_SVELTE_INPUT, file: undefined, start: undefined, length: undefined, @@ -933,6 +964,28 @@ async function createLanguageService( dirty = false; compilerHost = undefined; + if (!skipSvelteInputCheck) { + const svelteConfigDiagnostics = checkSvelteInput(program, projectConfig); + const codes = svelteConfigDiagnostics.map((d) => d.code); + if (!svelteConfigDiagnostics.length) { + // stop checking once it passed once + skipSvelteInputCheck = true; + } + // report even if empty to clear previous diagnostics + docContext.reportConfigError?.({ + uri: pathToUrl(tsconfigPath), + diagnostics: svelteConfigDiagnostics.map((d) => ({ + message: d.messageText as string, + range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, + severity: ts.DiagnosticCategory.Error, + source: 'svelte' + })) + }); + projectConfig.errors = projectConfig.errors + .filter((e) => !codes.includes(e.code)) + .concat(svelteConfigDiagnostics); + } + // https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624 // host.getCachedExportInfoMap will create the cache if it doesn't exist // so we need to check the property instead @@ -1135,6 +1188,7 @@ async function createLanguageService( if (!filePath) { return; } + virtualDocuments.set(filePath, document); configFileForOpenFiles.set(filePath, tsconfigPath || workspacePath); updateSnapshot(document); scheduleUpdate(filePath); diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 652f2d7a7..2d3e11ff3 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -209,19 +209,14 @@ export class SvelteCheck { }; if (lsContainer.configErrors.length > 0) { - const grouped = groupBy( - lsContainer.configErrors, - (error) => error.file?.fileName ?? tsconfigPath - ); - - return Object.entries(grouped).map(([filePath, errors]) => ({ - filePath, - text: '', - diagnostics: errors.map((diagnostic) => map(diagnostic)) - })); + return reportConfigError(); } const lang = lsContainer.getService(); + if (lsContainer.configErrors.length > 0) { + return reportConfigError(); + } + const files = lang.getProgram()?.getSourceFiles() || []; const options = lang.getProgram()?.getCompilerOptions() || {}; @@ -318,6 +313,19 @@ export class SvelteCheck { } }) ); + + function reportConfigError() { + const grouped = groupBy( + lsContainer.configErrors, + (error) => error.file?.fileName ?? tsconfigPath + ); + + return Object.entries(grouped).map(([filePath, errors]) => ({ + filePath, + text: '', + diagnostics: errors.map((diagnostic) => map(diagnostic)) + })); + } } private async getDiagnosticsForFile(uri: string) { diff --git a/packages/language-server/test/plugins/typescript/service.test.ts b/packages/language-server/test/plugins/typescript/service.test.ts index 50c793c6f..71db7f064 100644 --- a/packages/language-server/test/plugins/typescript/service.test.ts +++ b/packages/language-server/test/plugins/typescript/service.test.ts @@ -108,7 +108,46 @@ describe('service', () => { assert.ok(called); }); - it('do not errors if referenced tsconfig matches no svelte files', async () => { + it('does not report no svelte files when loaded through import', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + virtualSystem.readDirectory = () => [path.join(dirPath, 'random.ts')]; + + virtualSystem.writeFile( + path.join(dirPath, 'tsconfig.json'), + JSON.stringify({ + include: ['**/*.ts'] + }) + ); + + virtualSystem.writeFile( + path.join(dirPath, 'random.svelte'), + '' + ); + + virtualSystem.writeFile( + path.join(dirPath, 'random.ts'), + 'import {} from "./random.svelte"' + ); + + let called = false; + const service = await getService(path.join(dirPath, 'random.svelte'), rootUris, { + ...lsDocumentContext, + reportConfigError: (message) => { + called = true; + assert.deepStrictEqual([], message.diagnostics); + } + }); + + assert.equal( + normalizePath(path.join(dirPath, 'tsconfig.json')), + normalizePath(service.tsconfigPath) + ); + assert.ok(called); + }); + + it('does not errors if referenced tsconfig matches no svelte files', async () => { const dirPath = getRandomVirtualDirPath(testDir); const { virtualSystem, lsDocumentContext, rootUris } = setup(); @@ -565,10 +604,63 @@ describe('service', () => { sinon.assert.calledWith(watchDirectory.firstCall, []); }); + it('assigns files to service with the file in the program', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + const tsconfigPath = path.join(dirPath, 'tsconfig.json'); + virtualSystem.writeFile( + tsconfigPath, + JSON.stringify({ + compilerOptions: { + noImplicitOverride: true + }, + include: ['src/*.ts'] + }) + ); + + const referencedFile = path.join(dirPath, 'anotherPackage', 'index.svelte'); + const tsFilePath = path.join(dirPath, 'src', 'random.ts'); + + virtualSystem.readDirectory = () => [tsFilePath]; + virtualSystem.writeFile( + referencedFile, + '' + ); + virtualSystem.writeFile(tsFilePath, 'import "../anotherPackage/index.svelte";'); + + const document = new Document( + pathToUrl(referencedFile), + virtualSystem.readFile(referencedFile)! + ); + document.openedByClient = true; + const ls = await getService(referencedFile, rootUris, lsDocumentContext); + ls.updateSnapshot(document); + + assert.equal(normalizePath(ls.tsconfigPath), normalizePath(tsconfigPath)); + + const noImplicitOverrideErrorCode = 4114; + const findError = (ls: LanguageServiceContainer) => + ls + .getService() + .getSemanticDiagnostics(referencedFile) + .find((f) => f.code === noImplicitOverrideErrorCode); + + assert.ok(findError(ls)); + + virtualSystem.writeFile(tsFilePath, ''); + ls.updateTsOrJsFile(tsFilePath); + + const ls2 = await getService(referencedFile, rootUris, lsDocumentContext); + ls2.updateSnapshot(document); + + assert.deepStrictEqual(findError(ls2), undefined); + }); + function getSemanticDiagnosticsMessages(ls: LanguageServiceContainer, filePath: string) { return ls .getService() .getSemanticDiagnostics(filePath) - .map((d) => d.messageText); + .map((d) => ts.flattenDiagnosticMessageText(d.messageText, '\n')); } }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json index 6d3385d79..04d6ed3d5 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "allowJs": true, /** This is actually not needed, but makes the tests faster because TS does not look up other types. From e132b5667a87a7d233adef795b3e99ecc85e6aad Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:48:20 +0200 Subject: [PATCH 3/9] fix: make no Svelte files found a warning (#2507) There were reports that this was too overzealous as some people use this potentially not knowing whether or not this is a Svelte-projects, too. Therefore only issue a warning instead of an error. --- .../src/plugins/typescript/service.ts | 8 +++++--- packages/language-server/src/svelte-check.ts | 16 +++++++++++++--- .../svelte2tsx/processInstanceScriptContent.ts | 8 +++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index f1b70aa09..0e0d0f27a 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -1,6 +1,7 @@ import { dirname, join, resolve, basename } from 'path'; import ts from 'typescript'; import { + DiagnosticSeverity, PublishDiagnosticsParams, RelativePattern, TextDocumentContentChangeEvent @@ -803,7 +804,7 @@ async function createLanguageService( const excludeText = JSON.stringify(exclude); const svelteConfigDiagnostics: ts.Diagnostic[] = [ { - category: ts.DiagnosticCategory.Error, + category: ts.DiagnosticCategory.Warning, code: TsconfigSvelteDiagnostics.NO_SVELTE_INPUT, file: undefined, start: undefined, @@ -977,13 +978,14 @@ async function createLanguageService( diagnostics: svelteConfigDiagnostics.map((d) => ({ message: d.messageText as string, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, - severity: ts.DiagnosticCategory.Error, + severity: DiagnosticSeverity.Warning, source: 'svelte' })) }); - projectConfig.errors = projectConfig.errors + const new_errors = projectConfig.errors .filter((e) => !codes.includes(e.code)) .concat(svelteConfigDiagnostics); + projectConfig.errors.splice(0, projectConfig.errors.length, ...new_errors); } // https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624 diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 2d3e11ff3..75a62b173 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -208,19 +208,23 @@ export class SvelteCheck { }; }; - if (lsContainer.configErrors.length > 0) { + if ( + lsContainer.configErrors.some((error) => error.category === ts.DiagnosticCategory.Error) + ) { return reportConfigError(); } const lang = lsContainer.getService(); - if (lsContainer.configErrors.length > 0) { + if ( + lsContainer.configErrors.some((error) => error.category === ts.DiagnosticCategory.Error) + ) { return reportConfigError(); } const files = lang.getProgram()?.getSourceFiles() || []; const options = lang.getProgram()?.getCompilerOptions() || {}; - return await Promise.all( + const diagnostics = await Promise.all( files.map((file) => { const uri = pathToUrl(file.fileName); const doc = this.docManager.get(uri); @@ -314,6 +318,12 @@ export class SvelteCheck { }) ); + if (lsContainer.configErrors.length) { + diagnostics.push(...reportConfigError()); + } + + return diagnostics; + function reportConfigError() { const grouped = groupBy( lsContainer.configErrors, diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index e226c2b1b..50d0e5530 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -49,7 +49,13 @@ export function processInstanceScriptContent( const tsAst = ts.createSourceFile( 'component.ts.svelte', scriptContent, - ts.ScriptTarget.Latest, + ts.JSDocParsingMode + ? { + languageVersion: ts.ScriptTarget.Latest, + // Exists since TS 5.3 + jsDocParsingMode: ts.JSDocParsingMode.ParseNone + } + : ts.ScriptTarget.Latest, true, ts.ScriptKind.TS ); From e74f1d712b66873c06590fd47dcfcfc69aead7db Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 19 Sep 2024 13:51:34 +0200 Subject: [PATCH 4/9] chore: revert accidental commit --- .../src/svelte2tsx/processInstanceScriptContent.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index 50d0e5530..e226c2b1b 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -49,13 +49,7 @@ export function processInstanceScriptContent( const tsAst = ts.createSourceFile( 'component.ts.svelte', scriptContent, - ts.JSDocParsingMode - ? { - languageVersion: ts.ScriptTarget.Latest, - // Exists since TS 5.3 - jsDocParsingMode: ts.JSDocParsingMode.ParseNone - } - : ts.ScriptTarget.Latest, + ts.ScriptTarget.Latest, true, ts.ScriptKind.TS ); From 81019d91c38effe61c2ac4b8e8f830d8a6d1f148 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:06:10 +0200 Subject: [PATCH 5/9] fix: revert additional two-way-binding checks (#2508) This reverts commit 8c080cf36286984439b468e089e6e4eaacd4230e. This reverts #2477. Sadly, the idea didn't work out, as shown by two opened bug reports: - #2506: A type union can be narrowed on the input, but not on the way back out - #2494: A generic nested within a bound value is not properly resolved and not falling back to `any` (in #2477 we thought of the generic case, but only for when the generic is the whole value type, not when it's nested) For these reasons I don't see a way to properly implement #1392 at the moment. --- .../typescript/features/RenameProvider.ts | 10 +----- .../bindings-two-way-check/Legacy.svelte | 7 ---- .../bindings-two-way-check/Runes.svelte | 6 ---- .../RunesGeneric.svelte | 6 ---- .../expected_svelte_5.json | 36 ------------------- .../bindings-two-way-check/expectedv2.json | 18 ---------- .../bindings-two-way-check/input.svelte | 19 ---------- packages/svelte2tsx/repl/index.svelte | 12 +++---- .../src/htmlxtojsx_v2/nodes/Binding.ts | 6 ---- .../htmlxtojsx_v2/nodes/InlineComponent.ts | 1 - packages/svelte2tsx/svelte-shims-v4.d.ts | 13 ------- .../samples/binding-bare/expected-svelte5.js | 4 +-- .../samples/binding/expected-svelte5.js | 8 ++--- .../editing-binding/expected-svelte5.js | 2 +- 14 files changed, 14 insertions(+), 134 deletions(-) delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts index 8340478c3..3b9caf86e 100644 --- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts @@ -463,16 +463,8 @@ export class RenameProviderImpl implements RenameProvider { const mappedLocations = await Promise.all( renameLocations.map(async (loc) => { const snapshot = await snapshots.retrieve(loc.fileName); - const text = snapshot.getFullText(); - const end = loc.textSpan.start + loc.textSpan.length; - if ( - !(snapshot instanceof SvelteDocumentSnapshot) || - (!isTextSpanInGeneratedCode(text, loc.textSpan) && - // prevent generated code for bindings from being renamed - // (it's not inside a generate comment because diagnostics should show up) - text.slice(end + 3, end + 27) !== '__sveltets_binding_value') - ) { + if (!isTextSpanInGeneratedCode(snapshot.getFullText(), loc.textSpan)) { return { ...loc, range: this.mapRangeToOriginal(snapshot, loc.textSpan), diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte deleted file mode 100644 index 18db75ea2..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -{legacy1} -{legacy2} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte deleted file mode 100644 index 299be4d66..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - -{runes1} -{runes2} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte deleted file mode 100644 index 6accfc4b3..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - -{foo} -{bar} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json deleted file mode 100644 index 6b1d57c83..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "code": 2322, - "message": "Type 'string | number' is not assignable to type 'string'.\n Type 'number' is not assignable to type 'string'.", - "range": { - "end": { - "character": 28, - "line": 17 - }, - "start": { - "character": 28, - "line": 17 - } - }, - "severity": 1, - "source": "ts", - "tags": [] - }, - { - "code": 2322, - "message": "Type 'string | number' is not assignable to type 'string'.\n Type 'number' is not assignable to type 'string'.", - "range": { - "end": { - "character": 45, - "line": 18 - }, - "start": { - "character": 45, - "line": 18 - } - }, - "severity": 1, - "source": "ts", - "tags": [] - } -] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json deleted file mode 100644 index f8c8cb449..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "code": -1, - "message": "Unexpected token", - "range": { - "end": { - "character": 47, - "line": 12 - }, - "start": { - "character": 47, - "line": 12 - } - }, - "severity": 1, - "source": "ts" - } -] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte deleted file mode 100644 index 49020f784..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - diff --git a/packages/svelte2tsx/repl/index.svelte b/packages/svelte2tsx/repl/index.svelte index bf8061fb0..49517cc72 100644 --- a/packages/svelte2tsx/repl/index.svelte +++ b/packages/svelte2tsx/repl/index.svelte @@ -1,7 +1,7 @@ - - - \ No newline at end of file + +{#if value} + +{/if} diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts index 555269b61..5bb17cca7 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts @@ -131,12 +131,6 @@ export function handleBinding( if (isSvelte5Plus && element instanceof InlineComponent) { // To check if property is actually bindable element.appendToStartEnd([`${element.name}.$$bindings = '${attr.name}';`]); - // To check if the binding is also assigned to the variable (only works when there's no assertion, we can't transform that) - if (!isTypescriptNode(attr.expression)) { - element.appendToStartEnd([ - `${expressionStr} = __sveltets_binding_value(${element.originalName}, '${attr.name}');` - ]); - } } if (element instanceof Element) { diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts index 7459f227e..b1bab058d 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts @@ -39,7 +39,6 @@ export class InlineComponent { private startTagEnd: number; private isSelfclosing: boolean; public child?: any; - public originalName = this.node.name; // Add const $$xxx = ... only if the variable name is actually used // in order to prevent "$$xxx is defined but never used" TS hints diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index f01cf6d18..70e6df540 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -253,16 +253,3 @@ declare function __sveltets_2_isomorphic_component< declare function __sveltets_2_isomorphic_component_slots< Props extends Record, Events extends Record, Slots extends Record, Exports extends Record, Bindings extends string >(klass: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }): __sveltets_2_IsomorphicComponent<__sveltets_2_PropsWithChildren, Events, Slots, Exports, Bindings>; - -type __sveltets_NonUndefined = T extends undefined ? never : T; - -declare function __sveltets_binding_value< - // @ts-ignore this is only used for Svelte 5, which knows about the Component type - Comp extends typeof import('svelte').Component, - Key extends string ->(comp: Comp, key: Key): Key extends keyof import('svelte').ComponentProps ? - // bail on unknown because it hints at a generic type which we can't properly resolve here - // remove undefined because optional properties have it, and would result in false positives - unknown extends import('svelte').ComponentProps[Key] ? any : __sveltets_NonUndefined[Key]> : any; -// Overload to ensure typings that only use old SvelteComponent class or something invalid are gracefully handled -declare function __sveltets_binding_value(comp: any, key: string): any diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js index 43291dbae..bb934f658 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js @@ -1,5 +1,5 @@ { svelteHTML.createElement("input", { "type":`text`,"bind:value":value,});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/} { svelteHTML.createElement("input", { "type":`checkbox`,"bind:checked":checked,});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value,}});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';value = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`checkbox`,checked,}});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'checked';checked = __sveltets_binding_value(Input, 'checked');} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value,}});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`checkbox`,checked,}});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'checked';} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js index 355227003..6c881fec9 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js @@ -2,7 +2,7 @@ { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value'); Input} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value'; Input} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js index 31e8b2747..7bb207fea 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js @@ -1,3 +1,3 @@ { svelteHTML.createElement("input", { });obj = __sveltets_2_any(null);} { svelteHTML.createElement("input", { "bind:value":obj.,});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';obj = __sveltets_binding_value(Input, 'value');} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} \ No newline at end of file From 35af691b80537e8b12a908682c31314da97f55d9 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Thu, 26 Sep 2024 02:41:25 +0800 Subject: [PATCH 6/9] perf: auto import cache for svelte-kit language service proxy (#2513) #2464 getPackageJsonsVisibleToFile and getGlobalTypingsCacheLocation are singleton so this should definitely be fine. The proxy language service has the exact list of entry files and the auto-typing also doesn't add any export so it should be fine to just proxy these methods. --- .../src/language-service/sveltekit.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/packages/typescript-plugin/src/language-service/sveltekit.ts b/packages/typescript-plugin/src/language-service/sveltekit.ts index 8a3994c5d..9b5ee4fca 100644 --- a/packages/typescript-plugin/src/language-service/sveltekit.ts +++ b/packages/typescript-plugin/src/language-service/sveltekit.ts @@ -10,6 +10,22 @@ interface KitSnapshot { addedCode: InternalHelpers.AddedCode[]; } +declare module 'typescript/lib/tsserverlibrary' { + interface LanguageServiceHost { + /** @internal */ getCachedExportInfoMap?(): unknown; + /** @internal */ getModuleSpecifierCache?(): unknown; + /** @internal */ getGlobalTypingsCacheLocation?(): string | undefined; + /** @internal */ getSymlinkCache?(files: readonly ts.SourceFile[]): unknown; + /** @internal */ getPackageJsonsVisibleToFile?( + fileName: string, + rootDir?: string + ): readonly unknown[]; + /** @internal */ getPackageJsonAutoImportProvider?(): ts.Program | undefined; + + /** @internal*/ getModuleResolutionCache?(): ts.ModuleResolutionCache; + } +} + const cache = new WeakMap< ts.server.PluginCreateInfo, { @@ -719,6 +735,51 @@ function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, lo ? (...args: Parameters>) => originalLanguageServiceHost.realpath!(...args) : undefined; + + getProjectReferences = originalLanguageServiceHost.getProjectReferences + ? () => originalLanguageServiceHost.getProjectReferences!() + : undefined; + + getParsedCommandLine = originalLanguageServiceHost.getParsedCommandLine + ? (fileName: string) => originalLanguageServiceHost.getParsedCommandLine!(fileName) + : undefined; + + getCachedExportInfoMap = originalLanguageServiceHost.getCachedExportInfoMap + ? () => originalLanguageServiceHost.getCachedExportInfoMap!() + : undefined; + + getModuleSpecifierCache = originalLanguageServiceHost.getModuleSpecifierCache + ? () => originalLanguageServiceHost.getModuleSpecifierCache!() + : undefined; + + getGlobalTypingsCacheLocation = originalLanguageServiceHost.getGlobalTypingsCacheLocation + ? () => originalLanguageServiceHost.getGlobalTypingsCacheLocation!() + : undefined; + + getSymlinkCache = originalLanguageServiceHost.getSymlinkCache + ? (...args: any[]) => + originalLanguageServiceHost.getSymlinkCache!( + // @ts-ignore + ...args + ) + : undefined; + + getPackageJsonsVisibleToFile = originalLanguageServiceHost.getPackageJsonsVisibleToFile + ? (...args: any[]) => + originalLanguageServiceHost.getPackageJsonsVisibleToFile!( + // @ts-ignore + ...args + ) + : undefined; + + getPackageJsonAutoImportProvider = + originalLanguageServiceHost.getPackageJsonAutoImportProvider + ? () => originalLanguageServiceHost.getPackageJsonAutoImportProvider!() + : undefined; + + getModuleResolutionCache = originalLanguageServiceHost.getModuleResolutionCache + ? () => originalLanguageServiceHost.getModuleResolutionCache!() + : undefined; } // Ideally we'd create a full Proxy of the language service, but that seems to have cache issues From 837b61fa57cb80b0e4c531e5d7c61d0000707ce7 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:36:42 +0200 Subject: [PATCH 7/9] breaking(svelte5): only generate function component shape in runes mode (#2517) When a component is in runes mode and not using slots or events, adjust the output to only create the function type that mimics the underlying real shape of components in Svelte 5. This is a breaking change because previously the type was enhanced such that it also had the legacy class shape. As a result, users now may need to switch to `typeof Component` when using the component inside types. Sadly, due to a combination of requirements and TypeScript limitations, we need to always create both a legacy class component and function component type. - Constraints: Need to support Svelte 4 class component types, therefore we need to use __sveltets_2_ensureComponent to transform function components to classes - Limitations: TypeScript is not able to preserve generics during said transformation (i.e. there's no way to express keeping the generic etc) TODO Svelte 6/7: Switch this around and not use new Component in svelte2tsx anymore, which means we can remove the legacy class component. We need something like _ensureFnComponent then. --- .../features/SemanticTokensProvider.ts | 48 +++++++---- .../features/SemanticTokensProvider.test.ts | 7 -- .../fixtures/bindings/RunesGeneric.svelte | 7 ++ .../fixtures/bindings/expected_svelte_5.json | 84 +++++++++++++++++-- .../fixtures/bindings/expectedv2.json | 50 +++++++++-- .../fixtures/bindings/input.svelte | 7 +- .../src/svelte2tsx/addComponentExport.ts | 59 +++++++++---- packages/svelte2tsx/src/svelte2tsx/index.ts | 2 +- .../src/svelte2tsx/nodes/ComponentEvents.ts | 4 + .../src/svelte2tsx/nodes/ExportedNames.ts | 2 +- packages/svelte2tsx/svelte-shims-v4.d.ts | 38 ++++++++- .../expected/TestRunes.svelte.d.ts | 18 +--- .../expected/TestRunes.svelte.d.ts | 8 +- .../expected/TestRunes.svelte.d.ts | 18 +--- packages/svelte2tsx/test/helpers.ts | 5 +- .../expectedv2.ts | 8 +- .../runes-best-effort-types.v5/expectedv2.ts | 5 +- .../samples/runes-bindable.v5/expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../runes-only-export.v5/expectedv2.ts | 5 +- .../svelte2tsx/samples/runes.v5/expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../ts-runes-bindable.v5/expectedv2.ts | 5 +- .../ts-runes-generics.v5/expectedv2.ts | 4 +- .../ts-runes-with-slot.v5/expectedv2.ts | 4 +- .../samples/ts-runes.v5/expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- 30 files changed, 285 insertions(+), 148 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte diff --git a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts index 9af4f4239..2103371f2 100644 --- a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts @@ -66,29 +66,23 @@ export class SemanticTokensProviderImpl implements SemanticTokensProvider { continue; } - const originalPosition = this.mapToOrigin( + const original = this.map( textDocument, tsDoc, generatedOffset, generatedLength, - encodedClassification + encodedClassification, + classificationType ); - if (!originalPosition) { - continue; - } - - const [line, character, length] = originalPosition; // remove identifiers whose start and end mapped to the same location, // like the svelte2tsx inserted render function, // or reversed like Component.$on - if (length <= 0) { + if (!original || original[2] <= 0) { continue; } - const modifier = this.getTokenModifierFromClassification(encodedClassification); - - data.push([line, character, length, classificationType, modifier]); + data.push(original); } const sorted = data.sort((a, b) => { @@ -103,17 +97,20 @@ export class SemanticTokensProviderImpl implements SemanticTokensProvider { return builder.build(); } - private mapToOrigin( + private map( document: Document, snapshot: SvelteDocumentSnapshot, generatedOffset: number, generatedLength: number, - token: number - ): [line: number, character: number, length: number, start: number] | undefined { + encodedClassification: number, + classificationType: number + ): + | [line: number, character: number, length: number, token: number, modifier: number] + | undefined { const text = snapshot.getFullText(); if ( isInGeneratedCode(text, generatedOffset, generatedOffset + generatedLength) || - (token === 2817 /* top level function */ && + (encodedClassification === 2817 /* top level function */ && text.substring(generatedOffset, generatedOffset + generatedLength) === 'render') ) { return; @@ -132,7 +129,26 @@ export class SemanticTokensProviderImpl implements SemanticTokensProvider { const startOffset = document.offsetAt(startPosition); const endOffset = document.offsetAt(endPosition); - return [startPosition.line, startPosition.character, endOffset - startOffset, startOffset]; + // Ensure components in the template get no semantic highlighting + if ( + (classificationType === 0 || + classificationType === 5 || + classificationType === 7 || + classificationType === 10) && + snapshot.svelteNodeAt(startOffset)?.type === 'InlineComponent' && + (document.getText().charCodeAt(startOffset - 1) === /* < */ 60 || + document.getText().charCodeAt(startOffset - 1) === /* / */ 47) + ) { + return; + } + + return [ + startPosition.line, + startPosition.character, + endOffset - startOffset, + classificationType, + this.getTokenModifierFromClassification(encodedClassification) + ]; } /** diff --git a/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts b/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts index 7acb27099..a942f32f8 100644 --- a/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts @@ -191,13 +191,6 @@ describe('SemanticTokensProvider', function () { type: TokenType.variable, modifiers: [TokenModifier.declaration, TokenModifier.local, TokenModifier.readonly] }, - { - line: 12, - character: 5, - length: 'Imported'.length, - type: isSvelte5Plus ? TokenType.type : TokenType.class, - modifiers: isSvelte5Plus ? [TokenModifier.readonly] : [] - }, { line: 12, character: 23, diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte new file mode 100644 index 000000000..0b9ff0c17 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json index 72c8185db..8aca1d067 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json @@ -5,11 +5,11 @@ "range": { "end": { "character": 20, - "line": 25 + "line": 26 }, "start": { "character": 7, - "line": 25 + "line": 26 } }, "severity": 1, @@ -22,11 +22,11 @@ "range": { "end": { "character": 21, - "line": 26 + "line": 27 }, "start": { "character": 12, - "line": 26 + "line": 27 } }, "severity": 1, @@ -39,11 +39,11 @@ "range": { "end": { "character": 21, - "line": 26 + "line": 27 }, "start": { "character": 7, - "line": 26 + "line": 27 } }, "severity": 1, @@ -56,11 +56,79 @@ "range": { "end": { "character": 17, - "line": 27 + "line": 28 }, "start": { "character": 8, - "line": 27 + "line": 28 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2322, + "message": "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\nTo mark a property as bindable: 'let { readonly = $bindable() } = $props()'", + "range": { + "end": { + "character": 27, + "line": 30 + }, + "start": { + "character": 14, + "line": 30 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2353, + "message": "Object literal may only specify known properties, and 'only_bind' does not exist in type '$$ComponentProps'.", + "range": { + "end": { + "character": 28, + "line": 31 + }, + "start": { + "character": 19, + "line": 31 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2322, + "message": "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\nTo mark a property as bindable: 'let { only_bind = $bindable() } = $props()'", + "range": { + "end": { + "character": 28, + "line": 31 + }, + "start": { + "character": 14, + "line": 31 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2353, + "message": "Object literal may only specify known properties, and 'only_bind' does not exist in type '$$ComponentProps'.", + "range": { + "end": { + "character": 24, + "line": 32 + }, + "start": { + "character": 15, + "line": 32 } }, "severity": 1, diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json index e441ca7e2..da73688bf 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json @@ -1,15 +1,32 @@ [ + { + "code": 2344, + "message": "Type 'typeof Runes__SvelteComponent_' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof Runes__SvelteComponent_' provides no match for the signature '(...args: any): any'.", + "range": { + "end": { + "character": 41, + "line": 12 + }, + "start": { + "character": 29, + "line": 12 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, { "code": 2353, "message": "Object literal may only specify known properties, and 'can_bind' does not exist in type '{ only_bind?: (() => boolean) | undefined; }'.", "range": { "end": { "character": 20, - "line": 20 + "line": 21 }, "start": { "character": 12, - "line": 20 + "line": 21 } }, "severity": 1, @@ -22,11 +39,11 @@ "range": { "end": { "character": 16, - "line": 21 + "line": 22 }, "start": { "character": 8, - "line": 21 + "line": 22 } }, "severity": 1, @@ -39,11 +56,11 @@ "range": { "end": { "character": 16, - "line": 22 + "line": 23 }, "start": { "character": 8, - "line": 22 + "line": 23 } }, "severity": 1, @@ -56,11 +73,28 @@ "range": { "end": { "character": 20, - "line": 25 + "line": 26 }, "start": { "character": 12, - "line": 25 + "line": 26 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2353, + "message": "Object literal may only specify known properties, and 'readonly' does not exist in type '{ only_bind?: (() => boolean) | undefined; }'.", + "range": { + "end": { + "character": 27, + "line": 30 + }, + "start": { + "character": 19, + "line": 30 } }, "severity": 1, diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte index 55de44f2d..dd51d79f5 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte @@ -1,6 +1,7 @@ @@ -26,3 +27,7 @@ + + + + diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts index 4cbf2b980..fc9550b77 100644 --- a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts +++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts @@ -5,15 +5,16 @@ import { ExportedNames } from './nodes/ExportedNames'; import { ComponentDocumentation } from './nodes/ComponentDocumentation'; import { Generics } from './nodes/Generics'; import { surroundWithIgnoreComments } from '../utils/ignore'; +import { ComponentEvents } from './nodes/ComponentEvents'; export interface AddComponentExportPara { str: MagicString; canHaveAnyProp: boolean; /** - * If true, not fallback to `any` + * If strictEvents true, not fallback to `any` * -> all unknown events will throw a type error * */ - strictEvents: boolean; + events: ComponentEvents; isTsFile: boolean; usesAccessors: boolean; exportedNames: ExportedNames; @@ -41,7 +42,7 @@ export function addComponentExport(params: AddComponentExportPara) { } function addGenericsComponentExport({ - strictEvents, + events, canHaveAnyProp, exportedNames, componentDocumentation, @@ -70,7 +71,7 @@ class __sveltets_Render${genericsDef} { return ${props(true, canHaveAnyProp, exportedNames, `render${genericsRef}()`)}.props; } events() { - return ${events(strictEvents, `render${genericsRef}()`)}.events; + return ${_events(events.hasStrictEvents() || exportedNames.usesRunes(), `render${genericsRef}()`)}.events; } slots() { return render${genericsRef}().slots; @@ -94,15 +95,29 @@ ${ if (isSvelte5) { // Don't add props/events/slots type exports in dts mode for now, maybe someone asks for it to be back, // but it's safer to not do it for now to have more flexibility in the future. + let eventsSlotsType = []; + if (events.hasEvents() || !exportedNames.usesRunes()) { + eventsSlotsType.push(`$$events?: ${returnType('events')}`); + } + if (usesSlots) { + eventsSlotsType.push(`$$slots?: ${returnType('slots')}`); + eventsSlotsType.push(`children?: any`); + } const propsType = !canHaveAnyProp && exportedNames.hasNoProps() - ? `{$$events?: ${returnType('events')}${usesSlots ? `, $$slots?: ${returnType('slots')}, children?: any` : ''}}` - : `${returnType('props')} & {$$events?: ${returnType('events')}${usesSlots ? `, $$slots?: ${returnType('slots')}, children?: any` : ''}}`; + ? `{${eventsSlotsType.join(', ')}}` + : `${returnType('props')} & {${eventsSlotsType.join(', ')}}`; + const bindingsType = `ReturnType<__sveltets_Render${generics.toReferencesAnyString()}['bindings']>`; + + // Sadly, due to a combination of requirements and TypeScript limitations, we need to always create both a legacy class component and function component type. + // - Constraints: Need to support Svelte 4 class component types, therefore we need to use __sveltets_2_ensureComponent to transform function components to classes + // - Limitations: TypeScript is not able to preserve generics during said transformation (i.e. there's no way to express keeping the generic etc) + // TODO Svelte 6/7: Switch this around and not use new Component in svelte2tsx anymore, which means we can remove the legacy class component. We need something like _ensureFnComponent then. statement += `\ninterface $$IsomorphicComponent {\n` + ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` + ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` + - ` z_$$bindings?: ReturnType<__sveltets_Render${generics.toReferencesAnyString()}['bindings']>;\n` + + ` z_$$bindings?: ${bindingsType};\n` + `}\n` + `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` + surroundWithIgnoreComments( @@ -137,7 +152,7 @@ ${ } function addSimpleComponentExport({ - strictEvents, + events, isTsFile, canHaveAnyProp, exportedNames, @@ -154,7 +169,7 @@ function addSimpleComponentExport({ isTsFile, canHaveAnyProp, exportedNames, - events(strictEvents, 'render()') + _events(events.hasStrictEvents(), 'render()') ); const doc = componentDocumentation.getFormatted(); @@ -162,7 +177,11 @@ function addSimpleComponentExport({ let statement: string; if (mode === 'dts') { - if (isSvelte5) { + if (isSvelte5 && exportedNames.usesRunes() && !usesSlots && !events.hasEvents()) { + statement = + `\n${doc}const ${className || '$$Component'} = __sveltets_2_fn_component(render());\n` + + `export default ${className || '$$Component'};`; + } else if (isSvelte5) { // Inline definitions from Svelte shims; else dts files will reference the globals which will be unresolved statement = `\ninterface $$__sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { @@ -223,12 +242,18 @@ declare function $$__sveltets_2_isomorphic_component< } } else { if (isSvelte5) { - statement = - `\n${doc}const ${className || '$$Component'} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` + - surroundWithIgnoreComments( - `type ${className || '$$Component'} = InstanceType;\n` - ) + - `export default ${className || '$$Component'};`; + if (exportedNames.usesRunes() && !usesSlots && !events.hasEvents()) { + statement = + `\n${doc}const ${className || '$$Component'} = __sveltets_2_fn_component(render());\n` + + `export default ${className || '$$Component'};`; + } else { + statement = + `\n${doc}const ${className || '$$Component'} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` + + surroundWithIgnoreComments( + `type ${className || '$$Component'} = InstanceType;\n` + ) + + `export default ${className || '$$Component'};`; + } } else { statement = `\n\n${doc}export default class${ @@ -281,7 +306,7 @@ function addTypeExport( } } -function events(strictEvents: boolean, renderStr: string) { +function _events(strictEvents: boolean, renderStr: string) { return strictEvents ? renderStr : `__sveltets_2_with_any_event(${renderStr})`; } diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index e6410d661..91e0d6b5a 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -435,7 +435,7 @@ export function svelte2tsx( addComponentExport({ str, canHaveAnyProp: !exportedNames.uses$$Props && (uses$$props || uses$$restProps), - strictEvents: events.hasStrictEvents(), // TODO in Svelte 6 we should also apply strictEvents in runes mode + events, isTsFile: options?.isTsFile, exportedNames, usesAccessors, diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts index b2bb18e8e..a83dc9df0 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts @@ -77,6 +77,10 @@ export class ComponentEvents { this.componentEventsInterface.setComponentEventsInterface(node, this.str, astOffset); } + hasEvents(): boolean { + return this.eventsClass.events.size > 0; + } + hasStrictEvents(): boolean { return this.componentEventsInterface.isPresent() || this.strictEvents; } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index d461ecd66..3f5ec412a 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -793,7 +793,7 @@ export class ExportedNames { } hasPropsRune() { - return this.isSvelte5Plus && (this.$props.type || this.$props.comment); + return this.isSvelte5Plus && !!(this.$props.type || this.$props.comment); } checkGlobalsForRunes(globals: string[]) { diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index 70e6df540..71bfbac6d 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -220,11 +220,36 @@ declare type ATypedSvelteComponent = { * ``` */ declare type ConstructorOfATypedSvelteComponent = new (args: {target: any, props?: any}) => ATypedSvelteComponent +// Usage note: Cannot properly transform generic function components to class components due to TypeScript limitations declare function __sveltets_2_ensureComponent< - // @ts-ignore svelte.Component doesn't exist in Svelte 4 - T extends ConstructorOfATypedSvelteComponent | (typeof import('svelte') extends { mount: any } ? import('svelte').Component : never) | null | undefined - // @ts-ignore svelte.Component doesn't exist in Svelte 4 ->(type: T): NonNullable> ? typeof import('svelte').SvelteComponent : T : T>; + T extends + | ConstructorOfATypedSvelteComponent + | (typeof import('svelte') extends { mount: any } + ? // @ts-ignore svelte.Component doesn't exist in Svelte 4 + import('svelte').Component + : never) + | null + | undefined +>( + type: T +): NonNullable< + T extends ConstructorOfATypedSvelteComponent + ? T + : typeof import('svelte') extends { mount: any } + ? // @ts-ignore svelte.Component doesn't exist in Svelte 4 + T extends import('svelte').Component< + infer Props extends Record, + infer Exports extends Record, + infer Bindings extends string + > + ? new ( + options: import('svelte').ComponentConstructorOptions + ) => import('svelte').SvelteComponent & + Exports & { $$bindings: Bindings } + : never + : never +>; + declare function __sveltets_2_ensureArray | Iterable>(array: T): T extends ArrayLike ? U[] : T extends Iterable ? Iterable : any[]; type __sveltets_2_PropsWithChildren = Props & @@ -240,6 +265,11 @@ declare function __sveltets_2_runes_constructor(render: {props declare function __sveltets_$$bindings(...bindings: Bindings): Bindings[number]; +declare function __sveltets_2_fn_component< + Props extends Record, Exports extends Record, Bindings extends string + // @ts-ignore Svelte 5 only +>(klass: {props: Props, exports?: Exports, bindings?: Bindings }): import('svelte').Component; + interface __sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { new (options: import('svelte').ComponentConstructorOptions): import('svelte').SvelteComponent & { $$bindings?: Bindings } & Exports; (internal: unknown, props: Props extends Record ? {$$events?: Events, $$slots?: Slots} : Props & {$$events?: Events, $$slots?: Slots}): Exports & { $set?: any, $on?: any }; diff --git a/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts index 6c3b7250e..04808233f 100644 --- a/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts @@ -1,23 +1,7 @@ -interface $$__sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { - new (options: import('svelte').ComponentConstructorOptions): import('svelte').SvelteComponent & { - $$bindings?: Bindings; - } & Exports; - (internal: unknown, props: Props & { - $$events?: Events; - $$slots?: Slots; - }): Exports & { - $set?: any; - $on?: any; - }; - z_$$bindings?: Bindings; -} -declare const TestRunes: $$__sveltets_2_IsomorphicComponent<{ +declare const TestRunes: import("svelte").Component<{ foo: string; bar?: number; }, { - [evt: string]: CustomEvent; -}, {}, { baz: () => void; }, "bar">; -type TestRunes = InstanceType; export default TestRunes; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts index 6f01eae42..296b24ab4 100644 --- a/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts @@ -3,9 +3,7 @@ declare class __sveltets_Render, K extends keyof T foo: T; bar?: K; }; - events(): {} & { - [evt: string]: CustomEvent; - }; + events(): {}; slots(): {}; bindings(): "bar"; exports(): { @@ -16,9 +14,7 @@ interface $$IsomorphicComponent { new , K extends keyof T>(options: import('svelte').ComponentConstructorOptions['props']>>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']>; } & ReturnType<__sveltets_Render['exports']>; - , K extends keyof T>(internal: unknown, props: ReturnType<__sveltets_Render['props']> & { - $$events?: ReturnType<__sveltets_Render['events']>; - }): ReturnType<__sveltets_Render['exports']>; + , K extends keyof T>(internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>; z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; } declare const TestRunes: $$IsomorphicComponent; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts index 6c3b7250e..04808233f 100644 --- a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts @@ -1,23 +1,7 @@ -interface $$__sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { - new (options: import('svelte').ComponentConstructorOptions): import('svelte').SvelteComponent & { - $$bindings?: Bindings; - } & Exports; - (internal: unknown, props: Props & { - $$events?: Events; - $$slots?: Slots; - }): Exports & { - $set?: any; - $on?: any; - }; - z_$$bindings?: Bindings; -} -declare const TestRunes: $$__sveltets_2_IsomorphicComponent<{ +declare const TestRunes: import("svelte").Component<{ foo: string; bar?: number; }, { - [evt: string]: CustomEvent; -}, {}, { baz: () => void; }, "bar">; -type TestRunes = InstanceType; export default TestRunes; diff --git a/packages/svelte2tsx/test/helpers.ts b/packages/svelte2tsx/test/helpers.ts index ceec3b03d..7fa3f5acf 100644 --- a/packages/svelte2tsx/test/helpers.ts +++ b/packages/svelte2tsx/test/helpers.ts @@ -230,7 +230,10 @@ export function test_samples(dir: string, transform: TransformSampleFn, js: 'js' if (sample.name.endsWith('.v5') && !isSvelte5Plus) continue; const svelteFile = sample.find_file('*.svelte'); - const expectedFile = isSvelte5Plus ? `expected-svelte5.${js}` : `expectedv2.${js}`; + const expectedFile = + isSvelte5Plus && !sample.name.endsWith('.v5') + ? `expected-svelte5.${js}` + : `expectedv2.${js}`; const config = { filename: svelteFile, sampleName: sample.name, diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts index 6406a56c2..5c395efb6 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts @@ -9,7 +9,7 @@ async () => { { svelteHTML.createElement("h1", {}); world; }}; -return { props: {world: world}, slots: {}, events: {} }} - -export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(['world'], __sveltets_2_with_any_event(render()))) { -} \ No newline at end of file +return { props: {world: world}, exports: {}, bindings: "", slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(['world'], __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/runes-best-effort-types.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts index 4b9dd9437..b5c446abe 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts index c4de2d23e..5c4ac5f23 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$bindings('b'), 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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts index 05b04da66..8263d1173 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts @@ -9,6 +9,5 @@ async () => { state; derived;}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts index cd5d6aca0..0a77ec5d6 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts @@ -8,6 +8,5 @@ async () => { x;}; return { props: /** @type {Record} */ ({}), exports: /** @type {{foo: typeof foo}} */ ({}), bindings: __sveltets_$$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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts index 06e1497b4..328328888 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts @@ -8,6 +8,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts index fe4d98e31..6eceb26ee 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts @@ -8,6 +8,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {{snapshot: typeof snapshot}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts index b5014a5d2..eeb3e8864 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts @@ -6,6 +6,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {{snapshot: typeof snapshot}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts index eab76fea5..faa1d533b 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {}, bindings: __sveltets_$$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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts index 90775addb..ce3a575fe 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {}, bindings: __sveltets_$$bindings('b', 'c'), 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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts index b7e2a8765..d87b3a3ee 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts @@ -12,7 +12,7 @@ class __sveltets_Render { return render().props; } events() { - return __sveltets_2_with_any_event(render()).events; + return render().events; } slots() { return render().slots; @@ -23,7 +23,7 @@ class __sveltets_Render { interface $$IsomorphicComponent { new (options: import('svelte').ComponentConstructorOptions['props']>>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']> } & ReturnType<__sveltets_Render['exports']>; - (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {$$events?: ReturnType<__sveltets_Render['events']>}): ReturnType<__sveltets_Render['exports']>; + (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>; z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; } const Input__SvelteComponent_: $$IsomorphicComponent = null as any; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts index 68212de3d..d34195b64 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts @@ -16,7 +16,7 @@ class __sveltets_Render { return render().props; } events() { - return __sveltets_2_with_any_event(render()).events; + return render().events; } slots() { return render().slots; @@ -27,7 +27,7 @@ class __sveltets_Render { interface $$IsomorphicComponent { new (options: import('svelte').ComponentConstructorOptions['props']>& {children?: any}>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']> } & ReturnType<__sveltets_Render['exports']>; - (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {$$events?: ReturnType<__sveltets_Render['events']>, $$slots?: ReturnType<__sveltets_Render['slots']>, children?: any}): ReturnType<__sveltets_Render['exports']>; + (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {$$slots?: ReturnType<__sveltets_Render['slots']>, children?: any}): ReturnType<__sveltets_Render['exports']>; z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; } const Input__SvelteComponent_: $$IsomorphicComponent = null as any; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts index 12e191193..8e4065235 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts @@ -7,6 +7,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {}, bindings: __sveltets_$$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 +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts index 522d58fd2..df7501a9a 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts @@ -6,6 +6,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: any }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts index 04db7b55e..ae77eca3f 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts @@ -6,6 +6,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: typeof snapshot }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file From 7c76ec560698e9db6343329bd978d5a24cd03a90 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:46:16 +0800 Subject: [PATCH 8/9] fix: check project files update more aggressively before assigning service (#2518) #2516 Most of the time, the didOpen request is earlier than the watcher event. So if the file doesn't exist in the GlobalSnapshotManager we manually invoke the project file update check. This won't cause 2 project files check because if the file already is a project file we won't check project files. --- .../src/plugins/typescript/service.ts | 22 ++++++++++++--- .../test/plugins/typescript/service.test.ts | 27 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 0e0d0f27a..caf8f0350 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -40,7 +40,7 @@ export interface LanguageServiceContainer { deleteSnapshot(filePath: string): void; invalidateModuleCache(filePath: string[]): void; scheduleProjectFileUpdate(watcherNewFiles: string[]): void; - ensureProjectFileUpdates(): void; + ensureProjectFileUpdates(newFile?: string): void; updateTsOrJsFile(fileName: string, changes?: TextDocumentContentChangeEvent[]): void; /** * Checks if a file is present in the project. @@ -225,7 +225,7 @@ export async function getService( service: LanguageServiceContainer, triedTsConfig: Set ): Promise { - service.ensureProjectFileUpdates(); + service.ensureProjectFileUpdates(path); if (service.snapshotManager.isProjectFile(path)) { return service; } @@ -648,9 +648,23 @@ async function createLanguageService( } } - function ensureProjectFileUpdates(): void { + function ensureProjectFileUpdates(newFile?: string): void { const info = parsedTsConfigInfo.get(tsconfigPath); - if (!info || !info.pendingProjectFileUpdate) { + if (!info) { + return; + } + + if ( + newFile && + !info.pendingProjectFileUpdate && + // no global snapshots yet when initial load pending + !snapshotManager.isProjectFile(newFile) && + !docContext.globalSnapshotsManager.get(newFile) + ) { + scheduleProjectFileUpdate([newFile]); + } + + if (!info.pendingProjectFileUpdate) { return; } const projectFileCountBefore = snapshotManager.getProjectFileNames().length; diff --git a/packages/language-server/test/plugins/typescript/service.test.ts b/packages/language-server/test/plugins/typescript/service.test.ts index 71db7f064..e0aa9ffd4 100644 --- a/packages/language-server/test/plugins/typescript/service.test.ts +++ b/packages/language-server/test/plugins/typescript/service.test.ts @@ -657,6 +657,33 @@ describe('service', () => { assert.deepStrictEqual(findError(ls2), undefined); }); + it('assigns newly created files to the right service before the watcher trigger', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + const tsconfigPath = path.join(dirPath, 'tsconfig.json'); + virtualSystem.writeFile( + tsconfigPath, + JSON.stringify({ + compilerOptions: {} + }) + ); + + const svelteFilePath = path.join(dirPath, 'random.svelte'); + + virtualSystem.writeFile(svelteFilePath, ''); + + const ls = await getService(svelteFilePath, rootUris, lsDocumentContext); + + assert.equal(normalizePath(ls.tsconfigPath), normalizePath(tsconfigPath)); + + const svelteFilePath2 = path.join(dirPath, 'random2.svelte'); + virtualSystem.writeFile(svelteFilePath2, ''); + + const ls2 = await getService(svelteFilePath2, rootUris, lsDocumentContext); + assert.equal(normalizePath(ls2.tsconfigPath), normalizePath(tsconfigPath)); + }); + function getSemanticDiagnosticsMessages(ls: LanguageServiceContainer, filePath: string) { return ls .getService() From fc2144b82a34298f9046d929b94009f06e144749 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 26 Sep 2024 03:31:53 -0700 Subject: [PATCH 9/9] chore: upgrade to chokidar 4 (#2502) Also remove long-obsolete sapper handling --- packages/language-server/package.json | 2 +- .../src/lib/FallbackWatcher.ts | 7 +- packages/svelte-check/package.json | 2 +- packages/svelte-check/src/index.ts | 76 +++++++++++++------ pnpm-lock.yaml | 22 +++++- 5 files changed, 72 insertions(+), 37 deletions(-) diff --git a/packages/language-server/package.json b/packages/language-server/package.json index f3702c47b..2963ffe11 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -52,7 +52,7 @@ "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@vscode/emmet-helper": "2.8.4", - "chokidar": "^3.4.1", + "chokidar": "^4.0.1", "estree-walker": "^2.0.1", "fdir": "^6.2.0", "lodash": "^4.17.21", diff --git a/packages/language-server/src/lib/FallbackWatcher.ts b/packages/language-server/src/lib/FallbackWatcher.ts index 81ec47349..4af7e71ff 100644 --- a/packages/language-server/src/lib/FallbackWatcher.ts +++ b/packages/language-server/src/lib/FallbackWatcher.ts @@ -25,12 +25,7 @@ export class FallbackWatcher { this.watcher = watch( workspacePaths.map((workspacePath) => join(workspacePath, recursivePatterns)), { - ignored: (path: string) => - gitOrNodeModules.test(path) && - // Handle Sapper's alias mapping - !path.includes('src/node_modules') && - !path.includes('src\\node_modules'), - + ignored: gitOrNodeModules, // typescript would scan the project files on init. // We only need to know what got updated. ignoreInitial: true, diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 634a029f6..b49d294a2 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^3.4.1", + "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index 866d23bf3..f2aeb79fb 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -32,27 +32,7 @@ async function openAllDocuments( ) { const offset = workspaceUri.fsPath.length + 1; // We support a very limited subset of glob patterns: You can only have ** at the end or the start - const ignored: Array<(path: string) => boolean> = filePathsToIgnore.map((i) => { - if (i.endsWith('**')) i = i.slice(0, -2); - - if (i.startsWith('**')) { - i = i.slice(2); - - if (i.includes('*')) - throw new Error( - 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' - ); - - return (path) => path.includes(i); - } - - if (i.includes('*')) - throw new Error( - 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' - ); - - return (path) => path.startsWith(i); - }); + const ignored = createIgnored(filePathsToIgnore); const isIgnored = (path: string) => { path = path.slice(offset); for (const i of ignored) { @@ -84,6 +64,30 @@ async function openAllDocuments( } } +function createIgnored(filePathsToIgnore: string[]): Array<(path: string) => boolean> { + return filePathsToIgnore.map((i) => { + if (i.endsWith('**')) i = i.slice(0, -2); + + if (i.startsWith('**')) { + i = i.slice(2); + + if (i.includes('*')) + throw new Error( + 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' + ); + + return (path) => path.includes(i); + } + + if (i.includes('*')) + throw new Error( + 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' + ); + + return (path) => path.startsWith(i); + }); +} + async function getDiagnostics( workspaceUri: URI, writer: Writer, @@ -149,10 +153,32 @@ class DiagnosticsWatcher { filePathsToIgnore: string[], ignoreInitialAdd: boolean ) { - watch(`${workspaceUri.fsPath}/**/*.{svelte,d.ts,ts,js,jsx,tsx,mjs,cjs,mts,cts}`, { - ignored: ['node_modules', 'vite.config.{js,ts}.timestamp-*'] - .concat(filePathsToIgnore) - .map((ignore) => path.join(workspaceUri.fsPath, ignore)), + 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; + + watch(workspaceUri.fsPath, { + ignored: (path, stats) => { + if ( + path.includes('node_modules') || + path.includes('.git') || + (stats?.isFile() && (!fileEnding.test(path) || viteConfigRegex.test(path))) + ) { + return true; + } + + if (userIgnored.length !== 0) { + path = path.slice(offset); + for (const i of userIgnored) { + if (i(path)) { + return true; + } + } + } + + return false; + }, ignoreInitial: ignoreInitialAdd }) .on('add', (path) => this.updateDocument(path, true)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1bc6d8f0..f7d19793f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,8 +31,8 @@ importers: specifier: 2.8.4 version: 2.8.4 chokidar: - specifier: ^3.4.1 - version: 3.5.3 + specifier: ^4.0.1 + version: 4.0.1 estree-walker: specifier: ^2.0.1 version: 2.0.2 @@ -113,8 +113,8 @@ importers: specifier: ^0.3.25 version: 0.3.25 chokidar: - specifier: ^3.4.1 - version: 3.5.3 + specifier: ^4.0.1 + version: 4.0.1 fdir: specifier: ^6.2.0 version: 6.2.0 @@ -620,6 +620,10 @@ packages: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.1: + resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} + engines: {node: '>= 14.16.0'} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -1108,6 +1112,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.0.1: + resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==} + engines: {node: '>= 14.16.0'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1684,6 +1692,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.1: + dependencies: + readdirp: 4.0.1 + clean-stack@2.2.0: {} cliui@7.0.4: @@ -2162,6 +2174,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.0.1: {} + require-directory@2.1.1: {} resolve@1.22.2: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy