Send even for ProjectLoadStart and ProjectLoadFinish

Fixes #27206
This commit is contained in:
Sheetal Nandi 2018-10-05 12:20:48 -07:00
parent c080324974
commit 37e25c8873
6 changed files with 336 additions and 73 deletions

View File

@ -5,6 +5,8 @@ namespace ts.server {
// tslint:disable variable-name
export const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground";
export const ProjectLoadingStartEvent = "projectLoadingStart";
export const ProjectLoadingFinishEvent = "projectLoadingFinish";
export const SurveyReady = "surveyReady";
export const LargeFileReferencedEvent = "largeFileReferenced";
export const ConfigFileDiagEvent = "configFileDiag";
@ -18,6 +20,16 @@ namespace ts.server {
data: { openFiles: string[]; };
}
export interface ProjectLoadingStartEvent {
eventName: typeof ProjectLoadingStartEvent;
data: { project: Project; reason: string; };
}
export interface ProjectLoadingFinishEvent {
eventName: typeof ProjectLoadingFinishEvent;
data: { project: Project; };
}
export interface SurveyReady {
eventName: typeof SurveyReady;
data: { surveyId: string; };
@ -122,7 +134,15 @@ namespace ts.server {
readonly checkJs: boolean;
}
export type ProjectServiceEvent = LargeFileReferencedEvent | SurveyReady | ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
export type ProjectServiceEvent = LargeFileReferencedEvent |
SurveyReady |
ProjectsUpdatedInBackgroundEvent |
ProjectLoadingStartEvent |
ProjectLoadingFinishEvent |
ConfigFileDiagEvent |
ProjectLanguageServiceStateEvent |
ProjectInfoTelemetryEvent |
OpenFileInfoTelemetryEvent;
export type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
@ -721,6 +741,33 @@ namespace ts.server {
this.eventHandler(event);
}
/* @internal */
sendProjectLoadingStartEvent(project: ConfiguredProject, reason: string) {
if (!this.eventHandler) {
return;
}
project.sendLoadingProjectFinish = true;
const event: ProjectLoadingStartEvent = {
eventName: ProjectLoadingStartEvent,
data: { project, reason }
};
this.eventHandler(event);
}
/* @internal */
sendProjectLoadingFinishEvent(project: ConfiguredProject) {
if (!this.eventHandler || !project.sendLoadingProjectFinish) {
return;
}
project.sendLoadingProjectFinish = false;
const event: ProjectLoadingFinishEvent = {
eventName: ProjectLoadingFinishEvent,
data: { project }
};
this.eventHandler(event);
}
/* @internal */
delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project: Project) {
this.delayUpdateProjectGraph(project);
@ -955,6 +1002,7 @@ namespace ts.server {
else {
this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = "Change in config file detected";
this.delayUpdateProjectGraph(project);
// As we scheduled the update on configured project graph,
// we would need to schedule the project reload for only the root of inferred projects
@ -1613,22 +1661,23 @@ namespace ts.server {
}
/* @internal */
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath) {
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath, reason: string) {
const project = this.createConfiguredProject(configFileName);
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = reason;
return project;
}
/* @internal */
private createAndLoadConfiguredProject(configFileName: NormalizedPath) {
private createAndLoadConfiguredProject(configFileName: NormalizedPath, reason: string) {
const project = this.createConfiguredProject(configFileName);
this.loadConfiguredProject(project);
this.loadConfiguredProject(project, reason);
return project;
}
/* @internal */
private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath) {
const project = this.createAndLoadConfiguredProject(configFileName);
private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath, reason: string) {
const project = this.createAndLoadConfiguredProject(configFileName, reason);
project.updateGraph();
return project;
}
@ -1637,7 +1686,9 @@ namespace ts.server {
* Read the config file of the project, and update the project root file names.
*/
/* @internal */
private loadConfiguredProject(project: ConfiguredProject) {
private loadConfiguredProject(project: ConfiguredProject, reason: string) {
this.sendProjectLoadingStartEvent(project, reason);
// Read updated contents from disk
const configFilename = normalizePath(project.getConfigFilePath());
@ -1776,7 +1827,7 @@ namespace ts.server {
* Read the config file of the project again by clearing the cache and update the project graph
*/
/* @internal */
reloadConfiguredProject(project: ConfiguredProject) {
reloadConfiguredProject(project: ConfiguredProject, reason: string) {
// At this point, there is no reason to not have configFile in the host
const host = project.getCachedDirectoryStructureHost();
@ -1786,7 +1837,7 @@ namespace ts.server {
this.logger.info(`Reloading configured project ${configFileName}`);
// Load project from the disk
this.loadConfiguredProject(project);
this.loadConfiguredProject(project, reason);
project.updateGraph();
this.sendConfigFileDiagEvent(project, configFileName);
@ -2139,7 +2190,6 @@ namespace ts.server {
if (project.hasExternalProjectRef() &&
project.pendingReload === ConfigFileProgramReloadLevel.Full &&
!this.pendingProjectUpdates.has(project.getProjectName())) {
this.loadConfiguredProject(project);
project.updateGraph();
}
});
@ -2171,7 +2221,7 @@ namespace ts.server {
// as there is no need to load contents of the files from the disk
// Reload Projects
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue);
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue, "User requested reload projects");
this.ensureProjectForOpenFiles();
}
@ -2182,7 +2232,8 @@ namespace ts.server {
/*delayReload*/ true,
ignoreIfNotRootOfInferredProject ?
isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project
returnTrue // Reload all the open files impacted by config file
returnTrue, // Reload all the open files impacted by config file
"Change in config file detected"
);
this.delayEnsureProjectForOpenFiles();
}
@ -2194,7 +2245,7 @@ namespace ts.server {
* If the there is no existing project it just opens the configured project for the config file
* reloadForInfo provides a way to filter out files to reload configured project for
*/
private reloadConfiguredProjectForFiles<T>(openFiles: Map<T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean) {
private reloadConfiguredProjectForFiles<T>(openFiles: Map<T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) {
const updatedProjects = createMap<true>();
// try to reload config file for all open files
openFiles.forEach((openFileValue, path) => {
@ -2215,11 +2266,12 @@ namespace ts.server {
if (!updatedProjects.has(configFileName)) {
if (delayReload) {
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = reason;
this.delayUpdateProjectGraph(project);
}
else {
// reload from the disk
this.reloadConfiguredProject(project);
this.reloadConfiguredProject(project, reason);
}
updatedProjects.set(configFileName, true);
}
@ -2306,7 +2358,8 @@ namespace ts.server {
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createAndLoadConfiguredProject(configFileName);
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) ||
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName} for location: ${location.fileName}`);
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);
@ -2356,7 +2409,7 @@ namespace ts.server {
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createLoadAndUpdateConfiguredProject(configFileName);
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${fileName} to open`);
// Send the event only if the project got created as part of this open request and info is part of the project
if (info.isOrphan()) {
// Since the file isnt part of configured project, do not send config file info
@ -2797,8 +2850,8 @@ namespace ts.server {
if (!project) {
// errors are stored in the project, do not need to update the graph
project = this.getHostPreferences().lazyConfiguredProjectsFromExternalProject ?
this.createConfiguredProjectWithDelayLoad(tsconfigFile) :
this.createLoadAndUpdateConfiguredProject(tsconfigFile);
this.createConfiguredProjectWithDelayLoad(tsconfigFile, `Creating configured project in external project: ${proj.projectFileName}`) :
this.createLoadAndUpdateConfiguredProject(tsconfigFile, `Creating configured project in external project: ${proj.projectFileName}`);
}
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project

View File

@ -1320,6 +1320,8 @@ namespace ts.server {
/* @internal */
pendingReload: ConfigFileProgramReloadLevel;
/* @internal */
pendingReloadReason: string | undefined;
/*@internal*/
configFileSpecs: ConfigFileSpecs | undefined;
@ -1339,6 +1341,9 @@ namespace ts.server {
protected isInitialLoadPending: () => boolean = returnTrue;
/*@internal*/
sendLoadingProjectFinish = false;
/*@internal*/
constructor(configFileName: NormalizedPath,
projectService: ProjectService,
@ -1371,12 +1376,15 @@ namespace ts.server {
result = this.projectService.reloadFileNamesOfConfiguredProject(this);
break;
case ConfigFileProgramReloadLevel.Full:
this.projectService.reloadConfiguredProject(this);
const reason = Debug.assertDefined(this.pendingReloadReason);
this.pendingReloadReason = undefined;
this.projectService.reloadConfiguredProject(this, reason);
result = true;
break;
default:
result = super.updateGraph();
}
this.projectService.sendProjectLoadingFinishEvent(this);
this.projectService.sendProjectTelemetry(this);
this.projectService.sendSurveyReady(this);
return result;

View File

@ -2452,6 +2452,30 @@ namespace ts.server.protocol {
openFiles: string[];
}
export type ProjectLoadingStartEventName = "projectLoadingStart";
export interface ProjectLoadingStartEvent extends Event {
event: ProjectLoadingStartEventName;
body: ProjectLoadingStartEventBody;
}
export interface ProjectLoadingStartEventBody {
/** name of the project */
projectName: string;
/** reason for loading */
reason: string;
}
export type ProjectLoadingFinishEventName = "projectLoadingFinish";
export interface ProjectLoadingFinishEvent extends Event {
event: ProjectLoadingFinishEventName;
body: ProjectLoadingFinishEventBody;
}
export interface ProjectLoadingFinishEventBody {
/** name of the project */
projectName: string;
}
export type SurveyReadyEventName = "surveyReady";
export interface SurveyReadyEvent extends Event {

View File

@ -568,9 +568,19 @@ namespace ts.server {
const { openFiles } = event.data;
this.projectsUpdatedInBackgroundEvent(openFiles);
break;
case ProjectLoadingStartEvent:
const { project, reason } = event.data;
this.event<protocol.ProjectLoadingStartEventBody>(
{ projectName: project.getProjectName(), reason },
ProjectLoadingStartEvent);
break;
case ProjectLoadingFinishEvent:
const { project: finishProject } = event.data;
this.event<protocol.ProjectLoadingFinishEventBody>({ projectName: finishProject.getProjectName() }, ProjectLoadingStartEvent);
break;
case LargeFileReferencedEvent:
const { file, fileSize, maxFileSize } = event.data;
this.event<protocol.LargeFileReferencedEventBody>({ file, fileSize, maxFileSize }, "largeFileReferenced");
this.event<protocol.LargeFileReferencedEventBody>({ file, fileSize, maxFileSize }, LargeFileReferencedEvent);
break;
case ConfigFileDiagEvent:
const { triggerFile, configFileName: configFile, diagnostics } = event.data;
@ -579,14 +589,14 @@ namespace ts.server {
triggerFile,
configFile,
diagnostics: bakedDiags
}, "configFileDiag");
}, ConfigFileDiagEvent);
break;
case SurveyReady:
const { surveyId } = event.data;
this.event<protocol.SurveyReadyEventBody>({ surveyId }, "surveyReady");
this.event<protocol.SurveyReadyEventBody>({ surveyId }, SurveyReady);
break;
case ProjectLanguageServiceStateEvent: {
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
const eventName: protocol.ProjectLanguageServiceStateEventName = ProjectLanguageServiceStateEvent;
this.event<protocol.ProjectLanguageServiceStateEventBody>({
projectName: event.data.project.getProjectName(),
languageServiceEnabled: event.data.languageServiceEnabled
@ -617,7 +627,7 @@ namespace ts.server {
// Send project changed event
this.event<protocol.ProjectsUpdatedInBackgroundEventBody>({
openFiles
}, "projectsUpdatedInBackground");
}, ProjectsUpdatedInBackgroundEvent);
}
}

View File

@ -330,6 +330,19 @@ namespace ts.projectSystem {
return new TestSession({ ...sessionOptions, ...opts });
}
function createSessionWithEventTracking<T extends server.ProjectServiceEvent, U extends server.ProjectServiceEvent = T>(host: server.ServerHost, eventName: T["eventName"], eventName2?: U["eventName"]) {
const events: (T | U)[] = [];
const session = createSession(host, {
eventHandler: e => {
if (e.eventName === eventName || (eventName2 && e.eventName === eventName2)) {
events.push(e as T | U);
}
}
});
return { session, events };
}
interface CreateProjectServiceParameters {
cancellationToken?: HostCancellationToken;
logger?: server.Logger;
@ -2872,18 +2885,7 @@ namespace ts.projectSystem {
host.getFileSize = (filePath: string) =>
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
let lastEvent!: server.ProjectLanguageServiceStateEvent;
const session = createSession(host, {
canUseEvents: true,
eventHandler: e => {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent || e.eventName === server.LargeFileReferencedEvent || e.eventName === server.SurveyReady) {
return;
}
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
assert.equal(e.data.project.getProjectName(), config.path, "project name");
lastEvent = e;
}
});
const { session, events } = createSessionWithEventTracking<server.ProjectLanguageServiceStateEvent>(host, server.ProjectLanguageServiceStateEvent);
session.executeCommand(<protocol.OpenRequest>{
seq: 0,
type: "request",
@ -2894,17 +2896,19 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = configuredProjectAt(projectService, 0);
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
assert.isTrue(!!lastEvent, "should receive event");
assert.equal(lastEvent.data.project, project, "project name");
assert.equal(lastEvent.data.project.getProjectName(), config.path, "config path");
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");
assert.equal(events.length, 1, "should receive event");
assert.equal(events[0].data.project, project, "project name");
assert.equal(events[0].data.project.getProjectName(), config.path, "config path");
assert.isFalse(events[0].data.languageServiceEnabled, "Language service state");
host.reloadFS([f1, f2, configWithExclude]);
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.isTrue(project.languageServiceEnabled, "Language service enabled");
assert.equal(lastEvent.data.project, project, "project");
assert.isTrue(lastEvent.data.languageServiceEnabled, "Language service state");
assert.equal(events.length, 2, "should receive event");
assert.equal(events[1].data.project, project, "project");
assert.equal(events[1].data.project.getProjectName(), config.path, "config path");
assert.isTrue(events[1].data.languageServiceEnabled, "Language service state");
});
it("syntactic features work even if language service is disabled", () => {
@ -2924,17 +2928,7 @@ namespace ts.projectSystem {
const originalGetFileSize = host.getFileSize;
host.getFileSize = (filePath: string) =>
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
let lastEvent!: server.ProjectLanguageServiceStateEvent;
const session = createSession(host, {
canUseEvents: true,
eventHandler: e => {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent) {
return;
}
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
}
});
const { session, events } = createSessionWithEventTracking<server.ProjectLanguageServiceStateEvent>(host, server.ProjectLanguageServiceStateEvent);
session.executeCommand(<protocol.OpenRequest>{
seq: 0,
type: "request",
@ -2946,9 +2940,9 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = configuredProjectAt(projectService, 0);
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
assert.isTrue(!!lastEvent, "should receive event");
assert.equal(lastEvent.data.project, project, "project name");
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");
assert.equal(events.length, 1, "should receive event");
assert.equal(events[0].data.project, project, "project name");
assert.isFalse(events[0].data.languageServiceEnabled, "Language service state");
const options = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath);
const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options);
@ -3553,14 +3547,7 @@ namespace ts.projectSystem {
});
function createSessionWithEventHandler(host: TestServerHost) {
const surveyEvents: server.SurveyReady[] = [];
const session = createSession(host, {
eventHandler: e => {
if (e.eventName === server.SurveyReady) {
surveyEvents.push(e);
}
}
});
const { session, events: surveyEvents } = createSessionWithEventTracking<server.SurveyReady>(host, server.SurveyReady);
return { session, verifySurveyReadyEvent };
@ -9295,14 +9282,7 @@ export const x = 10;`
};
files.push(largeFile);
const host = createServerHost(files);
const largeFileReferencedEvents: server.LargeFileReferencedEvent[] = [];
const session = createSession(host, {
eventHandler: e => {
if (e.eventName === server.LargeFileReferencedEvent) {
largeFileReferencedEvents.push(e);
}
}
});
const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking<server.LargeFileReferencedEvent>(host, server.LargeFileReferencedEvent);
return { session, verifyLargeFile };
@ -9362,6 +9342,159 @@ export const x = 10;`
});
});
describe("tsserverProjectSystem ProjectLoadingStart and ProjectLoadingFinish events", () => {
const projectRoot = "/user/username/projects";
const aTs: File = {
path: `${projectRoot}/a/a.ts`,
content: "export class A { }"
};
const configA: File = {
path: `${projectRoot}/a/tsconfig.json`,
content: "{}"
};
const bTsPath = `${projectRoot}/b/b.ts`;
const configBPath = `${projectRoot}/b/tsconfig.json`;
const files = [libFile, aTs, configA];
function createSessionWithEventHandler(files: ReadonlyArray<File>) {
const host = createServerHost(files);
const originalReadFile = host.readFile;
host.readFile = file => {
if (file === configA.path || file === configBPath) {
assert.equal(events.length, 1, "Event for loading is sent before reading config file");
}
return originalReadFile.call(host, file);
};
const { session, events } = createSessionWithEventTracking<server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent>(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent);
const service = session.getProjectService();
return { host, session, verifyEvent, verifyEventWithOpenTs, service, events };
function verifyEvent(project: server.Project, reason: string) {
assert.deepEqual(events, [
{ eventName: server.ProjectLoadingStartEvent, data: { project, reason } },
{ eventName: server.ProjectLoadingFinishEvent, data: { project } }
]);
events.length = 0;
}
function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) {
openFilesForSession([file], session);
checkNumberOfProjects(service, { configuredProjects });
const project = service.configuredProjects.get(configPath)!;
assert.isDefined(project);
verifyEvent(project, `Creating possible configured project for ${file.path} to open`);
}
}
it("when project is created by open file", () => {
const bTs: File = {
path: bTsPath,
content: "export class B {}"
};
const configB: File = {
path: configBPath,
content: "{}"
};
const { verifyEventWithOpenTs } = createSessionWithEventHandler(files.concat(bTs, configB));
verifyEventWithOpenTs(aTs, configA.path, 1);
verifyEventWithOpenTs(bTs, configB.path, 2);
});
it("when change is detected in the config file", () => {
const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionWithEventHandler(files);
verifyEventWithOpenTs(aTs, configA.path, 1);
host.writeFile(configA.path, configA.content);
host.checkTimeoutQueueLengthAndRun(2);
const project = service.configuredProjects.get(configA.path)!;
verifyEvent(project, `Change in config file detected`);
});
it("when opening original location project", () => {
const aDTs: File = {
path: `${projectRoot}/a/a.d.ts`,
content: `export declare class A {
}
//# sourceMappingURL=a.d.ts.map
`
};
const aDTsMap: File = {
path: `${projectRoot}/a/a.d.ts.map`,
content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}`
};
const bTs: File = {
path: bTsPath,
content: `import {A} from "../a/a"; new A();`
};
const configB: File = {
path: configBPath,
content: JSON.stringify({
references: [{ path: "../a" }]
})
};
const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionWithEventHandler(files.concat(aDTs, aDTsMap, bTs, configB));
verifyEventWithOpenTs(bTs, configB.path, 1);
session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: {
file: bTs.path,
...protocolLocationFromSubstring(bTs.content, "A()")
}
});
checkNumberOfProjects(service, { configuredProjects: 2 });
const project = service.configuredProjects.get(configA.path)!;
assert.isDefined(project);
verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`);
});
describe("with external projects and config files ", () => {
const projectFileName = `${projectRoot}/a/project.csproj`;
function createSession(lazyConfiguredProjectsFromExternalProject: boolean) {
const { session, service, verifyEvent: verifyEventWorker, events } = createSessionWithEventHandler(files);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
service.openExternalProject(<protocol.ExternalProject>{
projectFileName,
rootFiles: toExternalFiles([aTs.path, configA.path]),
options: {}
});
checkNumberOfProjects(service, { configuredProjects: 1 });
return { session, service, verifyEvent, events };
function verifyEvent() {
const projectA = service.configuredProjects.get(configA.path)!;
assert.isDefined(projectA);
verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`);
}
}
it("when lazyConfiguredProjectsFromExternalProject is false", () => {
const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false);
verifyEvent();
});
it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => {
const { verifyEvent, events, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true);
assert.equal(events.length, 0);
openFilesForSession([aTs], session);
verifyEvent();
});
it("when lazyConfiguredProjectsFromExternalProject is disabled", () => {
const { verifyEvent, events, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true);
assert.equal(events.length, 0);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } });
verifyEvent();
});
});
});
describe("tsserverProjectSystem syntax operations", () => {
function navBarFull(session: TestSession, file: File) {
return JSON.stringify(session.executeCommandSeq<protocol.FileRequest>({

View File

@ -7514,6 +7514,26 @@ declare namespace ts.server.protocol {
*/
openFiles: string[];
}
type ProjectLoadingStartEventName = "projectLoadingStart";
interface ProjectLoadingStartEvent extends Event {
event: ProjectLoadingStartEventName;
body: ProjectLoadingStartEventBody;
}
interface ProjectLoadingStartEventBody {
/** name of the project */
projectName: string;
/** reason for loading */
reason: string;
}
type ProjectLoadingFinishEventName = "projectLoadingFinish";
interface ProjectLoadingFinishEvent extends Event {
event: ProjectLoadingFinishEventName;
body: ProjectLoadingFinishEventBody;
}
interface ProjectLoadingFinishEventBody {
/** name of the project */
projectName: string;
}
type SurveyReadyEventName = "surveyReady";
interface SurveyReadyEvent extends Event {
event: SurveyReadyEventName;
@ -8266,6 +8286,8 @@ declare namespace ts.server {
declare namespace ts.server {
const maxProgramSizeForNonTsFiles: number;
const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground";
const ProjectLoadingStartEvent = "projectLoadingStart";
const ProjectLoadingFinishEvent = "projectLoadingFinish";
const SurveyReady = "surveyReady";
const LargeFileReferencedEvent = "largeFileReferenced";
const ConfigFileDiagEvent = "configFileDiag";
@ -8278,6 +8300,19 @@ declare namespace ts.server {
openFiles: string[];
};
}
interface ProjectLoadingStartEvent {
eventName: typeof ProjectLoadingStartEvent;
data: {
project: Project;
reason: string;
};
}
interface ProjectLoadingFinishEvent {
eventName: typeof ProjectLoadingFinishEvent;
data: {
project: Project;
};
}
interface SurveyReady {
eventName: typeof SurveyReady;
data: {
@ -8362,7 +8397,7 @@ declare namespace ts.server {
interface OpenFileInfo {
readonly checkJs: boolean;
}
type ProjectServiceEvent = LargeFileReferencedEvent | SurveyReady | ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
type ProjectServiceEvent = LargeFileReferencedEvent | SurveyReady | ProjectsUpdatedInBackgroundEvent | ProjectLoadingStartEvent | ProjectLoadingFinishEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
interface SafeList {
[name: string]: {