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
parent 80ab111685
commit ca7a3af5e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 378 additions and 1 deletions

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();

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) {

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;

View File

@ -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 = {

View File

@ -0,0 +1,318 @@
currentDirectory:: / useCaseSensitiveFileNames: false
Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist
Before request
//// [/user/username/projects/project/a.ts]
/// <reference path="./b.ts"/>
//// [/user/username/projects/project/tsconfig.json]
{
"compilerOptions": {
"plugins": [
{
"name": "some-plugin"
}
]
}
}
//// [/a/lib/lib.d.ts]
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
Info seq [hh:mm:ss:mss] request:
{
"command": "open",
"arguments": {
"file": "/user/username/projects/project/a.ts"
},
"seq": 1,
"type": "request"
}
Info seq [hh:mm:ss:mss] Search path: /user/username/projects/project
Info seq [hh:mm:ss:mss] For info: /user/username/projects/project/a.ts :: Config file name: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] Creating configuration project /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/project/tsconfig.json 2000 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Config file
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "projectLoadingStart",
"body": {
"projectName": "/user/username/projects/project/tsconfig.json",
"reason": "Creating possible configured project for /user/username/projects/project/a.ts to open"
}
}
Info seq [hh:mm:ss:mss] Config: /user/username/projects/project/tsconfig.json : {
"rootNames": [
"/user/username/projects/project/a.ts"
],
"options": {
"plugins": [
{
"name": "some-plugin"
}
],
"configFilePath": "/user/username/projects/project/tsconfig.json"
}
}
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project 1 undefined Config: /user/username/projects/project/tsconfig.json WatchType: Wild card directory
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project 1 undefined Config: /user/username/projects/project/tsconfig.json WatchType: Wild card directory
Info seq [hh:mm:ss:mss] Enabling plugin some-plugin from candidate paths: /a/lib/tsc.js/../../..
Info seq [hh:mm:ss:mss] Loading some-plugin from /a/lib/tsc.js/../../.. (resolved to /a/lib/tsc.js/../../../node_modules)
Loading plugin: some-plugin
Info seq [hh:mm:ss:mss] Plugin validation succeeded
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined WatchType: Closed Script info
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/project/b.ts 500 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Missing file
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/node_modules/@types 1 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Type roots
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/node_modules/@types 1 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Type roots
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Type roots
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Type roots
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /user/username/projects/project/tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms
Info seq [hh:mm:ss:mss] Project '/user/username/projects/project/tsconfig.json' (Configured)
Info seq [hh:mm:ss:mss] Files (2)
/a/lib/lib.d.ts Text-1 "/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }"
/user/username/projects/project/a.ts SVC-1-0 "/// <reference path=\"./b.ts\"/>"
../../../../a/lib/lib.d.ts
Default library for target 'es5'
a.ts
Matched by default include pattern '**/*'
Info seq [hh:mm:ss:mss] -----------------------------------------------
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "projectLoadingFinish",
"body": {
"projectName": "/user/username/projects/project/tsconfig.json"
}
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "telemetry",
"body": {
"telemetryEventName": "projectInfo",
"payload": {
"projectId": "ff5803d884ff4e4485901596e00c181622d4efba4fec19a41fe48adf94ccdf94",
"fileStats": {
"js": 0,
"jsSize": 0,
"jsx": 0,
"jsxSize": 0,
"ts": 1,
"tsSize": 30,
"tsx": 0,
"tsxSize": 0,
"dts": 1,
"dtsSize": 334,
"deferred": 0,
"deferredSize": 0
},
"compilerOptions": {
"plugins": [
""
]
},
"typeAcquisition": {
"enable": false,
"include": false,
"exclude": false
},
"extends": false,
"files": false,
"include": false,
"exclude": false,
"compileOnSave": false,
"configFileName": "tsconfig.json",
"projectType": "configured",
"languageServiceEnabled": true,
"version": "FakeVersion"
}
}
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "configFileDiag",
"body": {
"triggerFile": "/user/username/projects/project/a.ts",
"configFile": "/user/username/projects/project/tsconfig.json",
"diagnostics": []
}
}
Info seq [hh:mm:ss:mss] Project '/user/username/projects/project/tsconfig.json' (Configured)
Info seq [hh:mm:ss:mss] Files (2)
Info seq [hh:mm:ss:mss] -----------------------------------------------
Info seq [hh:mm:ss:mss] Open files:
Info seq [hh:mm:ss:mss] FileName: /user/username/projects/project/a.ts ProjectRootPath: undefined
Info seq [hh:mm:ss:mss] Projects: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] response:
{
"responseRequired": false
}
After request
PolledWatches::
/user/username/projects/node_modules/@types: *new*
{"pollingInterval":500}
/user/username/projects/project/b.ts: *new*
{"pollingInterval":500}
/user/username/projects/project/node_modules/@types: *new*
{"pollingInterval":500}
FsWatches::
/a/lib/lib.d.ts: *new*
{}
/user/username/projects/project/tsconfig.json: *new*
{}
FsWatchesRecursive::
/user/username/projects/project: *new*
{}
Info seq [hh:mm:ss:mss] FileWatcher:: Triggered with /user/username/projects/project/b.ts 0:: WatchInfo: /user/username/projects/project/b.ts 500 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Missing file
Info seq [hh:mm:ss:mss] FileWatcher:: Close:: WatchInfo: /user/username/projects/project/b.ts 500 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Missing file
Info seq [hh:mm:ss:mss] Scheduled: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] Scheduled: *ensureProjectForOpenFiles*
Info seq [hh:mm:ss:mss] Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/project/b.ts 0:: WatchInfo: /user/username/projects/project/b.ts 500 undefined Project: /user/username/projects/project/tsconfig.json WatchType: Missing file
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Triggered with /user/username/projects/project/b.ts :: WatchInfo: /user/username/projects/project 1 undefined Config: /user/username/projects/project/tsconfig.json WatchType: Wild card directory
Info seq [hh:mm:ss:mss] Scheduled: /user/username/projects/project/tsconfig.json, Cancelled earlier one
Info seq [hh:mm:ss:mss] Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/project/b.ts :: WatchInfo: /user/username/projects/project 1 undefined Config: /user/username/projects/project/tsconfig.json WatchType: Wild card directory
Before request
//// [/user/username/projects/project/b.ts]
const y = 10;
PolledWatches::
/user/username/projects/node_modules/@types:
{"pollingInterval":500}
/user/username/projects/project/node_modules/@types:
{"pollingInterval":500}
PolledWatches *deleted*::
/user/username/projects/project/b.ts:
{"pollingInterval":500}
FsWatches::
/a/lib/lib.d.ts:
{}
/user/username/projects/project/tsconfig.json:
{}
FsWatchesRecursive::
/user/username/projects/project:
{}
Timeout callback:: count: 2
3: /user/username/projects/project/tsconfig.json *new*
4: *ensureProjectForOpenFiles* *new*
Info seq [hh:mm:ss:mss] request:
{
"command": "testProtocolCommand",
"arguments": "testProtocolCommandRequest",
"seq": 2,
"type": "request"
}
addProtocolHandler: {
"command": "testProtocolCommand",
"arguments": "testProtocolCommandRequest",
"seq": 2,
"type": "request"
}
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/project/b.ts 500 undefined WatchType: Closed Script info
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /user/username/projects/project/tsconfig.json Version: 2 structureChanged: true structureIsReused:: Not Elapsed:: *ms
Info seq [hh:mm:ss:mss] Project '/user/username/projects/project/tsconfig.json' (Configured)
Info seq [hh:mm:ss:mss] Files (3)
/a/lib/lib.d.ts Text-1 "/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }"
/user/username/projects/project/b.ts Text-1 "const y = 10;"
/user/username/projects/project/a.ts SVC-1-0 "/// <reference path=\"./b.ts\"/>"
../../../../a/lib/lib.d.ts
Default library for target 'es5'
b.ts
Referenced via './b.ts' from file 'a.ts'
Matched by default include pattern '**/*'
a.ts
Matched by default include pattern '**/*'
Info seq [hh:mm:ss:mss] -----------------------------------------------
Info seq [hh:mm:ss:mss] response:
{
"response": "testProtocolCommandResponse"
}
After request
PolledWatches::
/user/username/projects/node_modules/@types:
{"pollingInterval":500}
/user/username/projects/project/node_modules/@types:
{"pollingInterval":500}
FsWatches::
/a/lib/lib.d.ts:
{}
/user/username/projects/project/b.ts: *new*
{}
/user/username/projects/project/tsconfig.json:
{}
FsWatchesRecursive::
/user/username/projects/project:
{}
Before running Timeout callback:: count: 2
3: /user/username/projects/project/tsconfig.json
4: *ensureProjectForOpenFiles*
Info seq [hh:mm:ss:mss] Running: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] Running: *ensureProjectForOpenFiles*
Info seq [hh:mm:ss:mss] Before ensureProjectForOpenFiles:
Info seq [hh:mm:ss:mss] Project '/user/username/projects/project/tsconfig.json' (Configured)
Info seq [hh:mm:ss:mss] Files (3)
Info seq [hh:mm:ss:mss] -----------------------------------------------
Info seq [hh:mm:ss:mss] Open files:
Info seq [hh:mm:ss:mss] FileName: /user/username/projects/project/a.ts ProjectRootPath: undefined
Info seq [hh:mm:ss:mss] Projects: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] After ensureProjectForOpenFiles:
Info seq [hh:mm:ss:mss] Project '/user/username/projects/project/tsconfig.json' (Configured)
Info seq [hh:mm:ss:mss] Files (3)
Info seq [hh:mm:ss:mss] -----------------------------------------------
Info seq [hh:mm:ss:mss] Open files:
Info seq [hh:mm:ss:mss] FileName: /user/username/projects/project/a.ts ProjectRootPath: undefined
Info seq [hh:mm:ss:mss] Projects: /user/username/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] got projects updated in background /user/username/projects/project/a.ts
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "projectsUpdatedInBackground",
"body": {
"openFiles": [
"/user/username/projects/project/a.ts"
]
}
}
After running Timeout callback:: count: 0