mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-10 15:25:54 -06:00
parent
c080324974
commit
37e25c8873
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>({
|
||||
|
||||
@ -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]: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user