Skip to content

Commit

Permalink
fix: prevent crash in moduleResolution Node16+ (#2230)
Browse files Browse the repository at this point in the history
Also remove the "restart on scriptkind change", TS supports it for a while now
  • Loading branch information
jasonlyu123 authored Dec 13, 2023
1 parent 3f7f27b commit bc82064
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 7 deletions.
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

0 comments on commit bc82064

Please sign in to comment.