mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-31 08:07:10 -05:00
Create baselines for tsserver event tests (#53330)
This commit is contained in:
@@ -267,9 +267,12 @@ function convertToLocation(lineAndCharacter: LineAndCharacter): protocol.Locatio
|
||||
return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 };
|
||||
}
|
||||
|
||||
function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName;
|
||||
function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: false): protocol.Diagnostic;
|
||||
function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName {
|
||||
/** @internal */
|
||||
export function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName;
|
||||
/** @internal */
|
||||
export function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: false): protocol.Diagnostic;
|
||||
/** @internal */
|
||||
export function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName {
|
||||
const start = (diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start!)))!; // TODO: GH#18217
|
||||
const end = (diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start! + diag.length!)))!; // TODO: GH#18217
|
||||
const text = flattenDiagnosticMessageText(diag.messageText, "\n");
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
createLoggerWithInMemoryLogs,
|
||||
createProjectService,
|
||||
createSession,
|
||||
createSessionWithEventTracking,
|
||||
openFilesForSession,
|
||||
verifyGetErrRequest,
|
||||
} from "./helpers";
|
||||
@@ -751,25 +750,22 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => {
|
||||
const originalGetFileSize = host.getFileSize;
|
||||
host.getFileSize = (filePath: string) =>
|
||||
filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
|
||||
const { session, events } = createSessionWithEventTracking<ts.server.ProjectLanguageServiceStateEvent>(host, ts.server.ProjectLanguageServiceStateEvent);
|
||||
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
|
||||
session.executeCommand({
|
||||
seq: 0,
|
||||
type: "request",
|
||||
command: "open",
|
||||
arguments: { file: f1.path }
|
||||
} as ts.server.protocol.OpenRequest);
|
||||
session.logger.log(`Language languageServiceEnabled:: ${session.getProjectService().configuredProjects.get(config.path)!.languageServiceEnabled}`);
|
||||
|
||||
const projectService = session.getProjectService();
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const project = configuredProjectAt(projectService, 0);
|
||||
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
|
||||
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 ts.server.NormalizedPath);
|
||||
const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options);
|
||||
assert.deepEqual(edits, [{ span: ts.createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]);
|
||||
session.executeCommandSeq({
|
||||
command: ts.server.protocol.CommandTypes.FormatFull,
|
||||
arguments: {
|
||||
file: f1.path,
|
||||
}
|
||||
});
|
||||
baselineTsserverLogs("configuredProjects", "syntactic features work even if language service is disabled", session);
|
||||
});
|
||||
|
||||
it("when multiple projects are open, detects correct default project", () => {
|
||||
@@ -1263,7 +1259,7 @@ describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, libFile, configFile]);
|
||||
const { session, events } = createSessionWithEventTracking<ts.server.ConfigFileDiagEvent>(host, ts.server.ConfigFileDiagEvent);
|
||||
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
|
||||
const originalReadFile = host.readFile;
|
||||
host.readFile = f => {
|
||||
return f === configFile.path ?
|
||||
@@ -1272,15 +1268,6 @@ describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file
|
||||
};
|
||||
openFilesForSession([file1], session);
|
||||
|
||||
assert.deepEqual(events, [{
|
||||
eventName: ts.server.ConfigFileDiagEvent,
|
||||
data: {
|
||||
triggerFile: file1.path,
|
||||
configFileName: configFile.path,
|
||||
diagnostics: [
|
||||
ts.createCompilerDiagnostic(ts.Diagnostics.Cannot_read_file_0, configFile.path)
|
||||
]
|
||||
}
|
||||
}]);
|
||||
baselineTsserverLogs("configuredProjects", "should be tolerated without crashing the server", session);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,16 +5,18 @@ import {
|
||||
libFile,
|
||||
} from "../../virtualFileSystemWithWatch";
|
||||
import {
|
||||
checkNumberOfProjects,
|
||||
checkProjectActualFiles,
|
||||
createSessionWithEventTracking,
|
||||
baselineTsserverLogs,
|
||||
createLoggerWithInMemoryLogs,
|
||||
createSession,
|
||||
openFilesForSession,
|
||||
} from "../helpers";
|
||||
|
||||
describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large file", () => {
|
||||
|
||||
function getFileType(useLargeTsFile: boolean) {
|
||||
return useLargeTsFile ? "ts" : "js";
|
||||
}
|
||||
function getLargeFile(useLargeTsFile: boolean) {
|
||||
return `src/large.${useLargeTsFile ? "ts" : "js"}`;
|
||||
return `src/large.${getFileType(useLargeTsFile)}`;
|
||||
}
|
||||
|
||||
function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) {
|
||||
@@ -25,23 +27,9 @@ describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large fi
|
||||
};
|
||||
files.push(largeFile);
|
||||
const host = createServerHost(files);
|
||||
const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking<ts.server.LargeFileReferencedEvent>(host, ts.server.LargeFileReferencedEvent);
|
||||
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
|
||||
|
||||
return { session, verifyLargeFile };
|
||||
|
||||
function verifyLargeFile(project: ts.server.Project) {
|
||||
checkProjectActualFiles(project, files.map(f => f.path));
|
||||
|
||||
// large file for non ts file should be empty and for ts file should have content
|
||||
const service = session.getProjectService();
|
||||
const info = service.getScriptInfo(largeFile.path)!;
|
||||
assert.equal(info.cacheSourceFile!.sourceFile.text, useLargeTsFile ? largeFile.content : "");
|
||||
|
||||
assert.deepEqual(largeFileReferencedEvents, useLargeTsFile ? ts.emptyArray : [{
|
||||
eventName: ts.server.LargeFileReferencedEvent,
|
||||
data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: ts.server.maxFileSize }
|
||||
}]);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
function verifyLargeFile(useLargeTsFile: boolean) {
|
||||
@@ -55,11 +43,9 @@ describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large fi
|
||||
content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } })
|
||||
};
|
||||
const files = [file, libFile, tsconfig];
|
||||
const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile);
|
||||
const service = session.getProjectService();
|
||||
const session = createSessionWithEventHandler(files, useLargeTsFile);
|
||||
openFilesForSession([file], session);
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
verifyLargeFile(service.configuredProjects.get(tsconfig.path)!);
|
||||
baselineTsserverLogs("events/largeFileReferenced", `when large ${getFileType(useLargeTsFile)} file is included by tsconfig`, session);
|
||||
});
|
||||
|
||||
it("when large file is included by module resolution", () => {
|
||||
@@ -68,11 +54,9 @@ describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large fi
|
||||
content: `export var y = 10;import {x} from "./large"`
|
||||
};
|
||||
const files = [file, libFile];
|
||||
const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile);
|
||||
const service = session.getProjectService();
|
||||
const session = createSessionWithEventHandler(files, useLargeTsFile);
|
||||
openFilesForSession([file], session);
|
||||
checkNumberOfProjects(service, { inferredProjects: 1 });
|
||||
verifyLargeFile(service.inferredProjects[0]);
|
||||
baselineTsserverLogs("events/largeFileReferenced", `when large ${getFileType(useLargeTsFile)} file is included by module resolution`, session);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,9 @@ import {
|
||||
} from "../../virtualFileSystemWithWatch";
|
||||
import {
|
||||
baselineTsserverLogs,
|
||||
checkNumberOfProjects,
|
||||
configuredProjectAt,
|
||||
createLoggerWithInMemoryLogs,
|
||||
createProjectService,
|
||||
createSessionWithEventTracking,
|
||||
createSession,
|
||||
} from "../helpers";
|
||||
|
||||
describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () => {
|
||||
@@ -36,30 +34,19 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", ()
|
||||
host.getFileSize = (filePath: string) =>
|
||||
filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
|
||||
|
||||
const { session, events } = createSessionWithEventTracking<ts.server.ProjectLanguageServiceStateEvent>(host, ts.server.ProjectLanguageServiceStateEvent);
|
||||
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
|
||||
session.executeCommand({
|
||||
seq: 0,
|
||||
type: "request",
|
||||
command: "open",
|
||||
arguments: { file: f1.path }
|
||||
} as ts.server.protocol.OpenRequest);
|
||||
const projectService = session.getProjectService();
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const project = configuredProjectAt(projectService, 0);
|
||||
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
|
||||
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");
|
||||
session.logger.log(`Language service enabled: ${session.getProjectService().configuredProjects.get(config.path)!.languageServiceEnabled}`);
|
||||
|
||||
host.writeFile(configWithExclude.path, configWithExclude.content);
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
assert.isTrue(project.languageServiceEnabled, "Language service enabled");
|
||||
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");
|
||||
session.logger.log(`Language service enabled: ${session.getProjectService().configuredProjects.get(config.path)!.languageServiceEnabled}`);
|
||||
baselineTsserverLogs("events/projectLanguageServiceState", "language service disabled events are triggered", session);
|
||||
});
|
||||
|
||||
it("Large file size is determined correctly", () => {
|
||||
@@ -87,6 +74,6 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", ()
|
||||
const project = service.configuredProjects.get(config.path)!;
|
||||
service.logger.info(`languageServiceEnabled: ${project.languageServiceEnabled}`);
|
||||
service.logger.info(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`);
|
||||
baselineTsserverLogs("projectLanguageServiceStateEvent", "large file size is determined correctly", service);
|
||||
baselineTsserverLogs("events/projectLanguageServiceState", "large file size is determined correctly", service);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,9 +6,10 @@ import {
|
||||
TestServerHost,
|
||||
} from "../../virtualFileSystemWithWatch";
|
||||
import {
|
||||
checkNumberOfProjects,
|
||||
createSessionWithDefaultEventHandler,
|
||||
createSessionWithEventTracking,
|
||||
baselineTsserverLogs,
|
||||
createLoggerWithInMemoryLogs,
|
||||
createSession,
|
||||
createSessionWithCustomEventHandler,
|
||||
openFilesForSession,
|
||||
protocolLocationFromSubstring,
|
||||
TestSession,
|
||||
@@ -28,220 +29,157 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading
|
||||
const configBPath = `/user/username/projects/b/tsconfig.json`;
|
||||
const files = [libFile, aTs, configA];
|
||||
|
||||
function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => {
|
||||
session: TestSession;
|
||||
getNumberOfEvents: () => number;
|
||||
clearEvents: () => void;
|
||||
verifyProjectLoadEvents: (expected: [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]) => void;
|
||||
}) {
|
||||
function createSessionToVerifyEvent(files: readonly File[]) {
|
||||
const host = createServerHost(files);
|
||||
const originalReadFile = host.readFile;
|
||||
const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host);
|
||||
host.readFile = file => {
|
||||
if (file === configA.path || file === configBPath) {
|
||||
assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file");
|
||||
}
|
||||
return originalReadFile.call(host, file);
|
||||
};
|
||||
const service = session.getProjectService();
|
||||
return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents };
|
||||
|
||||
function verifyEvent(project: ts.server.Project, reason: string) {
|
||||
verifyProjectLoadEvents([
|
||||
{ eventName: ts.server.ProjectLoadingStartEvent, data: { project, reason } },
|
||||
{ eventName: ts.server.ProjectLoadingFinishEvent, data: { project } }
|
||||
]);
|
||||
clearEvents();
|
||||
}
|
||||
|
||||
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 } = createSessionToVerifyEvent(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 } = createSessionToVerifyEvent(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 change is detected in an extended config file", () => {
|
||||
const bTs: File = {
|
||||
path: bTsPath,
|
||||
content: "export class B {}"
|
||||
};
|
||||
const configB: File = {
|
||||
path: configBPath,
|
||||
content: JSON.stringify({
|
||||
extends: "../a/tsconfig.json",
|
||||
})
|
||||
};
|
||||
const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files.concat(bTs, configB));
|
||||
verifyEventWithOpenTs(bTs, configB.path, 1);
|
||||
|
||||
host.writeFile(configA.path, configA.content);
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
const project = service.configuredProjects.get(configB.path)!;
|
||||
verifyEvent(project, `Change in extended config file ${configA.path} detected`);
|
||||
});
|
||||
|
||||
describe("when opening original location project", () => {
|
||||
it("with project references", () => {
|
||||
verify();
|
||||
});
|
||||
|
||||
it("when disableSourceOfProjectReferenceRedirect is true", () => {
|
||||
verify(/*disableSourceOfProjectReferenceRedirect*/ true);
|
||||
});
|
||||
|
||||
function verify(disableSourceOfProjectReferenceRedirect?: true) {
|
||||
const aDTs: File = {
|
||||
path: `/user/username/projects/a/a.d.ts`,
|
||||
content: `export declare class A {
|
||||
}
|
||||
//# sourceMappingURL=a.d.ts.map
|
||||
`
|
||||
};
|
||||
const aDTsMap: File = {
|
||||
path: `/user/username/projects/a/a.d.ts.map`,
|
||||
content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}`
|
||||
};
|
||||
function verifyProjectLoadingStartAndFinish(sessionType: string, createSession: (host: TestServerHost) => TestSession) {
|
||||
describe(sessionType, () => {
|
||||
it("when project is created by open file", () => {
|
||||
const bTs: File = {
|
||||
path: bTsPath,
|
||||
content: `import {A} from "../a/a"; new A();`
|
||||
content: "export class B {}"
|
||||
};
|
||||
const configB: File = {
|
||||
path: configBPath,
|
||||
content: "{}"
|
||||
};
|
||||
const host = createServerHost(files.concat(bTs, configB));
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aTs], session);
|
||||
openFilesForSession([bTs], session);
|
||||
baselineTsserverLogs("events/projectLoading", `project is created by open file ${sessionType}`, session);
|
||||
});
|
||||
|
||||
it("when change is detected in the config file", () => {
|
||||
const host = createServerHost(files);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aTs], session);
|
||||
|
||||
host.writeFile(configA.path, configA.content);
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
baselineTsserverLogs("events/projectLoading", `change is detected in the config file ${sessionType}`, session);
|
||||
});
|
||||
|
||||
it("when change is detected in an extended config file", () => {
|
||||
const bTs: File = {
|
||||
path: bTsPath,
|
||||
content: "export class B {}"
|
||||
};
|
||||
const configB: File = {
|
||||
path: configBPath,
|
||||
content: JSON.stringify({
|
||||
...(disableSourceOfProjectReferenceRedirect && {
|
||||
compilerOptions: {
|
||||
disableSourceOfProjectReferenceRedirect
|
||||
}
|
||||
}),
|
||||
references: [{ path: "../a" }]
|
||||
extends: "../a/tsconfig.json",
|
||||
})
|
||||
};
|
||||
const host = createServerHost(files.concat(bTs, configB));
|
||||
const session = createSession(host);
|
||||
openFilesForSession([bTs], session);
|
||||
|
||||
const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB));
|
||||
verifyEventWithOpenTs(bTs, configB.path, 1);
|
||||
host.writeFile(configA.path, configA.content);
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
baselineTsserverLogs("events/projectLoading", `change is detected in an extended config file ${sessionType}`, session);
|
||||
});
|
||||
|
||||
session.executeCommandSeq<ts.server.protocol.ReferencesRequest>({
|
||||
command: ts.server.protocol.CommandTypes.References,
|
||||
arguments: {
|
||||
file: bTs.path,
|
||||
...protocolLocationFromSubstring(bTs.content, "A()")
|
||||
}
|
||||
describe("when opening original location project", () => {
|
||||
it("with project references", () => {
|
||||
verify();
|
||||
});
|
||||
|
||||
checkNumberOfProjects(service, { configuredProjects: 2 });
|
||||
const project = service.configuredProjects.get(configA.path)!;
|
||||
assert.isDefined(project);
|
||||
verifyEvent(
|
||||
project,
|
||||
disableSourceOfProjectReferenceRedirect ?
|
||||
`Creating project for original file: ${aTs.path} for location: ${aDTs.path}` :
|
||||
`Creating project for original file: ${aTs.path}`
|
||||
);
|
||||
}
|
||||
});
|
||||
it("when disableSourceOfProjectReferenceRedirect is true", () => {
|
||||
verify(/*disableSourceOfProjectReferenceRedirect*/ true);
|
||||
});
|
||||
|
||||
describe("with external projects and config files ", () => {
|
||||
const projectFileName = `/user/username/projects/a/project.csproj`;
|
||||
function verify(disableSourceOfProjectReferenceRedirect?: true) {
|
||||
const aDTs: File = {
|
||||
path: `/user/username/projects/a/a.d.ts`,
|
||||
content: `export declare class A {
|
||||
}
|
||||
//# sourceMappingURL=a.d.ts.map
|
||||
`
|
||||
};
|
||||
const aDTsMap: File = {
|
||||
path: `/user/username/projects/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({
|
||||
...(disableSourceOfProjectReferenceRedirect && {
|
||||
compilerOptions: {
|
||||
disableSourceOfProjectReferenceRedirect
|
||||
}
|
||||
}),
|
||||
references: [{ path: "../a" }]
|
||||
})
|
||||
};
|
||||
|
||||
function createSession(lazyConfiguredProjectsFromExternalProject: boolean) {
|
||||
const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files);
|
||||
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
|
||||
service.openExternalProject({
|
||||
projectFileName,
|
||||
rootFiles: toExternalFiles([aTs.path, configA.path]),
|
||||
options: {}
|
||||
} as ts.server.protocol.ExternalProject);
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
return { session, service, verifyEvent, getNumberOfEvents };
|
||||
const host = createServerHost(files.concat(aDTs, aDTsMap, bTs, configB));
|
||||
const session = createSession(host);
|
||||
openFilesForSession([bTs], session);
|
||||
|
||||
function verifyEvent() {
|
||||
const projectA = service.configuredProjects.get(configA.path)!;
|
||||
assert.isDefined(projectA);
|
||||
verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`);
|
||||
session.executeCommandSeq<ts.server.protocol.ReferencesRequest>({
|
||||
command: ts.server.protocol.CommandTypes.References,
|
||||
arguments: {
|
||||
file: bTs.path,
|
||||
...protocolLocationFromSubstring(bTs.content, "A()")
|
||||
}
|
||||
});
|
||||
baselineTsserverLogs("events/projectLoading", `opening original location project${disableSourceOfProjectReferenceRedirect ? " disableSourceOfProjectReferenceRedirect" : ""} ${sessionType}`, session);
|
||||
}
|
||||
}
|
||||
|
||||
it("when lazyConfiguredProjectsFromExternalProject is false", () => {
|
||||
const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false);
|
||||
verifyEvent();
|
||||
});
|
||||
|
||||
it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => {
|
||||
const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true);
|
||||
assert.equal(getNumberOfEvents(), 0);
|
||||
describe("with external projects and config files ", () => {
|
||||
const projectFileName = `/user/username/projects/a/project.csproj`;
|
||||
|
||||
openFilesForSession([aTs], session);
|
||||
verifyEvent();
|
||||
});
|
||||
function createSessionAndOpenProject(lazyConfiguredProjectsFromExternalProject: boolean) {
|
||||
const host = createServerHost(files);
|
||||
const session = createSession(host);
|
||||
session.executeCommandSeq<ts.server.protocol.ConfigureRequest>({
|
||||
command: ts.server.protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
preferences: { lazyConfiguredProjectsFromExternalProject }
|
||||
}
|
||||
});
|
||||
session.executeCommandSeq<ts.server.protocol.OpenExternalProjectRequest>({
|
||||
command: ts.server.protocol.CommandTypes.OpenExternalProject,
|
||||
arguments: {
|
||||
projectFileName,
|
||||
rootFiles: toExternalFiles([aTs.path, configA.path]),
|
||||
options: {}
|
||||
}
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
it("when lazyConfiguredProjectsFromExternalProject is disabled", () => {
|
||||
const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true);
|
||||
assert.equal(getNumberOfEvents(), 0);
|
||||
it("when lazyConfiguredProjectsFromExternalProject is false", () => {
|
||||
const session = createSessionAndOpenProject(/*lazyConfiguredProjectsFromExternalProject*/ false);
|
||||
baselineTsserverLogs("events/projectLoading", `lazyConfiguredProjectsFromExternalProject is false ${sessionType}`, session);
|
||||
});
|
||||
|
||||
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } });
|
||||
verifyEvent();
|
||||
it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => {
|
||||
const session = createSessionAndOpenProject(/*lazyConfiguredProjectsFromExternalProject*/ true);
|
||||
openFilesForSession([aTs], session);
|
||||
baselineTsserverLogs("events/projectLoading", `lazyConfiguredProjectsFromExternalProject is true and file is opened ${sessionType}`, session);
|
||||
});
|
||||
|
||||
it("when lazyConfiguredProjectsFromExternalProject is disabled", () => {
|
||||
const session = createSessionAndOpenProject(/*lazyConfiguredProjectsFromExternalProject*/ true);
|
||||
session.executeCommandSeq<ts.server.protocol.ConfigureRequest>({
|
||||
command: ts.server.protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
preferences: { lazyConfiguredProjectsFromExternalProject: false }
|
||||
}
|
||||
});
|
||||
baselineTsserverLogs("events/projectLoading", `lazyConfiguredProjectsFromExternalProject is disabled ${sessionType}`, session);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("when using event handler", () => {
|
||||
verifyProjectLoadingStartAndFinish(host => {
|
||||
const { session, events } = createSessionWithEventTracking<ts.server.ProjectLoadingStartEvent | ts.server.ProjectLoadingFinishEvent>(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]);
|
||||
return {
|
||||
session,
|
||||
getNumberOfEvents: () => events.length,
|
||||
clearEvents: () => events.length = 0,
|
||||
verifyProjectLoadEvents: expected => assert.deepEqual(events, expected)
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe("when using default event handler", () => {
|
||||
verifyProjectLoadingStartAndFinish(host => {
|
||||
const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler<ts.server.protocol.ProjectLoadingStartEvent | ts.server.protocol.ProjectLoadingFinishEvent>(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]);
|
||||
return {
|
||||
session,
|
||||
getNumberOfEvents: () => getEvents().length,
|
||||
clearEvents,
|
||||
verifyProjectLoadEvents
|
||||
};
|
||||
|
||||
function verifyProjectLoadEvents(expected: [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]) {
|
||||
const actual = getEvents().map(e => ({ eventName: e.event, data: e.body }));
|
||||
const mappedExpected = expected.map(e => {
|
||||
const { project, ...rest } = e.data;
|
||||
return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } };
|
||||
});
|
||||
assert.deepEqual(actual, mappedExpected);
|
||||
}
|
||||
});
|
||||
});
|
||||
verifyProjectLoadingStartAndFinish("when using event handler", host => createSessionWithCustomEventHandler(host));
|
||||
verifyProjectLoadingStartAndFinish("when using default event handler", host => createSession(
|
||||
host,
|
||||
{ canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }
|
||||
));
|
||||
});
|
||||
|
||||
@@ -7,44 +7,15 @@ import {
|
||||
} from "../../virtualFileSystemWithWatch";
|
||||
import {
|
||||
baselineTsserverLogs,
|
||||
createHasErrorMessageLogger,
|
||||
createLoggerWithInMemoryLogs,
|
||||
createSessionWithDefaultEventHandler,
|
||||
createSessionWithEventTracking,
|
||||
Logger,
|
||||
createSession,
|
||||
createSessionWithCustomEventHandler,
|
||||
openFilesForSession,
|
||||
TestSession,
|
||||
} from "../helpers";
|
||||
|
||||
describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) {
|
||||
assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`);
|
||||
const seen = new Map<string, true>();
|
||||
ts.forEach(actual, f => {
|
||||
assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`);
|
||||
seen.set(f, true);
|
||||
assert.isTrue(ts.contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`);
|
||||
});
|
||||
}
|
||||
|
||||
function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: ts.server.ProjectsUpdatedInBackgroundEvent[]) => void) {
|
||||
return (file: File) => {
|
||||
session.executeCommandSeq({
|
||||
command: ts.server.protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: file.path
|
||||
}
|
||||
} as ts.server.protocol.OpenRequest);
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([]);
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectsUpdatedInBackgroundEventVerifier {
|
||||
session: TestSession;
|
||||
verifyProjectsUpdatedInBackgroundEventHandler(events: ts.server.ProjectsUpdatedInBackgroundEvent[]): void;
|
||||
verifyInitialOpen(file: File): void;
|
||||
}
|
||||
|
||||
function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: TestServerHost, logger?: Logger) => ProjectsUpdatedInBackgroundEventVerifier) {
|
||||
function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: TestServerHost) => TestSession) {
|
||||
it("when adding new file", () => {
|
||||
const commonFile1: File = {
|
||||
path: "/a/b/file1.ts",
|
||||
@@ -62,87 +33,53 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: `{}`
|
||||
};
|
||||
const openFiles = [commonFile1.path];
|
||||
const host = createServerHost([commonFile1, libFile, configFile]);
|
||||
const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host);
|
||||
verifyInitialOpen(commonFile1);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([commonFile1], session);
|
||||
|
||||
host.writeFile(commonFile2.path, commonFile2.content);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([{
|
||||
eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles
|
||||
}
|
||||
}]);
|
||||
|
||||
host.writeFile(commonFile3.path, commonFile3.content);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([{
|
||||
eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles
|
||||
}
|
||||
}]);
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and when adding new file`, session);
|
||||
});
|
||||
|
||||
describe("with --out or --outFile setting", () => {
|
||||
function verifyEventWithOutSettings(compilerOptions: ts.CompilerOptions = {}) {
|
||||
const config: File = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions
|
||||
})
|
||||
};
|
||||
function verifyEventWithOutSettings(subScenario: string, compilerOptions: ts.CompilerOptions = {}) {
|
||||
it(subScenario, () => {
|
||||
const config: File = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions
|
||||
})
|
||||
};
|
||||
|
||||
const f1: File = {
|
||||
path: "/a/a.ts",
|
||||
content: "export let x = 1"
|
||||
};
|
||||
const f2: File = {
|
||||
path: "/a/b.ts",
|
||||
content: "export let y = 1"
|
||||
};
|
||||
const f1: File = {
|
||||
path: "/a/a.ts",
|
||||
content: "export let x = 1"
|
||||
};
|
||||
const f2: File = {
|
||||
path: "/a/b.ts",
|
||||
content: "export let y = 1"
|
||||
};
|
||||
|
||||
const openFiles = [f1.path];
|
||||
const files = [f1, config, libFile];
|
||||
const host = createServerHost(files);
|
||||
const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host);
|
||||
verifyInitialOpen(f1);
|
||||
const files = [f1, config, libFile];
|
||||
const host = createServerHost(files);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([f1], session);
|
||||
|
||||
host.writeFile(f2.path, f2.content);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
host.writeFile(f2.path, f2.content);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([{
|
||||
eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles
|
||||
}
|
||||
}]);
|
||||
|
||||
host.writeFile(f2.path, "export let x = 11");
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([{
|
||||
eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles
|
||||
}
|
||||
}]);
|
||||
host.writeFile(f2.path, "export let x = 11");
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and ${subScenario}`, session);
|
||||
});
|
||||
}
|
||||
|
||||
it("when both options are not set", () => {
|
||||
verifyEventWithOutSettings();
|
||||
});
|
||||
|
||||
it("when --out is set", () => {
|
||||
const outJs = "/a/out.js";
|
||||
verifyEventWithOutSettings({ out: outJs });
|
||||
});
|
||||
|
||||
it("when --outFile is set", () => {
|
||||
const outJs = "/a/out.js";
|
||||
verifyEventWithOutSettings({ outFile: outJs });
|
||||
});
|
||||
verifyEventWithOutSettings("when both options are not set");
|
||||
verifyEventWithOutSettings("when --out is set", { out: "/a/out.js" });
|
||||
verifyEventWithOutSettings("when --outFile is set", { outFile: "/a/out.js" });
|
||||
});
|
||||
|
||||
describe("with modules and configured project", () => {
|
||||
@@ -191,50 +128,23 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
|
||||
const files: File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile];
|
||||
|
||||
const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files;
|
||||
const filesToReload = firstReloadFileList?.map(fileName => ts.find(files, file => file.path === fileName)!) || files;
|
||||
const host = createServerHost([filesToReload[0], configFile]);
|
||||
|
||||
// Initial project creation
|
||||
const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host);
|
||||
const openFiles = [filesToReload[0].path];
|
||||
verifyInitialOpen(filesToReload[0]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([filesToReload[0]], session);
|
||||
|
||||
// Since this is first event, it will have all the files
|
||||
filesToReload.forEach(f => host.ensureFileOrFolder(f));
|
||||
if (!firstReloadFileList) host.runQueuedTimeoutCallbacks(); // Invalidated module resolutions to schedule project update
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
|
||||
return {
|
||||
host,
|
||||
host, session,
|
||||
moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile,
|
||||
updateContentOfOpenFile,
|
||||
verifyNoProjectsUpdatedInBackgroundEvent,
|
||||
verifyProjectsUpdatedInBackgroundEvent
|
||||
};
|
||||
|
||||
function getFiles(filelist: string[]) {
|
||||
return ts.map(filelist, getFile);
|
||||
}
|
||||
|
||||
function getFile(fileName: string) {
|
||||
return ts.find(files, file => file.path === fileName)!;
|
||||
}
|
||||
|
||||
function verifyNoProjectsUpdatedInBackgroundEvent() {
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([]);
|
||||
}
|
||||
|
||||
function verifyProjectsUpdatedInBackgroundEvent() {
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([{
|
||||
eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
function updateContentOfOpenFile(file: File, newContent: string) {
|
||||
session.executeCommandSeq<ts.server.protocol.ChangeRequest>({
|
||||
command: ts.server.protocol.CommandTypes.Change,
|
||||
@@ -252,35 +162,36 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
}
|
||||
|
||||
it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
|
||||
const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
|
||||
const { host, moduleFile1, session } = getInitialState();
|
||||
|
||||
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { console.log('hi'); };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should contains only itself`, session);
|
||||
});
|
||||
|
||||
it("should be up-to-date with the reference map changes", () => {
|
||||
const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState();
|
||||
const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, session } = getInitialState();
|
||||
|
||||
// Change file1Consumer1 content to `export let y = Foo();`
|
||||
updateContentOfOpenFile(file1Consumer1, "export let y = Foo();");
|
||||
verifyNoProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Add the import statements back to file1Consumer1
|
||||
updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`);
|
||||
verifyNoProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export var T2: string;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Multiple file edits in one go:
|
||||
|
||||
@@ -288,65 +199,72 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
|
||||
updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`);
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should be up-to-date with the reference map changes`, session);
|
||||
});
|
||||
|
||||
it("should be up-to-date with deleted files", () => {
|
||||
const { host, moduleFile1, file1Consumer2, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
|
||||
const { host, moduleFile1, file1Consumer2, session } = getInitialState();
|
||||
|
||||
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
|
||||
// Delete file1Consumer2
|
||||
host.deleteFile(file1Consumer2.path);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should be up-to-date with deleted files`, session);
|
||||
});
|
||||
|
||||
it("should be up-to-date with newly created files", () => {
|
||||
const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState();
|
||||
const { host, moduleFile1, session, } = getInitialState();
|
||||
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
host.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should be up-to-date with newly created files`, session);
|
||||
});
|
||||
|
||||
it("should detect changes in non-root files", () => {
|
||||
const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
|
||||
const { host, moduleFile1, session } = getInitialState({
|
||||
configObj: { files: [file1Consumer1Path] },
|
||||
});
|
||||
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// change file1 internal, and verify only file1 is affected
|
||||
host.writeFile(moduleFile1.path, moduleFile1.content + "var T1: number;");
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should detect changes in non-root files`, session);
|
||||
});
|
||||
|
||||
it("should return all files if a global file changed shape", () => {
|
||||
const { host, globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
|
||||
const { host, globalFile3, session } = getInitialState();
|
||||
|
||||
host.writeFile(globalFile3.path, globalFile3.content + "var T2: string;");
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should return all files if a global file changed shape`, session);
|
||||
});
|
||||
|
||||
it("should always return the file itself if '--isolatedModules' is specified", () => {
|
||||
const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
|
||||
const { host, moduleFile1, session } = getInitialState({
|
||||
configObj: { compilerOptions: { isolatedModules: true } }
|
||||
});
|
||||
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should always return the file itself if --isolatedModules is specified`, session);
|
||||
});
|
||||
|
||||
it("should always return the file itself if '--out' or '--outFile' is specified", () => {
|
||||
const outFilePath = "/a/b/out.js";
|
||||
const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
|
||||
const { host, moduleFile1, session } = getInitialState({
|
||||
configObj: { compilerOptions: { module: "system", outFile: outFilePath } }
|
||||
});
|
||||
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should always return the file itself if --out or --outFile is specified`, session);
|
||||
});
|
||||
|
||||
it("should return cascaded affected file list", () => {
|
||||
@@ -354,21 +272,22 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
path: "/a/b/file1Consumer1Consumer1.ts",
|
||||
content: `import {y} from "./file1Consumer1";`
|
||||
};
|
||||
const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
|
||||
const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, session } = getInitialState({
|
||||
getAdditionalFileOrFolder: () => [file1Consumer1Consumer1]
|
||||
});
|
||||
|
||||
updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;");
|
||||
verifyNoProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Doesnt change the shape of file1Consumer1
|
||||
host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Change both files before the timeout
|
||||
updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;");
|
||||
host.writeFile(moduleFile1.path, `export var T2: number;export function Foo() { };`);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should return cascaded affected file list`, session);
|
||||
});
|
||||
|
||||
it("should work fine for files with circular references", () => {
|
||||
@@ -384,13 +303,14 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
/// <reference path="./file1.ts" />
|
||||
export var t2 = 10;`
|
||||
};
|
||||
const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
|
||||
const { host, session } = getInitialState({
|
||||
getAdditionalFileOrFolder: () => [file1, file2],
|
||||
firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath]
|
||||
});
|
||||
|
||||
host.writeFile(file2.path, file2.content + "export var t3 = 10;");
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should work fine for files with circular references`, session);
|
||||
});
|
||||
|
||||
it("should detect removed code file", () => {
|
||||
@@ -400,13 +320,14 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
/// <reference path="./moduleFile1.ts" />
|
||||
export var x = Foo();`
|
||||
};
|
||||
const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
|
||||
const { host, session } = getInitialState({
|
||||
getAdditionalFileOrFolder: () => [referenceFile1],
|
||||
firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath]
|
||||
});
|
||||
|
||||
host.deleteFile(moduleFile1Path);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should detect removed code file`, session);
|
||||
});
|
||||
|
||||
it("should detect non-existing code file", () => {
|
||||
@@ -416,17 +337,18 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
/// <reference path="./moduleFile2.ts" />
|
||||
export var x = Foo();`
|
||||
};
|
||||
const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
|
||||
const { host, moduleFile2, updateContentOfOpenFile, session } = getInitialState({
|
||||
getAdditionalFileOrFolder: () => [referenceFile1],
|
||||
firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath]
|
||||
});
|
||||
|
||||
updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();");
|
||||
verifyNoProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
// Create module File2 and see both files are saved
|
||||
host.writeFile(moduleFile2.path, moduleFile2.content);
|
||||
verifyProjectsUpdatedInBackgroundEvent();
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and should detect non-existing code file`, session);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -451,34 +373,19 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
content: JSON.stringify({ compilerOptions: { typeRoots: [] } })
|
||||
};
|
||||
|
||||
const openFiles = [file1.path];
|
||||
const host = createServerHost([file1, file3, libFile, configFile]);
|
||||
const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, createLoggerWithInMemoryLogs(host));
|
||||
verifyInitialOpen(file1);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([file1], session);
|
||||
|
||||
file3.content += "export class d {}";
|
||||
host.writeFile(file3.path, file3.content);
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
|
||||
// Since this is first event
|
||||
verifyProjectsUpdatedInBackgroundEventHandler([{
|
||||
eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles
|
||||
}
|
||||
}]);
|
||||
|
||||
host.writeFile(file2.path, file2.content);
|
||||
host.runQueuedTimeoutCallbacks(); // For invalidation
|
||||
host.runQueuedTimeoutCallbacks(); // For actual update
|
||||
|
||||
verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{
|
||||
eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles
|
||||
}
|
||||
}] : []);
|
||||
baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session);
|
||||
baselineTsserverLogs("events/projectUpdatedInBackground", `${scenario} and ${subScenario}`, session);
|
||||
});
|
||||
}
|
||||
verifyWithMaxCacheLimit("project is not at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true);
|
||||
@@ -487,85 +394,23 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
|
||||
}
|
||||
|
||||
describe("when event handler is set in the session", () => {
|
||||
verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithProjectChangedEventHandler);
|
||||
|
||||
function createSessionWithProjectChangedEventHandler(host: TestServerHost, logger: Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier {
|
||||
const { session, events: projectChangedEvents } = createSessionWithEventTracking<ts.server.ProjectsUpdatedInBackgroundEvent>(
|
||||
host,
|
||||
ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
logger && { logger }
|
||||
);
|
||||
return {
|
||||
session,
|
||||
verifyProjectsUpdatedInBackgroundEventHandler,
|
||||
verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler)
|
||||
};
|
||||
|
||||
function eventToString(event: ts.server.ProjectsUpdatedInBackgroundEvent) {
|
||||
return JSON.stringify(event && { eventName: event.eventName, data: event.data });
|
||||
}
|
||||
|
||||
function eventsToString(events: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) {
|
||||
return "[" + ts.map(events, eventToString).join(",") + "]";
|
||||
}
|
||||
|
||||
function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) {
|
||||
assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`);
|
||||
ts.forEach(projectChangedEvents, (actualEvent, i) => {
|
||||
const expectedEvent = expectedEvents[i];
|
||||
assert.strictEqual(actualEvent.eventName, expectedEvent.eventName);
|
||||
verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles);
|
||||
});
|
||||
|
||||
// Verified the events, reset them
|
||||
projectChangedEvents.length = 0;
|
||||
}
|
||||
}
|
||||
verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithCustomEventHandler);
|
||||
});
|
||||
|
||||
describe("when event handler is not set but session is created with canUseEvents = true", () => {
|
||||
describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => {
|
||||
verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", createSessionThatUsesEvents);
|
||||
verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", host => createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
}));
|
||||
});
|
||||
|
||||
describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => {
|
||||
verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", (host, logger) => createSessionThatUsesEvents(host, logger, /*noGetErrOnBackgroundUpdate*/ true));
|
||||
verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", host => createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host),
|
||||
noGetErrOnBackgroundUpdate: true
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
function createSessionThatUsesEvents(host: TestServerHost, logger: Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier {
|
||||
const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler<ts.server.protocol.ProjectsUpdatedInBackgroundEvent>(
|
||||
host,
|
||||
ts.server.ProjectsUpdatedInBackgroundEvent,
|
||||
{ noGetErrOnBackgroundUpdate, logger: logger || createHasErrorMessageLogger() }
|
||||
);
|
||||
|
||||
return {
|
||||
session,
|
||||
verifyProjectsUpdatedInBackgroundEventHandler,
|
||||
verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler)
|
||||
};
|
||||
|
||||
function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) {
|
||||
const expectedEvents: ts.server.protocol.ProjectsUpdatedInBackgroundEventBody[] = ts.map(expected, e => {
|
||||
return {
|
||||
openFiles: e.data.openFiles
|
||||
};
|
||||
});
|
||||
const events = getEvents();
|
||||
assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${ts.map(events, e => e.body)} Expected: ${expectedEvents}`);
|
||||
ts.forEach(events, (actualEvent, i) => {
|
||||
const expectedEvent = expectedEvents[i];
|
||||
verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles);
|
||||
});
|
||||
|
||||
// Verified the events, reset them
|
||||
clearEvents();
|
||||
|
||||
if (events.length) {
|
||||
host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -387,81 +387,6 @@ export function toExternalFiles(fileNames: string[]) {
|
||||
return ts.map(fileNames, toExternalFile);
|
||||
}
|
||||
|
||||
export function fileStats(nonZeroStats: Partial<ts.server.FileStats>): ts.server.FileStats {
|
||||
return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats };
|
||||
}
|
||||
|
||||
export class TestServerEventManager {
|
||||
private events: ts.server.ProjectServiceEvent[] = [];
|
||||
readonly session: TestSession;
|
||||
readonly service: ts.server.ProjectService;
|
||||
readonly host: TestServerHost;
|
||||
constructor(files: File[], suppressDiagnosticEvents?: boolean) {
|
||||
this.host = createServerHost(files);
|
||||
this.session = createSession(this.host, {
|
||||
canUseEvents: true,
|
||||
eventHandler: event => this.events.push(event),
|
||||
suppressDiagnosticEvents,
|
||||
});
|
||||
this.service = this.session.getProjectService();
|
||||
}
|
||||
|
||||
getEvents(): readonly ts.server.ProjectServiceEvent[] {
|
||||
const events = this.events;
|
||||
this.events = [];
|
||||
return events;
|
||||
}
|
||||
|
||||
getEvent<T extends ts.server.ProjectServiceEvent>(eventName: T["eventName"]): T["data"] {
|
||||
let eventData: T["data"] | undefined;
|
||||
ts.filterMutate(this.events, e => {
|
||||
if (e.eventName === eventName) {
|
||||
if (eventData !== undefined) {
|
||||
assert(false, "more than one event found");
|
||||
}
|
||||
eventData = e.data;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return ts.Debug.checkDefined(eventData);
|
||||
}
|
||||
|
||||
hasZeroEvent<T extends ts.server.ProjectServiceEvent>(eventName: T["eventName"]) {
|
||||
this.events.forEach(event => assert.notEqual(event.eventName, eventName));
|
||||
}
|
||||
|
||||
assertProjectInfoTelemetryEvent(partial: Partial<ts.server.ProjectInfoTelemetryEventData>, configFile = "/tsconfig.json"): void {
|
||||
assert.deepEqual<ts.server.ProjectInfoTelemetryEventData>(this.getEvent<ts.server.ProjectInfoTelemetryEvent>(ts.server.ProjectInfoTelemetryEvent), {
|
||||
projectId: ts.sys.createSHA256Hash!(configFile),
|
||||
fileStats: fileStats({ ts: 1 }),
|
||||
compilerOptions: {},
|
||||
extends: false,
|
||||
files: false,
|
||||
include: false,
|
||||
exclude: false,
|
||||
compileOnSave: false,
|
||||
typeAcquisition: {
|
||||
enable: false,
|
||||
exclude: false,
|
||||
include: false,
|
||||
},
|
||||
configFileName: "tsconfig.json",
|
||||
projectType: "configured",
|
||||
languageServiceEnabled: true,
|
||||
version: ts.version,
|
||||
...partial,
|
||||
});
|
||||
}
|
||||
|
||||
assertOpenFileTelemetryEvent(info: ts.server.OpenFileInfo): void {
|
||||
assert.deepEqual<ts.server.OpenFileInfoTelemetryEventData>(this.getEvent<ts.server.OpenFileInfoTelemetryEvent>(ts.server.OpenFileInfoTelemetryEvent), { info });
|
||||
}
|
||||
assertNoOpenFilesTelemetryEvent(): void {
|
||||
this.hasZeroEvent<ts.server.OpenFileInfoTelemetryEvent>(ts.server.OpenFileInfoTelemetryEvent);
|
||||
}
|
||||
}
|
||||
|
||||
export type TestSessionAndServiceHost = TestServerHostTrackingWrittenFiles & {
|
||||
patched: boolean;
|
||||
baselineHost(title: string): void;
|
||||
@@ -528,7 +453,6 @@ export interface TestSessionOptions extends ts.server.SessionOptions {
|
||||
export type TestSessionRequest<T extends ts.server.protocol.Request> = Pick<T, "command" | "arguments">;
|
||||
export class TestSession extends ts.server.Session {
|
||||
private seq = 0;
|
||||
public events: ts.server.protocol.Event[] = [];
|
||||
public testhost: TestSessionAndServiceHost;
|
||||
public override logger: Logger;
|
||||
|
||||
@@ -574,13 +498,7 @@ export class TestSession extends ts.server.Session {
|
||||
return this.executeCommand(request);
|
||||
}
|
||||
|
||||
public override event<T extends object>(body: T, eventName: string) {
|
||||
this.events.push(ts.server.toEvent(eventName, body));
|
||||
super.event(body, eventName);
|
||||
}
|
||||
|
||||
public clearMessages() {
|
||||
ts.clear(this.events);
|
||||
this.testhost.clearOutput();
|
||||
}
|
||||
}
|
||||
@@ -610,38 +528,32 @@ export function createSession(host: TestServerHost, opts: Partial<TestSessionOpt
|
||||
return new TestSession({ ...sessionOptions, ...opts });
|
||||
}
|
||||
|
||||
export function createSessionWithEventTracking<T extends ts.server.ProjectServiceEvent>(host: TestServerHost, eventNames: T["eventName"] | T["eventName"][], opts: Partial<TestSessionOptions> = {}) {
|
||||
const events: T[] = [];
|
||||
const session = createSession(host, {
|
||||
eventHandler: e => {
|
||||
if (ts.isArray(eventNames) ? eventNames.some(eventName => e.eventName === eventName) : eventNames === e.eventName) {
|
||||
events.push(e as T);
|
||||
}
|
||||
},
|
||||
...opts
|
||||
});
|
||||
|
||||
return { session, events };
|
||||
}
|
||||
|
||||
export function createSessionWithDefaultEventHandler<T extends ts.server.protocol.AnyEvent>(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial<TestSessionOptions> = {}) {
|
||||
const session = createSession(host, { canUseEvents: true, ...opts });
|
||||
|
||||
return {
|
||||
session,
|
||||
getEvents,
|
||||
clearEvents
|
||||
};
|
||||
|
||||
function getEvents() {
|
||||
return ts.mapDefined(host.getOutput(), s => {
|
||||
const e = mapOutputToJson(s);
|
||||
return (ts.isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function clearEvents() {
|
||||
session.clearMessages();
|
||||
export function createSessionWithCustomEventHandler(host: TestServerHost, opts?: Partial<TestSessionOptions>) {
|
||||
const session = createSession(host, { eventHandler, logger: createLoggerWithInMemoryLogs(host), ...opts });
|
||||
return session;
|
||||
function eventHandler(event: ts.server.ProjectServiceEvent) {
|
||||
let data = event.data as any;
|
||||
switch (event.eventName) {
|
||||
// No change to data
|
||||
case ts.server.ProjectsUpdatedInBackgroundEvent:
|
||||
case ts.server.LargeFileReferencedEvent:
|
||||
case ts.server.ProjectInfoTelemetryEvent:
|
||||
case ts.server.OpenFileInfoTelemetryEvent:
|
||||
break;
|
||||
// Convert project to project name
|
||||
case ts.server.ProjectLoadingStartEvent:
|
||||
case ts.server.ProjectLoadingFinishEvent:
|
||||
case ts.server.ProjectLanguageServiceStateEvent:
|
||||
data = { ...data, project: event.data.project.getProjectName() };
|
||||
break;
|
||||
// Map diagnostics
|
||||
case ts.server.ConfigFileDiagEvent:
|
||||
data = { ...data, diagnostics: ts.map(event.data.diagnostics, diagnostic => ts.server.formatDiagnosticToProtocol(diagnostic, /*includeFileName*/ true)) };
|
||||
break;
|
||||
default:
|
||||
ts.Debug.assertNever(event);
|
||||
}
|
||||
session.event(data, `CustomHandler::${event.eventName}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import { File } from "../virtualFileSystemWithWatch";
|
||||
import { createServerHost, File } from "../virtualFileSystemWithWatch";
|
||||
import {
|
||||
checkNumberOfProjects,
|
||||
fileStats,
|
||||
TestServerEventManager,
|
||||
baselineTsserverLogs,
|
||||
closeFilesForSession,
|
||||
createLoggerWithInMemoryLogs,
|
||||
createSession,
|
||||
openFilesForSession,
|
||||
toExternalFiles,
|
||||
} from "./helpers";
|
||||
|
||||
describe("unittests:: tsserver:: project telemetry", () => {
|
||||
it("does nothing for inferred project", () => {
|
||||
const file = makeFile("/a.js");
|
||||
const et = new TestServerEventManager([file]);
|
||||
et.service.openClientFile(file.path);
|
||||
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
|
||||
const host = createServerHost([file]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([file], session);
|
||||
baselineTsserverLogs("telemetry", "does nothing for inferred project", session);
|
||||
});
|
||||
|
||||
it("only sends an event once", () => {
|
||||
@@ -20,22 +26,16 @@ describe("unittests:: tsserver:: project telemetry", () => {
|
||||
const file2 = makeFile("/b.ts");
|
||||
const tsconfig = makeFile("/a/tsconfig.json", {});
|
||||
|
||||
const et = new TestServerEventManager([file, file2, tsconfig]);
|
||||
et.service.openClientFile(file.path);
|
||||
et.assertProjectInfoTelemetryEvent({}, tsconfig.path);
|
||||
|
||||
et.service.closeClientFile(file.path);
|
||||
checkNumberOfProjects(et.service, { configuredProjects: 1 });
|
||||
|
||||
et.service.openClientFile(file2.path);
|
||||
checkNumberOfProjects(et.service, { inferredProjects: 1 });
|
||||
|
||||
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
|
||||
|
||||
et.service.openClientFile(file.path);
|
||||
checkNumberOfProjects(et.service, { configuredProjects: 1, inferredProjects: 1 });
|
||||
|
||||
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
|
||||
const host = createServerHost([file, file2, tsconfig]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([file], session);
|
||||
closeFilesForSession([file], session);
|
||||
openFilesForSession([file2], session);
|
||||
openFilesForSession([file], session);
|
||||
baselineTsserverLogs("telemetry", "only sends an event once", session);
|
||||
});
|
||||
|
||||
it("counts files by extension", () => {
|
||||
@@ -44,54 +44,49 @@ describe("unittests:: tsserver:: project telemetry", () => {
|
||||
const compilerOptions: ts.CompilerOptions = { allowJs: true };
|
||||
const tsconfig = makeFile("/tsconfig.json", { compilerOptions, include: ["src"] });
|
||||
|
||||
const et = new TestServerEventManager([...files, notIncludedFile, tsconfig]);
|
||||
et.service.openClientFile(files[0].path);
|
||||
et.assertProjectInfoTelemetryEvent({
|
||||
fileStats: fileStats({ ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 }),
|
||||
compilerOptions,
|
||||
include: true,
|
||||
const host = createServerHost([...files, notIncludedFile, tsconfig]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([files[0]], session);
|
||||
baselineTsserverLogs("telemetry", "counts files by extension", session);
|
||||
});
|
||||
|
||||
it("works with external project", () => {
|
||||
const file1 = makeFile("/a.ts");
|
||||
const et = new TestServerEventManager([file1]);
|
||||
const host = createServerHost([file1]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
const compilerOptions: ts.server.protocol.CompilerOptions = { strict: true };
|
||||
|
||||
const projectFileName = "/hunter2/foo.csproj";
|
||||
|
||||
open();
|
||||
|
||||
// TODO: Apparently compilerOptions is mutated, so have to repeat it here!
|
||||
et.assertProjectInfoTelemetryEvent({
|
||||
compilerOptions: { strict: true },
|
||||
compileOnSave: true,
|
||||
// These properties can't be present for an external project, so they are undefined instead of false.
|
||||
extends: undefined,
|
||||
files: undefined,
|
||||
include: undefined,
|
||||
exclude: undefined,
|
||||
configFileName: "other",
|
||||
projectType: "external",
|
||||
}, "/hunter2/foo.csproj");
|
||||
|
||||
// Also test that opening an external project only sends an event once.
|
||||
et.service.closeClientFile(file1.path);
|
||||
closeFilesForSession([file1], session);
|
||||
|
||||
et.service.closeExternalProject(projectFileName);
|
||||
checkNumberOfProjects(et.service, { externalProjects: 0 });
|
||||
session.executeCommandSeq<ts.server.protocol.CloseExternalProjectRequest>({
|
||||
command: ts.server.protocol.CommandTypes.CloseExternalProject,
|
||||
arguments: { projectFileName }
|
||||
});
|
||||
|
||||
open();
|
||||
assert.equal(et.getEvents().length, 0);
|
||||
baselineTsserverLogs("telemetry", "works with external project", session);
|
||||
|
||||
function open(): void {
|
||||
et.service.openExternalProject({
|
||||
rootFiles: toExternalFiles([file1.path]),
|
||||
options: compilerOptions,
|
||||
projectFileName,
|
||||
session.executeCommandSeq<ts.server.protocol.OpenExternalProjectRequest>({
|
||||
command: ts.server.protocol.CommandTypes.OpenExternalProject,
|
||||
arguments: {
|
||||
rootFiles: toExternalFiles([file1.path]),
|
||||
options: compilerOptions,
|
||||
projectFileName,
|
||||
}
|
||||
});
|
||||
checkNumberOfProjects(et.service, { externalProjects: 1 });
|
||||
et.service.openClientFile(file1.path); // Only on file open the project will be updated
|
||||
openFilesForSession([file1], session); // Only on file open the project will be updated
|
||||
}
|
||||
});
|
||||
|
||||
@@ -128,39 +123,16 @@ describe("unittests:: tsserver:: project telemetry", () => {
|
||||
// Sensitive data doesn't get through even if sent to an option of safe type
|
||||
checkJs: "hunter2" as any as boolean,
|
||||
};
|
||||
const safeCompilerOptions: ts.CompilerOptions = {
|
||||
project: "",
|
||||
outFile: "",
|
||||
outDir: "",
|
||||
rootDir: "",
|
||||
baseUrl: "",
|
||||
rootDirs: [""],
|
||||
typeRoots: [""],
|
||||
types: [""],
|
||||
sourceRoot: "",
|
||||
mapRoot: "",
|
||||
jsxFactory: "",
|
||||
out: "",
|
||||
reactNamespace: "",
|
||||
charset: "",
|
||||
locale: "",
|
||||
declarationDir: "",
|
||||
paths: "" as any,
|
||||
|
||||
declaration: true,
|
||||
|
||||
lib: ["es6", "dom"],
|
||||
};
|
||||
(compilerOptions as any).unknownCompilerOption = "hunter2"; // These are always ignored.
|
||||
const tsconfig = makeFile("/tsconfig.json", { compilerOptions, files: ["/a.ts"] });
|
||||
|
||||
const et = new TestServerEventManager([file, tsconfig]);
|
||||
et.service.openClientFile(file.path);
|
||||
|
||||
et.assertProjectInfoTelemetryEvent({
|
||||
compilerOptions: safeCompilerOptions,
|
||||
files: true,
|
||||
const host = createServerHost([file, tsconfig]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([file], session);
|
||||
baselineTsserverLogs("telemetry", "does not expose paths", session);
|
||||
});
|
||||
|
||||
it("sends telemetry for extends, files, include, exclude, and compileOnSave", () => {
|
||||
@@ -173,16 +145,13 @@ describe("unittests:: tsserver:: project telemetry", () => {
|
||||
exclude: ["hunter2"],
|
||||
compileOnSave: true,
|
||||
});
|
||||
|
||||
const et = new TestServerEventManager([tsconfig, file]);
|
||||
et.service.openClientFile(file.path);
|
||||
et.assertProjectInfoTelemetryEvent({
|
||||
extends: true,
|
||||
files: true,
|
||||
include: true,
|
||||
exclude: true,
|
||||
compileOnSave: true,
|
||||
const host = createServerHost([file, tsconfig]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([file], session);
|
||||
baselineTsserverLogs("telemetry", "sends telemetry for extends, files, include, exclude, and compileOnSave", session);
|
||||
});
|
||||
|
||||
const autoJsCompilerOptions = {
|
||||
@@ -204,18 +173,13 @@ describe("unittests:: tsserver:: project telemetry", () => {
|
||||
exclude: [],
|
||||
},
|
||||
});
|
||||
const et = new TestServerEventManager([jsconfig, file]);
|
||||
et.service.openClientFile(file.path);
|
||||
et.assertProjectInfoTelemetryEvent({
|
||||
fileStats: fileStats({ js: 1 }),
|
||||
compilerOptions: autoJsCompilerOptions,
|
||||
typeAcquisition: {
|
||||
enable: true,
|
||||
include: true,
|
||||
exclude: false,
|
||||
},
|
||||
configFileName: "jsconfig.json",
|
||||
}, "/jsconfig.json");
|
||||
const host = createServerHost([file, jsconfig]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([file], session);
|
||||
baselineTsserverLogs("telemetry", "sends telemetry for typeAcquisition settings", session);
|
||||
});
|
||||
|
||||
it("sends telemetry for file sizes", () => {
|
||||
@@ -224,73 +188,67 @@ describe("unittests:: tsserver:: project telemetry", () => {
|
||||
const tsconfig = makeFile("/jsconfig.json", {
|
||||
compilerOptions: autoJsCompilerOptions
|
||||
});
|
||||
const et = new TestServerEventManager([tsconfig, jsFile, tsFile]);
|
||||
et.service.openClientFile(jsFile.path);
|
||||
et.assertProjectInfoTelemetryEvent({
|
||||
fileStats: fileStats({ js: 1, jsSize: 1, ts: 1, tsSize: 2 }),
|
||||
compilerOptions: autoJsCompilerOptions,
|
||||
typeAcquisition: {
|
||||
enable: true,
|
||||
include: false,
|
||||
exclude: false,
|
||||
},
|
||||
configFileName: "jsconfig.json",
|
||||
}, "/jsconfig.json");
|
||||
const host = createServerHost([tsconfig, tsFile, jsFile]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([jsFile], session);
|
||||
baselineTsserverLogs("telemetry", "sends telemetry for file sizes", session);
|
||||
});
|
||||
|
||||
it("detects whether language service was disabled", () => {
|
||||
const file = makeFile("/a.js");
|
||||
const tsconfig = makeFile("/jsconfig.json", {});
|
||||
const et = new TestServerEventManager([tsconfig, file]);
|
||||
const host = createServerHost([tsconfig, file]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
const fileSize = ts.server.maxProgramSizeForNonTsFiles + 1;
|
||||
et.host.getFileSize = () => fileSize;
|
||||
et.service.openClientFile(file.path);
|
||||
et.getEvent<ts.server.ProjectLanguageServiceStateEvent>(ts.server.ProjectLanguageServiceStateEvent);
|
||||
et.assertProjectInfoTelemetryEvent({
|
||||
fileStats: fileStats({ js: 1, jsSize: fileSize }),
|
||||
compilerOptions: autoJsCompilerOptions,
|
||||
configFileName: "jsconfig.json",
|
||||
typeAcquisition: {
|
||||
enable: true,
|
||||
include: false,
|
||||
exclude: false,
|
||||
},
|
||||
languageServiceEnabled: false,
|
||||
}, "/jsconfig.json");
|
||||
host.getFileSize = () => fileSize;
|
||||
openFilesForSession([file], session);
|
||||
baselineTsserverLogs("telemetry", "detects whether language service was disabled", session);
|
||||
});
|
||||
|
||||
describe("open files telemetry", () => {
|
||||
it("sends event for inferred project", () => {
|
||||
const ajs = makeFile("/a.js", "// @ts-check\nconst x = 0;");
|
||||
const bjs = makeFile("/b.js");
|
||||
const et = new TestServerEventManager([ajs, bjs]);
|
||||
|
||||
et.service.openClientFile(ajs.path);
|
||||
et.assertOpenFileTelemetryEvent({ checkJs: true });
|
||||
|
||||
et.service.openClientFile(bjs.path);
|
||||
et.assertOpenFileTelemetryEvent({ checkJs: false });
|
||||
const host = createServerHost([ajs, bjs]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([ajs, bjs], session);
|
||||
|
||||
// No repeated send for opening a file seen before.
|
||||
et.service.openClientFile(bjs.path);
|
||||
et.assertNoOpenFilesTelemetryEvent();
|
||||
openFilesForSession([bjs], session);
|
||||
baselineTsserverLogs("telemetry", "sends event for inferred project", session);
|
||||
});
|
||||
|
||||
it("not for '.ts' file", () => {
|
||||
const ats = makeFile("/a.ts", "");
|
||||
const et = new TestServerEventManager([ats]);
|
||||
|
||||
et.service.openClientFile(ats.path);
|
||||
et.assertNoOpenFilesTelemetryEvent();
|
||||
const host = createServerHost([ats]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([ats], session);
|
||||
baselineTsserverLogs("telemetry", "not for ts file", session);
|
||||
});
|
||||
|
||||
it("even for project with 'ts-check' in config", () => {
|
||||
const file = makeFile("/a.js");
|
||||
const compilerOptions: ts.CompilerOptions = { checkJs: true };
|
||||
const jsconfig = makeFile("/jsconfig.json", { compilerOptions });
|
||||
const et = new TestServerEventManager([jsconfig, file]);
|
||||
et.service.openClientFile(file.path);
|
||||
et.assertOpenFileTelemetryEvent({ checkJs: false });
|
||||
const host = createServerHost([jsconfig, file]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
logger: createLoggerWithInMemoryLogs(host)
|
||||
});
|
||||
openFilesForSession([file], session);
|
||||
baselineTsserverLogs("telemetry", "even for project with ts-check in config", session);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user