Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 59 additions & 7 deletions packages/language-server/src/plugins/typescript/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
ensureRealSvelteFilePath,
findTsConfigPath,
getNearestWorkspaceUri,
hasTsExtensions
hasTsExtensions,
isSvelteFilePath
} from './utils';

export interface LanguageServiceContainer {
Expand Down Expand Up @@ -84,6 +85,7 @@ const extendedConfigToTsConfigPath = new FileMap<FileSet>();
const configFileModifiedTime = new FileMap<Date | undefined>();
const configFileForOpenFiles = new FileMap<string>();
const pendingReloads = new FileSet();
const documentRegistries = new Map<string, ts.DocumentRegistry>();

/**
* For testing only: Reset the cache for services.
Expand Down Expand Up @@ -295,7 +297,12 @@ async function createLanguageService(
hasInvalidatedResolutions: svelteModuleLoader.mightHaveInvalidatedResolutions
};

let languageService = ts.createLanguageService(host);
const documentRegistry = getOrCreateDocumentRegistry(
host.getCurrentDirectory(),
tsSystem.useCaseSensitiveFileNames
);

const languageService = ts.createLanguageService(host, documentRegistry);
const transformationConfig: SvelteSnapshotOptions = {
parse: svelteCompiler?.parse,
version: svelteCompiler?.VERSION,
Expand Down Expand Up @@ -366,11 +373,6 @@ async function createLanguageService(
const newSnapshot = DocumentSnapshot.fromDocument(document, transformationConfig);

snapshotManager.set(filePath, newSnapshot);
if (prevSnapshot && prevSnapshot.scriptKind !== newSnapshot.scriptKind) {
// Restart language service as it doesn't handle script kind changes.
languageService.dispose();
languageService = ts.createLanguageService(host);
}

return newSnapshot;
}
Expand Down Expand Up @@ -853,3 +855,53 @@ function scheduleReload(fileName: string) {
// where a file update is received before the service is reloaded, swallowing the update
pendingReloads.add(fileName);
}

function getOrCreateDocumentRegistry(
currentDirectory: string,
useCaseSensitiveFileNames: boolean
): ts.DocumentRegistry {
// unless it's a multi root workspace, there's only one registry
const key = [currentDirectory, useCaseSensitiveFileNames].join('|');

let registry = documentRegistries.get(key);
if (registry) {
return registry;
}

registry = ts.createDocumentRegistry(useCaseSensitiveFileNames, currentDirectory);

// impliedNodeFormat is always undefined when the svelte source file is created
// We might patched it later but the registry doesn't know about it
const releaseDocumentWithKey = registry.releaseDocumentWithKey;
registry.releaseDocumentWithKey = (
path: ts.Path,
key: ts.DocumentRegistryBucketKey,
scriptKind: ts.ScriptKind,
impliedNodeFormat?: ts.ResolutionMode
) => {
if (isSvelteFilePath(path)) {
releaseDocumentWithKey(path, key, scriptKind, undefined);
return;
}

releaseDocumentWithKey(path, key, scriptKind, impliedNodeFormat);
};

registry.releaseDocument = (
fileName: string,
compilationSettings: ts.CompilerOptions,
scriptKind: ts.ScriptKind,
impliedNodeFormat?: ts.ResolutionMode
) => {
if (isSvelteFilePath(fileName)) {
registry?.releaseDocument(fileName, compilationSettings, scriptKind, undefined);
return;
}

registry?.releaseDocument(fileName, compilationSettings, scriptKind, impliedNodeFormat);
};

documentRegistries.set(key, registry);

return registry;
}
47 changes: 47 additions & 0 deletions packages/language-server/test/plugins/typescript/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,53 @@ describe('service', () => {
});
});

it('patch release document so dispose do not throw', async () => {
// testing this because the patch rely on ts implementation details
// and we want to be aware of the changes

const dirPath = getRandomVirtualDirPath(testDir);
const { virtualSystem, lsDocumentContext, rootUris } = setup();

virtualSystem.writeFile(
path.join(dirPath, 'tsconfig.json'),
JSON.stringify({
compilerOptions: {
module: 'NodeNext',
moduleResolution: 'NodeNext'
}
})
);

const ls = await getService(
path.join(dirPath, 'random.svelte'),
rootUris,
lsDocumentContext
);

const document = new Document(pathToUrl(path.join(dirPath, 'random.svelte')), '');
document.openedByClient = true;
ls.updateSnapshot(document);

const document2 = new Document(
pathToUrl(path.join(dirPath, 'random2.svelte')),
'<script>import Random from "./random.svelte";</script>'
);
document.openedByClient = true;
ls.updateSnapshot(document2);

const lang = ls.getService();

lang.getProgram();

// ensure updated document also works
document2.update(' ', 0, 0);
lang.getProgram();

assert.doesNotThrow(() => {
lang.dispose();
});
});

function createReloadTester(
docContext: LanguageServiceDocumentContext,
testAfterReload: () => Promise<void>
Expand Down