Always update LS state through Project (#56356)

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
Sheetal Nandi
2023-11-10 12:45:39 -08:00
committed by GitHub
co-authored by Daniel Rosenwasser
parent 80ab111685
commit ca7a3af5e6
5 changed files with 378 additions and 1 deletions
+11 -1
View File
@@ -1330,6 +1330,14 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
this.hasAddedOrRemovedSymlinks = true;
}
/** @internal */
updateFromProjectInProgress = false;
/** @internal */
updateFromProject() {
updateProjectIfDirty(this);
}
/**
* Updates set of files that contribute to this project
* @returns: true if set of files in the project stays the same and false - otherwise.
@@ -1523,8 +1531,10 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
this.hasInvalidatedResolutions = hasInvalidatedResolutions;
this.hasInvalidatedLibResolutions = hasInvalidatedLibResolutions;
this.resolutionCache.startCachingPerDirectoryResolution();
this.program = this.languageService.getProgram(); // TODO: GH#18217
this.dirty = false;
this.updateFromProjectInProgress = true;
this.program = this.languageService.getProgram(); // TODO: GH#18217
this.updateFromProjectInProgress = false;
tracing?.push(tracing.Phase.Session, "finishCachingPerDirectoryResolution");
this.resolutionCache.finishCachingPerDirectoryResolution(this.program, oldProgram);
tracing?.pop();
+9
View File
@@ -1620,6 +1620,15 @@ export function createLanguageService(
}
function synchronizeHostData(): void {
if (host.updateFromProject && !host.updateFromProjectInProgress) {
host.updateFromProject();
}
else {
synchronizeHostDataWorker();
}
}
function synchronizeHostDataWorker(): void {
Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic);
// perform fast check if host supports it
if (host.getProjectVersion) {
+3
View File
@@ -316,6 +316,9 @@ export interface IncompleteCompletionsCache {
export interface LanguageServiceHost extends GetEffectiveTypeRootsHost, MinimalResolutionCacheHost {
getCompilationSettings(): CompilerOptions;
getNewLine?(): string;
/** @internal */ updateFromProject?(): void;
/** @internal */ updateFromProjectInProgress?: boolean;
getProjectVersion?(): string;
getScriptFileNames(): string[];
getScriptKind?(fileName: string): ScriptKind;
@@ -28,6 +28,8 @@ describe("unittests:: tsserver:: plugins:: loading", () => {
create(info: ts.server.PluginCreateInfo) {
info.session?.addProtocolHandler(testProtocolCommand, request => {
session.logger.log(`addProtocolHandler: ${jsonToReadableText(request)}`);
// Assume this one needs program
info.languageService.getProgram();
return {
response: testProtocolCommandResponse,
};
@@ -101,6 +103,41 @@ describe("unittests:: tsserver:: plugins:: loading", () => {
baselineTsserverLogs("plugins", "With session and custom protocol message", session);
});
it("when plugins use LS to get program and update is pending", () => {
const pluginName = "some-plugin";
const aTs: File = {
path: "/user/username/projects/project/a.ts",
content: `/// <reference path="./b.ts"/>`,
};
const tsconfig: File = {
path: "/user/username/projects/project/tsconfig.json",
content: jsonToReadableText({
compilerOptions: {
plugins: [
{ name: pluginName },
],
},
}),
};
const { session, host } = createHostWithPlugin([aTs, tsconfig, libFile]);
openFilesForSession([aTs], session);
// Write the missing file (referenced by 'a.ts') to schedule an update.
host.writeFile("/user/username/projects/project/b.ts", "const y = 10;");
// This should update the language service with a new program.
session.executeCommandSeq({
command: testProtocolCommand,
arguments: testProtocolCommandRequest,
});
// This results in a program update.
host.runQueuedTimeoutCallbacks();
baselineTsserverLogs("plugins", "when plugins use LS to get program and update is pending", session);
});
it("gets external files with config file reload", () => {
const aTs: File = { path: `/user/username/projects/myproject/a.ts`, content: `export const x = 10;` };
const tsconfig: File = {