From e4e3191d699ec0c7c5ac7ca845d282194619bf0b Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Fri, 1 Jul 2022 12:48:26 +0800 Subject: [PATCH 1/7] virtual system for testing, test case --- .../plugins/typescript/DocumentSnapshot.ts | 9 +- .../plugins/typescript/LSAndTSDocResolver.ts | 12 +- .../src/plugins/typescript/SnapshotManager.ts | 4 +- .../src/plugins/typescript/module-loader.ts | 5 +- .../src/plugins/typescript/service.ts | 6 +- .../src/plugins/typescript/svelte-sys.ts | 14 +- .../features/CompletionProvider.test.ts | 47 ++++++ .../plugins/typescript/module-loader.test.ts | 2 +- .../test/plugins/typescript/service.test.ts | 101 ++---------- .../plugins/typescript/svelte-sys.test.ts | 7 +- .../test/plugins/typescript/test-utils.ts | 153 ++++++++++++++++++ 11 files changed, 246 insertions(+), 114 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/test-utils.ts diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index d770daf53..285c30c45 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -96,12 +96,13 @@ export namespace DocumentSnapshot { export function fromFilePath( filePath: string, createDocument: (filePath: string, text: string) => Document, - options: SvelteSnapshotOptions + options: SvelteSnapshotOptions, + tsSystem: ts.System ) { if (isSvelteFilePath(filePath)) { return DocumentSnapshot.fromSvelteFilePath(filePath, createDocument, options); } else { - return DocumentSnapshot.fromNonSvelteFilePath(filePath); + return DocumentSnapshot.fromNonSvelteFilePath(filePath, tsSystem); } } @@ -110,7 +111,7 @@ export namespace DocumentSnapshot { * @param filePath path to the js/ts file * @param options options that apply in case it's a svelte file */ - export function fromNonSvelteFilePath(filePath: string) { + export function fromNonSvelteFilePath(filePath: string, tsSystem: ts.System) { let originalText = ''; // The following (very hacky) code makes sure that the ambient module definitions @@ -121,7 +122,7 @@ export namespace DocumentSnapshot { // on their own. const normalizedPath = filePath.replace(/\\/g, '/'); if (!normalizedPath.endsWith('node_modules/svelte/types/runtime/ambient.d.ts')) { - originalText = ts.sys.readFile(filePath) || ''; + originalText = tsSystem.readFile(filePath) || ''; } if ( normalizedPath.endsWith('svelte2tsx/svelte-shims.d.ts') || diff --git a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts index f926289ae..2f9cafe7d 100644 --- a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts +++ b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts @@ -1,3 +1,4 @@ +import { dirname } from 'path'; import ts from 'typescript'; import { TextDocumentContentChangeEvent } from 'vscode-languageserver'; import { Document, DocumentManager } from '../../lib/documents'; @@ -27,6 +28,7 @@ interface LSAndTSDocResolverOptions { onProjectReloaded?: () => void; watchTsConfig?: boolean; + tsSystem?: ts.System; } export class LSAndTSDocResolver { @@ -69,7 +71,7 @@ export class LSAndTSDocResolver { return document; }; - private globalSnapshotsManager = new GlobalSnapshotsManager(); + private globalSnapshotsManager = new GlobalSnapshotsManager(this.lsDocumentContext.tsSystem); private extendedConfigCache = new Map(); private get lsDocumentContext(): LanguageServiceDocumentContext { @@ -83,7 +85,7 @@ export class LSAndTSDocResolver { extendedConfigCache: this.extendedConfigCache, onProjectReloaded: this.options?.onProjectReloaded, watchTsConfig: !!this.options?.watchTsConfig, - tsSystem: ts.sys + tsSystem: this.options?.tsSystem ?? ts.sys }; } @@ -168,7 +170,11 @@ export class LSAndTSDocResolver { async getTSService(filePath?: string): Promise { if (this.options?.tsconfigPath) { - return getServiceForTsconfig(this.options?.tsconfigPath, this.lsDocumentContext); + return getServiceForTsconfig( + this.options?.tsconfigPath, + dirname(this.options.tsconfigPath), + this.lsDocumentContext + ); } if (!filePath) { throw new Error('Cannot call getTSService without filePath and without tsconfigPath'); diff --git a/packages/language-server/src/plugins/typescript/SnapshotManager.ts b/packages/language-server/src/plugins/typescript/SnapshotManager.ts index 23c73c404..e5fb6b57b 100644 --- a/packages/language-server/src/plugins/typescript/SnapshotManager.ts +++ b/packages/language-server/src/plugins/typescript/SnapshotManager.ts @@ -16,6 +16,8 @@ export class GlobalSnapshotsManager { private emitter = new EventEmitter(); private documents = new Map(); + constructor(private readonly tsSystem: ts.System) {} + get(fileName: string) { fileName = normalizePath(fileName); return this.documents.get(fileName); @@ -48,7 +50,7 @@ export class GlobalSnapshotsManager { this.emitter.emit('change', fileName, previousSnapshot); return previousSnapshot; } else { - const newSnapshot = DocumentSnapshot.fromNonSvelteFilePath(fileName); + const newSnapshot = DocumentSnapshot.fromNonSvelteFilePath(fileName, this.tsSystem); if (previousSnapshot) { newSnapshot.version = previousSnapshot.version + 1; diff --git a/packages/language-server/src/plugins/typescript/module-loader.ts b/packages/language-server/src/plugins/typescript/module-loader.ts index 7a31fbf33..e9eda52a7 100644 --- a/packages/language-server/src/plugins/typescript/module-loader.ts +++ b/packages/language-server/src/plugins/typescript/module-loader.ts @@ -121,9 +121,10 @@ class ImpliedNodeFormatResolver { */ export function createSvelteModuleLoader( getSnapshot: (fileName: string) => DocumentSnapshot, - compilerOptions: ts.CompilerOptions + compilerOptions: ts.CompilerOptions, + tsSystem: ts.System ) { - const svelteSys = createSvelteSys(getSnapshot); + const svelteSys = createSvelteSys(getSnapshot, tsSystem); const moduleCache = new ModuleResolutionCache(); const impliedNodeFormatResolver = new ImpliedNodeFormatResolver(); diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 2ad397c22..292996c90 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -263,7 +263,8 @@ async function createLanguageService( const newSnapshot = DocumentSnapshot.fromFilePath( filePath, docContext.createDocument, - transformationConfig + transformationConfig, + tsSystem ); snapshotManager.set(filePath, newSnapshot); return newSnapshot; @@ -281,7 +282,8 @@ async function createLanguageService( doc = DocumentSnapshot.fromFilePath( fileName, docContext.createDocument, - transformationConfig + transformationConfig, + tsSystem ); snapshotManager.set(fileName, doc); return doc; diff --git a/packages/language-server/src/plugins/typescript/svelte-sys.ts b/packages/language-server/src/plugins/typescript/svelte-sys.ts index 64a3c840d..56950c06d 100644 --- a/packages/language-server/src/plugins/typescript/svelte-sys.ts +++ b/packages/language-server/src/plugins/typescript/svelte-sys.ts @@ -5,14 +5,14 @@ import { ensureRealSvelteFilePath, isVirtualSvelteFilePath, toRealSvelteFilePath /** * This should only be accessed by TS svelte module resolution. */ -export function createSvelteSys(getSnapshot: (fileName: string) => DocumentSnapshot) { +export function createSvelteSys(getSnapshot: (fileName: string) => DocumentSnapshot, tsSystem: ts.System) { const fileExistsCache = new Map(); const svelteSys: ts.System & { deleteFromCache: (path: string) => void } = { - ...ts.sys, + ...tsSystem, fileExists(path: string) { path = ensureRealSvelteFilePath(path); - const exists = fileExistsCache.get(path) ?? ts.sys.fileExists(path); + const exists = fileExistsCache.get(path) ?? tsSystem.fileExists(path); fileExistsCache.set(path, exists); return exists; }, @@ -23,19 +23,19 @@ export function createSvelteSys(getSnapshot: (fileName: string) => DocumentSnaps readDirectory(path, extensions, exclude, include, depth) { const extensionsWithSvelte = (extensions ?? []).concat('.svelte'); - return ts.sys.readDirectory(path, extensionsWithSvelte, exclude, include, depth); + return tsSystem.readDirectory(path, extensionsWithSvelte, exclude, include, depth); }, deleteFile(path) { fileExistsCache.delete(ensureRealSvelteFilePath(path)); - return ts.sys.deleteFile?.(path); + return tsSystem.deleteFile?.(path); }, deleteFromCache(path) { fileExistsCache.delete(ensureRealSvelteFilePath(path)); } }; - if (ts.sys.realpath) { - const realpath = ts.sys.realpath; + if (tsSystem.realpath) { + const realpath = tsSystem.realpath; svelteSys.realpath = function (path) { if (isVirtualSvelteFilePath(path)) { return realpath(toRealSvelteFilePath(path)) + '.ts'; diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts index 09c55fce8..21665d11e 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -23,6 +23,7 @@ import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDo import { sortBy } from 'lodash'; import { LSConfigManager } from '../../../../src/ls-config'; import { __resetCache } from '../../../../src/plugins/typescript/service'; +import { getRandomVirtualDirPath, setupVirtualEnvironment } from '../test-utils'; const testDir = join(__dirname, '..'); const testFilesDir = join(testDir, 'testfiles', 'completions'); @@ -1320,6 +1321,52 @@ function test(useNewTransformation: boolean) { ); }); + it('can auto import in workspace without tsconfig/jsconfig', async () => { + const virtualTestDir = getRandomVirtualDirPath(testFilesDir); + const { docManager, document, lsAndTsDocResolver, lsConfigManager, virtualSystem } = + setupVirtualEnvironment({ + filename: 'index.svelte', + fileContent: '', + testDir: virtualTestDir, + useNewTransformation + }); + + const mockPackageDir = join(virtualTestDir, 'node_modules', '@types/random-package'); + + // the main problem is how ts resolve reference type directive + // it would start with relative url and failed to auto import + virtualSystem.writeFile( + join(mockPackageDir, 'index.d.ts'), + '/// ' + '\nexport function bar(): string' + ); + + virtualSystem.writeFile( + join(virtualTestDir, 'node_modules', '@types', 'random-package2', 'index.d.ts'), + 'declare function foo(): string\n' + 'export = foo' + ); + + const completionProvider = new CompletionsProviderImpl( + lsAndTsDocResolver, + lsConfigManager + ); + + // let the language service aware of random-package and random-package2 + docManager.openDocument({ + text: '', + uri: pathToUrl(join(virtualTestDir, 'test.svelte')) + }); + + const completions = await completionProvider.getCompletions(document, { + line: 0, + character: 2 + }); + const item = completions?.items.find((item) => item.label === 'Test'); + + const { detail } = await completionProvider.resolveCompletion(document, item!); + + assert.strictEqual(detail, 'Auto import from random-package2\nfunction foo(): string'); + }); + // Hacky, but it works. Needed due to testing both new and old transformation after(() => { __resetCache(); diff --git a/packages/language-server/test/plugins/typescript/module-loader.test.ts b/packages/language-server/test/plugins/typescript/module-loader.test.ts index 5ecbbe150..7de407bba 100644 --- a/packages/language-server/test/plugins/typescript/module-loader.test.ts +++ b/packages/language-server/test/plugins/typescript/module-loader.test.ts @@ -24,7 +24,7 @@ describe('createSvelteModuleLoader', () => { sinon.stub(svS, 'createSvelteSys').returns(svelteSys); const compilerOptions: ts.CompilerOptions = { strict: true, paths: { '/@/*': [] } }; - const moduleResolver = createSvelteModuleLoader(getSvelteSnapshotStub, compilerOptions); + const moduleResolver = createSvelteModuleLoader(getSvelteSnapshotStub, compilerOptions, ts.sys); return { getSvelteSnapshotStub, diff --git a/packages/language-server/test/plugins/typescript/service.test.ts b/packages/language-server/test/plugins/typescript/service.test.ts index b6d20534e..65402456f 100644 --- a/packages/language-server/test/plugins/typescript/service.test.ts +++ b/packages/language-server/test/plugins/typescript/service.test.ts @@ -1,80 +1,20 @@ -import path from 'path'; import assert from 'assert'; -import ts, { FileWatcherEventKind } from 'typescript'; +import path from 'path'; +import ts from 'typescript'; import { Document } from '../../../src/lib/documents'; import { getService, LanguageServiceDocumentContext } from '../../../src/plugins/typescript/service'; import { GlobalSnapshotsManager } from '../../../src/plugins/typescript/SnapshotManager'; -import { normalizePath, pathToUrl } from '../../../src/utils'; +import { pathToUrl } from '../../../src/utils'; +import { createVirtualTsSystem, getRandomVirtualDirPath } from './test-utils'; describe('service', () => { const testDir = path.join(__dirname, 'testfiles'); function setup() { - const virtualFs = new Map(); - // array behave more similar to the actual fs event than Set - const watchers = new Map(); - const watchTimeout = new Map>>(); - - const virtualSystem: ts.System = { - ...ts.sys, - writeFile(path, data) { - const normalizedPath = normalizePath(path); - const existsBefore = virtualFs.has(normalizedPath); - virtualFs.set(normalizedPath, data); - triggerWatch( - normalizedPath, - existsBefore ? ts.FileWatcherEventKind.Changed : ts.FileWatcherEventKind.Created - ); - }, - readFile(path) { - return virtualFs.get(normalizePath(path)); - }, - fileExists(path) { - return virtualFs.has(normalizePath(path)); - }, - deleteFile(path) { - const normalizedPath = normalizePath(path); - const existsBefore = virtualFs.has(normalizedPath); - virtualFs.delete(normalizedPath); - - if (existsBefore) { - triggerWatch(normalizedPath, ts.FileWatcherEventKind.Deleted); - } - }, - watchFile(path, callback) { - const normalizedPath = normalizePath(path); - let watchersOfPath = watchers.get(normalizedPath); - - if (!watchersOfPath) { - watchersOfPath = []; - watchers.set(normalizedPath, watchersOfPath); - } - - watchersOfPath.push(callback); - - return { - close() { - const watchersOfPath = watchers.get(normalizedPath); - - if (watchersOfPath) { - watchers.set( - normalizedPath, - watchersOfPath.filter((watcher) => watcher === callback) - ); - } - - const timeouts = watchTimeout.get(normalizedPath); - - if (timeouts != null) { - timeouts.forEach((timeout) => clearTimeout(timeout)); - } - } - }; - } - }; + const virtualSystem = createVirtualTsSystem(testDir); const lsDocumentContext: LanguageServiceDocumentContext = { ambientTypesSource: 'svelte2tsx', @@ -82,7 +22,7 @@ describe('service', () => { return new Document(pathToUrl(fileName), content); }, extendedConfigCache: new Map(), - globalSnapshotsManager: new GlobalSnapshotsManager(), + globalSnapshotsManager: new GlobalSnapshotsManager(virtualSystem), transformOnTemplateError: true, tsSystem: virtualSystem, useNewTransformation: true, @@ -94,33 +34,10 @@ describe('service', () => { const rootUris = [pathToUrl(testDir)]; return { virtualSystem, lsDocumentContext, rootUris }; - - function triggerWatch(normalizedPath: string, kind: FileWatcherEventKind) { - let timeoutsOfPath = watchTimeout.get(normalizedPath); - - if (!timeoutsOfPath) { - timeoutsOfPath = []; - watchTimeout.set(normalizedPath, timeoutsOfPath); - } - - timeoutsOfPath.push( - setTimeout( - () => - watchers - .get(normalizedPath) - ?.forEach((callback) => callback(normalizedPath, kind)), - 0 - ) - ); - } - } - - function getRandomVirtualDirPath() { - return path.join(testDir, `virtual-path-${Math.floor(Math.random() * 100_000)}`); } it('can find tsconfig and override with default config', async () => { - const dirPath = getRandomVirtualDirPath(); + const dirPath = getRandomVirtualDirPath(testDir); const { virtualSystem, lsDocumentContext, rootUris } = setup(); virtualSystem.writeFile( @@ -181,7 +98,7 @@ describe('service', () => { } it('can watch tsconfig', async () => { - const dirPath = getRandomVirtualDirPath(); + const dirPath = getRandomVirtualDirPath(testDir); const { virtualSystem, lsDocumentContext, rootUris } = setup(); const tsconfigPath = path.join(dirPath, 'tsconfig.json'); @@ -226,7 +143,7 @@ describe('service', () => { }); it('can watch extended tsconfig', async () => { - const dirPath = getRandomVirtualDirPath(); + const dirPath = getRandomVirtualDirPath(testDir); const { virtualSystem, lsDocumentContext, rootUris } = setup(); const tsconfigPath = path.join(dirPath, 'tsconfig.json'); const extend = './.svelte-kit/tsconfig.json'; diff --git a/packages/language-server/test/plugins/typescript/svelte-sys.test.ts b/packages/language-server/test/plugins/typescript/svelte-sys.test.ts index f74c08882..3d0287c57 100644 --- a/packages/language-server/test/plugins/typescript/svelte-sys.test.ts +++ b/packages/language-server/test/plugins/typescript/svelte-sys.test.ts @@ -23,8 +23,11 @@ describe('Svelte Sys', () => { } ); - sinon.replace(ts.sys, 'fileExists', fileExistsStub); - const loader = createSvelteSys(getSnapshotStub); + // sinon.replace(ts.sys, 'fileExists', fileExistsStub); + const loader = createSvelteSys(getSnapshotStub, { + ...ts.sys, + fileExists: fileExistsStub + }); return { tsFile, diff --git a/packages/language-server/test/plugins/typescript/test-utils.ts b/packages/language-server/test/plugins/typescript/test-utils.ts new file mode 100644 index 000000000..850a1cd12 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/test-utils.ts @@ -0,0 +1,153 @@ +import path, { isAbsolute, join } from 'path'; +import ts from 'typescript'; +import { DocumentManager, Document } from '../../../src/lib/documents'; +import { LSConfigManager } from '../../../src/ls-config'; +import { LSAndTSDocResolver } from '../../../src/plugins'; +import { normalizePath, pathToUrl } from '../../../src/utils'; + +export function createVirtualTsSystem(currentDirectory: string): ts.System { + const virtualFs = new Map(); + // array behave more similar to the actual fs event than Set + const watchers = new Map(); + const watchTimeout = new Map>>(); + + function toAbsolute(path: string) { + return isAbsolute(path) ? path : join(currentDirectory, path); + } + + const virtualSystem: ts.System = { + ...ts.sys, + getCurrentDirectory() { + return currentDirectory; + }, + writeFile(path, data) { + const normalizedPath = normalizePath(toAbsolute(path)); + const existsBefore = virtualFs.has(normalizedPath); + virtualFs.set(normalizedPath, data); + triggerWatch( + normalizedPath, + existsBefore ? ts.FileWatcherEventKind.Changed : ts.FileWatcherEventKind.Created + ); + }, + readFile(path) { + return virtualFs.get(normalizePath(toAbsolute(path))); + }, + fileExists(path) { + return virtualFs.has(normalizePath(toAbsolute(path))); + }, + directoryExists(path) { + const normalizedPath = normalizePath(toAbsolute(path)); + return Array.from(virtualFs.keys()).some(fileName => fileName.startsWith(normalizedPath)); + }, + deleteFile(path) { + const normalizedPath = normalizePath(toAbsolute(path)); + const existsBefore = virtualFs.has(normalizedPath); + virtualFs.delete(normalizedPath); + + if (existsBefore) { + triggerWatch(normalizedPath, ts.FileWatcherEventKind.Deleted); + } + }, + watchFile(path, callback) { + const normalizedPath = normalizePath(toAbsolute(path)); + let watchersOfPath = watchers.get(normalizedPath); + + if (!watchersOfPath) { + watchersOfPath = []; + watchers.set(normalizedPath, watchersOfPath); + } + + watchersOfPath.push(callback); + + return { + close() { + const watchersOfPath = watchers.get(normalizedPath); + + if (watchersOfPath) { + watchers.set( + normalizedPath, + watchersOfPath.filter((watcher) => watcher === callback) + ); + } + + const timeouts = watchTimeout.get(normalizedPath); + + if (timeouts != null) { + timeouts.forEach((timeout) => clearTimeout(timeout)); + } + } + }; + } + }; + + return virtualSystem; + + function triggerWatch(normalizedPath: string, kind: ts.FileWatcherEventKind) { + let timeoutsOfPath = watchTimeout.get(normalizedPath); + + if (!timeoutsOfPath) { + timeoutsOfPath = []; + watchTimeout.set(normalizedPath, timeoutsOfPath); + } + + timeoutsOfPath.push( + setTimeout( + () => + watchers + .get(normalizedPath) + ?.forEach((callback) => callback(normalizedPath, kind)), + 0 + ) + ); + } +} + +export function getRandomVirtualDirPath(testDir: string) { + return path.join(testDir, `virtual-path-${Math.floor(Math.random() * 100_000)}`); +} + +interface VirtualEnvironmentOptions { + testDir: string; + filename: string; + useNewTransformation: boolean; + fileContent: string; +} + +export function setupVirtualEnvironment({ + testDir, + fileContent, + filename, + useNewTransformation +}: VirtualEnvironmentOptions) { + const docManager = new DocumentManager( + (textDocument) => new Document(textDocument.uri, textDocument.text) + ); + + const lsConfigManager = new LSConfigManager(); + lsConfigManager.update({ svelte: { useNewTransformation } }); + + const virtualSystem = createVirtualTsSystem(testDir); + const lsAndTsDocResolver = new LSAndTSDocResolver( + docManager, + [pathToUrl(testDir)], + lsConfigManager, + { + tsSystem: virtualSystem + } + ); + + const filePath = join(testDir, filename); + virtualSystem.writeFile(filePath, fileContent); + const document = docManager.openDocument({ + uri: pathToUrl(filePath), + text: virtualSystem.readFile(filePath) || '' + }); + + return { + lsAndTsDocResolver, + document, + docManager, + virtualSystem, + lsConfigManager + }; +} From e19c5e02044b5b7553167d91180b4f76125ba38c Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Fri, 1 Jul 2022 12:52:29 +0800 Subject: [PATCH 2/7] adjust test to be more specific --- .../plugins/typescript/features/CompletionProvider.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts index 21665d11e..b3c89bca7 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -1358,9 +1358,9 @@ function test(useNewTransformation: boolean) { const completions = await completionProvider.getCompletions(document, { line: 0, - character: 2 + character: 9 }); - const item = completions?.items.find((item) => item.label === 'Test'); + const item = completions?.items.find((item) => item.label === 'foo'); const { detail } = await completionProvider.resolveCompletion(document, item!); From bf07b3dba21166aa99f517074c19f323fc9ee92a Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Fri, 1 Jul 2022 12:53:10 +0800 Subject: [PATCH 3/7] always has workspace path --- .../src/plugins/typescript/service.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 292996c90..ac1019b0e 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -5,7 +5,7 @@ import { getPackageInfo } from '../../importPackage'; import { Document } from '../../lib/documents'; import { configLoader } from '../../lib/documents/configLoader'; import { Logger } from '../../logger'; -import { normalizePath } from '../../utils'; +import { normalizePath, urlToPath } from '../../utils'; import { DocumentSnapshot, SvelteSnapshotOptions } from './DocumentSnapshot'; import { createSvelteModuleLoader } from './module-loader'; import { @@ -13,7 +13,7 @@ import { ignoredBuildDirectories, SnapshotManager } from './SnapshotManager'; -import { ensureRealSvelteFilePath, findTsConfigPath, hasTsExtensions } from './utils'; +import { ensureRealSvelteFilePath, findTsConfigPath, hasTsExtensions, isSubPath } from './utils'; export interface LanguageServiceContainer { readonly tsconfigPath: string; @@ -78,7 +78,18 @@ export async function getService( docContext: LanguageServiceDocumentContext ): Promise { const tsconfigPath = findTsConfigPath(path, workspaceUris, docContext.tsSystem.fileExists); - return getServiceForTsconfig(tsconfigPath, docContext); + + if (tsconfigPath) { + return getServiceForTsconfig(tsconfigPath, dirname(tsconfigPath), docContext); + } + + const nearestWorkspaceUri = workspaceUris.find((workspaceUri) => isSubPath(workspaceUri, path)); + + return getServiceForTsconfig( + tsconfigPath, + (nearestWorkspaceUri && urlToPath(nearestWorkspaceUri)) ?? '', + docContext + ); } export async function forAllServices( @@ -95,11 +106,14 @@ export async function forAllServices( */ export async function getServiceForTsconfig( tsconfigPath: string, + workspacePath: string, docContext: LanguageServiceDocumentContext ): Promise { + const tsconfigPathOrWorkspacePath = tsconfigPath || workspacePath; + let service: LanguageServiceContainer; - if (services.has(tsconfigPath)) { - service = await services.get(tsconfigPath)!; + if (services.has(tsconfigPathOrWorkspacePath)) { + service = await services.get(tsconfigPathOrWorkspacePath)!; } else { const reloading = pendingReloads.has(tsconfigPath); @@ -110,8 +124,8 @@ export async function getServiceForTsconfig( } pendingReloads.delete(tsconfigPath); - const newService = createLanguageService(tsconfigPath, docContext); - services.set(tsconfigPath, newService); + const newService = createLanguageService(tsconfigPath, workspacePath, docContext); + services.set(tsconfigPathOrWorkspacePath, newService); service = await newService; } @@ -120,9 +134,9 @@ export async function getServiceForTsconfig( async function createLanguageService( tsconfigPath: string, + workspacePath: string, docContext: LanguageServiceDocumentContext ): Promise { - const workspacePath = tsconfigPath ? dirname(tsconfigPath) : ''; const { tsSystem } = docContext; const { @@ -137,7 +151,7 @@ async function createLanguageService( docContext.globalSnapshotsManager, files, raw, - workspacePath || process.cwd() + workspacePath ); // Load all configs within the tsconfig scope and the one above so that they are all loaded @@ -145,7 +159,7 @@ async function createLanguageService( // the default language. await configLoader.loadConfigs(workspacePath); - const svelteModuleLoader = createSvelteModuleLoader(getSnapshot, compilerOptions); + const svelteModuleLoader = createSvelteModuleLoader(getSnapshot, compilerOptions, tsSystem); let svelteTsPath: string; try { From 532f438d270861bcbd0c8a2bee3ff870fd6e9bbe Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Fri, 1 Jul 2022 12:58:57 +0800 Subject: [PATCH 4/7] format --- .../language-server/src/plugins/typescript/svelte-sys.ts | 5 ++++- .../test/plugins/typescript/module-loader.test.ts | 6 +++++- .../language-server/test/plugins/typescript/test-utils.ts | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/svelte-sys.ts b/packages/language-server/src/plugins/typescript/svelte-sys.ts index 56950c06d..ff5d8b088 100644 --- a/packages/language-server/src/plugins/typescript/svelte-sys.ts +++ b/packages/language-server/src/plugins/typescript/svelte-sys.ts @@ -5,7 +5,10 @@ import { ensureRealSvelteFilePath, isVirtualSvelteFilePath, toRealSvelteFilePath /** * This should only be accessed by TS svelte module resolution. */ -export function createSvelteSys(getSnapshot: (fileName: string) => DocumentSnapshot, tsSystem: ts.System) { +export function createSvelteSys( + getSnapshot: (fileName: string) => DocumentSnapshot, + tsSystem: ts.System +) { const fileExistsCache = new Map(); const svelteSys: ts.System & { deleteFromCache: (path: string) => void } = { diff --git a/packages/language-server/test/plugins/typescript/module-loader.test.ts b/packages/language-server/test/plugins/typescript/module-loader.test.ts index 7de407bba..34f41b409 100644 --- a/packages/language-server/test/plugins/typescript/module-loader.test.ts +++ b/packages/language-server/test/plugins/typescript/module-loader.test.ts @@ -24,7 +24,11 @@ describe('createSvelteModuleLoader', () => { sinon.stub(svS, 'createSvelteSys').returns(svelteSys); const compilerOptions: ts.CompilerOptions = { strict: true, paths: { '/@/*': [] } }; - const moduleResolver = createSvelteModuleLoader(getSvelteSnapshotStub, compilerOptions, ts.sys); + const moduleResolver = createSvelteModuleLoader( + getSvelteSnapshotStub, + compilerOptions, + ts.sys + ); return { getSvelteSnapshotStub, diff --git a/packages/language-server/test/plugins/typescript/test-utils.ts b/packages/language-server/test/plugins/typescript/test-utils.ts index 850a1cd12..9672deb14 100644 --- a/packages/language-server/test/plugins/typescript/test-utils.ts +++ b/packages/language-server/test/plugins/typescript/test-utils.ts @@ -37,7 +37,9 @@ export function createVirtualTsSystem(currentDirectory: string): ts.System { }, directoryExists(path) { const normalizedPath = normalizePath(toAbsolute(path)); - return Array.from(virtualFs.keys()).some(fileName => fileName.startsWith(normalizedPath)); + return Array.from(virtualFs.keys()).some((fileName) => + fileName.startsWith(normalizedPath) + ); }, deleteFile(path) { const normalizedPath = normalizePath(toAbsolute(path)); From b1d3c972068fbdc2e50787600aef89ee8d50d243 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Fri, 1 Jul 2022 13:22:19 +0800 Subject: [PATCH 5/7] comment grammar --- .../test/plugins/typescript/features/CompletionProvider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts index b3c89bca7..e122711e8 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -1334,7 +1334,7 @@ function test(useNewTransformation: boolean) { const mockPackageDir = join(virtualTestDir, 'node_modules', '@types/random-package'); // the main problem is how ts resolve reference type directive - // it would start with relative url and failed to auto import + // it would start with a relative url and fail to auto import virtualSystem.writeFile( join(mockPackageDir, 'index.d.ts'), '/// ' + '\nexport function bar(): string' From 61c55c097d4e0b8bfe5e89d9effe4ffcc93bf3d8 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Fri, 1 Jul 2022 13:29:12 +0800 Subject: [PATCH 6/7] play it more safe --- packages/language-server/src/plugins/typescript/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index ac1019b0e..48b526fb7 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -87,7 +87,7 @@ export async function getService( return getServiceForTsconfig( tsconfigPath, - (nearestWorkspaceUri && urlToPath(nearestWorkspaceUri)) ?? '', + (nearestWorkspaceUri && urlToPath(nearestWorkspaceUri)) ?? ts.sys.getCurrentDirectory(), docContext ); } From 959b17f429ba2852b65495748998a5dee657c0a9 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Fri, 1 Jul 2022 13:31:44 +0800 Subject: [PATCH 7/7] use tsSystem provided in docContext --- packages/language-server/src/plugins/typescript/service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 48b526fb7..18d514a2c 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -87,7 +87,8 @@ export async function getService( return getServiceForTsconfig( tsconfigPath, - (nearestWorkspaceUri && urlToPath(nearestWorkspaceUri)) ?? ts.sys.getCurrentDirectory(), + (nearestWorkspaceUri && urlToPath(nearestWorkspaceUri)) ?? + docContext.tsSystem.getCurrentDirectory(), docContext ); }