diff --git a/packages/language-server/.gitignore b/packages/language-server/.gitignore index 02c400bc6..25fe7c815 100644 --- a/packages/language-server/.gitignore +++ b/packages/language-server/.gitignore @@ -1,4 +1,4 @@ dist/ .vscode/ node_modules/ -!test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/ \ No newline at end of file +!test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package \ No newline at end of file diff --git a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts index 3b0a76c06..b62680027 100644 --- a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts +++ b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts @@ -113,6 +113,7 @@ export class LSAndTSDocResolver { } this.lsDocumentContext = { + isSvelteCheck: !!this.options?.isSvelteCheck, ambientTypesSource: this.options?.isSvelteCheck ? 'svelte-check' : 'svelte2tsx', createDocument: this.createDocument, transformOnTemplateError: !this.options?.isSvelteCheck, diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index caf8f0350..47321a531 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -29,6 +29,7 @@ import { isSvelteFilePath } from './utils'; import { createProject, ProjectService } from './serviceCache'; +import { internalHelpers } from 'svelte2tsx'; export interface LanguageServiceContainer { readonly tsconfigPath: string; @@ -132,6 +133,7 @@ export function __resetCache() { } export interface LanguageServiceDocumentContext { + isSvelteCheck: boolean; ambientTypesSource: string; transformOnTemplateError: boolean; createDocument: (fileName: string, content: string) => Document; @@ -1211,25 +1213,13 @@ async function createLanguageService( } function getSvelteShimFiles() { - const isSvelte3 = sveltePackageInfo.version.major === 3; - const svelteHtmlDeclaration = isSvelte3 - ? undefined - : join(sveltePackageInfo.path, 'svelte-html.d.ts'); - const svelteHtmlFallbackIfNotExist = - svelteHtmlDeclaration && tsSystem.fileExists(svelteHtmlDeclaration) - ? svelteHtmlDeclaration - : './svelte-jsx-v4.d.ts'; - - const svelteTsxFiles = ( - isSvelte3 - ? ['./svelte-shims.d.ts', './svelte-jsx.d.ts', './svelte-native-jsx.d.ts'] - : [ - './svelte-shims-v4.d.ts', - svelteHtmlFallbackIfNotExist, - './svelte-native-jsx.d.ts' - ] - ).map((f) => tsSystem.resolvePath(resolve(svelteTsPath, f))); - + const svelteTsxFiles = internalHelpers.get_global_types( + tsSystem, + sveltePackageInfo.version.major === 3, + sveltePackageInfo.path, + svelteTsPath, + docContext.isSvelteCheck ? undefined : tsconfigPath || workspacePath + ); const result = new FileSet(tsSystem.useCaseSensitiveFileNames); svelteTsxFiles.forEach((f) => result.add(normalizePath(f))); diff --git a/packages/language-server/test/plugins/typescript/service.test.ts b/packages/language-server/test/plugins/typescript/service.test.ts index e0aa9ffd4..360848fad 100644 --- a/packages/language-server/test/plugins/typescript/service.test.ts +++ b/packages/language-server/test/plugins/typescript/service.test.ts @@ -21,6 +21,7 @@ describe('service', () => { const rootUris = [pathToUrl(testDir)]; const lsDocumentContext: LanguageServiceDocumentContext = { + isSvelteCheck: false, ambientTypesSource: 'svelte2tsx', createDocument(fileName, content) { return new Document(pathToUrl(fileName), content); diff --git a/packages/svelte2tsx/index.d.ts b/packages/svelte2tsx/index.d.ts index 162d7df45..87c2fda52 100644 --- a/packages/svelte2tsx/index.d.ts +++ b/packages/svelte2tsx/index.d.ts @@ -134,6 +134,13 @@ export function emitDts(config: EmitDtsConfig): Promise; * static top level `ts` namespace, it must be passed as a parameter. */ export const internalHelpers: { + get_global_types: ( + tsSystem: ts.System, + isSvelte3: boolean, + sveltePath: string, + typesPath: string, + hiddenFolderPath?: string, + ) => string[], isKitFile: ( fileName: string, options: InternalHelpers.KitFilesSettings diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 7cf2706ef..6fd902108 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -1,6 +1,6 @@ { "name": "svelte2tsx", - "version": "0.7.0", + "version": "0.7.23", "description": "Convert Svelte components to TSX for type checking", "author": "David Pershouse", "license": "MIT", diff --git a/packages/svelte2tsx/src/helpers/files.ts b/packages/svelte2tsx/src/helpers/files.ts new file mode 100644 index 000000000..a4bd1d2b4 --- /dev/null +++ b/packages/svelte2tsx/src/helpers/files.ts @@ -0,0 +1,72 @@ +import { basename, dirname, join, resolve } from 'path'; +import type ts from 'typescript'; + +/** + * Returns the path to the global svelte2tsx files that should be included in the project. + * Creates a hidden folder in the user's node_modules if `hiddenFolderPath` is provided. + */ +export function get_global_types( + tsSystem: ts.System, + isSvelte3: boolean, + sveltePath: string, + typesPath: string, + hiddenFolderPath?: string +): string[] { + const svelteHtmlPath = isSvelte3 ? undefined : join(sveltePath, 'svelte-html.d.ts'); + const svelteHtmlPathExists = svelteHtmlPath && tsSystem.fileExists(svelteHtmlPath); + const svelteHtmlFile = svelteHtmlPathExists ? svelteHtmlPath : './svelte-jsx-v4.d.ts'; + + let svelteTsxFiles: string[]; + if (isSvelte3) { + svelteTsxFiles = ['./svelte-shims.d.ts', './svelte-jsx.d.ts', './svelte-native-jsx.d.ts']; + } else { + svelteTsxFiles = ['./svelte-shims-v4.d.ts', './svelte-native-jsx.d.ts']; + if (!svelteHtmlPathExists) { + svelteTsxFiles.push(svelteHtmlPath); + } + } + svelteTsxFiles = svelteTsxFiles.map((f) => tsSystem.resolvePath(resolve(typesPath, f))); + + if (hiddenFolderPath) { + try { + // IDE context - the `import('svelte')` statements inside the d.ts files will load the Svelte version of + // the extension, which can cause all sorts of problems. Therefore put the files into a hidden folder in + // the user's node_modules, preferably next to the Svelte package. + let path = dirname(sveltePath); + + if (!tsSystem.directoryExists(resolve(path, 'node_modules'))) { + path = hiddenFolderPath; + + while (path && !tsSystem.directoryExists(resolve(path, 'node_modules'))) { + const parent = dirname(path); + if (path === parent) { + path = ''; + break; + } + path = parent; + } + } + + if (path) { + const hiddenPath = resolve(path, 'node_modules/.svelte2tsx-language-server-files'); + const newFiles = []; + for (const f of svelteTsxFiles) { + const hiddenFile = resolve(hiddenPath, basename(f)); + const existing = tsSystem.readFile(hiddenFile); + const toWrite = tsSystem.readFile(f) || ''; + if (existing !== toWrite) { + tsSystem.writeFile(hiddenFile, toWrite); + } + newFiles.push(hiddenFile); + } + svelteTsxFiles = newFiles; + } + } catch (e) {} + } + + if (svelteHtmlPathExists) { + svelteTsxFiles.push(tsSystem.resolvePath(resolve(typesPath, svelteHtmlFile))); + } + + return svelteTsxFiles; +} diff --git a/packages/svelte2tsx/src/helpers/index.ts b/packages/svelte2tsx/src/helpers/index.ts index ab252d21c..57ed548b7 100644 --- a/packages/svelte2tsx/src/helpers/index.ts +++ b/packages/svelte2tsx/src/helpers/index.ts @@ -1,3 +1,4 @@ +import { get_global_types } from './files'; import { isHooksFile, isKitFile, @@ -23,5 +24,6 @@ export const internalHelpers = { upsertKitFile, toVirtualPos, toOriginalPos, - findExports + findExports, + get_global_types }; diff --git a/packages/typescript-plugin/src/index.ts b/packages/typescript-plugin/src/index.ts index ccdf87a20..65b9c5f12 100644 --- a/packages/typescript-plugin/src/index.ts +++ b/packages/typescript-plugin/src/index.ts @@ -12,6 +12,7 @@ import { importSvelteCompiler, isSvelteProject } from './utils'; +import { internalHelpers } from 'svelte2tsx'; function init(modules: { typescript: typeof ts }): ts.server.PluginModule { const configManager = new ConfigManager(); @@ -200,32 +201,27 @@ function init(modules: { typescript: typeof ts }): ts.server.PluginModule { } const svelteTsPath = dirname(require.resolve('svelte2tsx')); - const sveltePath = require.resolve( + const svelteCompilerPath = require.resolve( 'svelte/compiler', configFilePath ? { paths: [configFilePath] } : undefined ); - const VERSION = require(sveltePath).VERSION; - const isSvelte3 = VERSION.split('.')[0] === '3'; - const svelteHtmlDeclaration = isSvelte3 - ? undefined - : join(dirname(sveltePath), 'svelte-html.d.ts'); - const svelteHtmlFallbackIfNotExist = - svelteHtmlDeclaration && modules.typescript.sys.fileExists(svelteHtmlDeclaration) - ? svelteHtmlDeclaration - : './svelte-jsx-v4.d.ts'; - const svelteTsxFiles = ( - isSvelte3 - ? ['./svelte-shims.d.ts', './svelte-jsx.d.ts', './svelte-native-jsx.d.ts'] - : [ - './svelte-shims-v4.d.ts', - svelteHtmlFallbackIfNotExist, - './svelte-native-jsx.d.ts' - ] - ).map((f) => modules.typescript.sys.resolvePath(resolve(svelteTsPath, f))); - - resolvedSvelteTsxFiles = svelteTsxFiles; - - return svelteTsxFiles; + const sveltePath = dirname( + require.resolve( + 'svelte/package.json', + configFilePath ? { paths: [configFilePath] } : undefined + ) + ); + const VERSION = require(svelteCompilerPath).VERSION; + + resolvedSvelteTsxFiles = internalHelpers.get_global_types( + modules.typescript.sys, + VERSION.split('.')[0] === '3', + sveltePath, + svelteTsPath, + configFilePath + ); + + return resolvedSvelteTsxFiles; } function isSvelteProjectWithCache(project: ts.server.Project) {