Add tsserver entrypoint for vfs

This commit is contained in:
Nathan Shively-Sanders
2022-03-30 08:21:07 -07:00
parent 197308efc1
commit 22c9470b69
7 changed files with 93 additions and 46 deletions
+10 -17
View File
@@ -757,8 +757,7 @@ namespace ts.server {
readonly currentDirectory: NormalizedPath;
readonly toCanonicalFileName: (f: string) => string;
public host: ServerHost;
public fs: ts.TestFSWithWatch.VirtualServerHost | undefined;
public readonly host: ServerHost;
public readonly logger: Logger;
public readonly cancellationToken: HostCancellationToken;
public readonly useSingleInferredProject: boolean;
@@ -3693,24 +3692,18 @@ namespace ts.server {
/* @internal */
updateFileSystem(createdFiles: Iterator<protocol.FileSystemRequestArgs> | undefined, updatedFiles?: Iterator<protocol.FileSystemRequestArgs>, deletedFiles?: string[]) {
// TODO: Maybe it is somehow gauche or verboten to use protocol types but the translation in applyChangesInOpenFiles seems stupid
// 1. set some internal tsserver state for mocked FS (if it hasn't already been set, this might not be the first message)
if (!this.fs) {
// -nervous laugh-
this.fs = ts.TestFSWithWatch.createVirtualServerHost([], { withSafeList: false })
;(this as any).host = this.fs
;(this.session as any).host = this.fs
}
// 2. update vfs
// I THINK that only vfs needs to update, because none of these files should be open.
// (I guess files could update from the filesystem while they are still open, but that's something to solve at the end of prototyping I think)
const fs = this.host as ts.TestFSWithWatch.VirtualServerHost;
if (createdFiles) {
let it
while (!(it = createdFiles.next()).done) {
const document = it.value
if (document.fileContent) {
if (!this.fs.directoryExists(ts.getDirectoryPath(document.file)))
this.fs.createDirectory(ts.getDirectoryPath(document.file), /*recursive*/ true);
this.fs.writeFile(document.file, document.fileContent);
if (!fs.directoryExists(ts.getDirectoryPath(document.file)))
fs.createDirectory(ts.getDirectoryPath(document.file), /*recursive*/ true);
fs.writeFile(document.file, document.fileContent);
}
}
}
@@ -3719,12 +3712,12 @@ namespace ts.server {
let it
while (!(it = updatedFiles.next()).done) {
if (it.value.fileContent) {
if (this.fs.fileExists(it.value.file)) {
this.fs.modifyFile(it.value.file, it.value.fileContent)
if (fs.fileExists(it.value.file)) {
fs.modifyFile(it.value.file, it.value.fileContent)
}
else {
this.fs.createDirectory(ts.getDirectoryPath(it.value.file), /*recursive*/ true);
this.fs.writeFile(it.value.file, it.value.fileContent);
fs.createDirectory(ts.getDirectoryPath(it.value.file), /*recursive*/ true);
fs.writeFile(it.value.file, it.value.fileContent);
}
}
}
@@ -3732,7 +3725,7 @@ namespace ts.server {
if (deletedFiles) {
// TODO: Probably want to delete empty parent folders while they are empty too
for (const file of deletedFiles) {
this.fs.deleteFile(file)
fs.deleteFile(file)
}
}
}
@@ -45,7 +45,7 @@ ${'content' in file ? file.content : file.fileContent}`;
// 1. make sure that everything in files is there
assert.isDefined(host)
const fs = (host as any).fs as ESMap<string, ts.TestFSWithWatch.FSEntry>
console.log(Array.from(fs.values() as any as Iterable<ts.TestFSWithWatch.FSEntry>).filter(ts.TestFSWithWatch.isFsFile))
// console.log(Array.from(fs.values() as any as Iterable<ts.TestFSWithWatch.FSEntry>).filter(ts.TestFSWithWatch.isFsFile))
assert.equal(Array.from(fs.values() as any as Iterable<ts.TestFSWithWatch.FSEntry>).filter(ts.TestFSWithWatch.isFsFile).length, files.length)
for (const { file, fileContent } of files) {
assert(host?.fileExists(file))
@@ -54,11 +54,8 @@ ${'content' in file ? file.content : file.fileContent}`;
// 2. make sure nothing else is
}
it("with updateFileSystem request", () => {
// TODO: Create a virtual host and make sure it works with session etc
// TODO: File watchers still seem wrong
// 5. send updateFS request with a create/update/delete of a watched file, assert that file watchers fired
// 6. probably some other watcher tests, not sure what
const host = createServerHost([]); // old path goes into virtualFileSystemWithWatch.ts, so I guess it's getting the old host, not the replaced one
// TODO: probably some other watcher tests, not sure what
const host = ts.TestFSWithWatch.createVirtualServerHost([], { withSafeList: false });
const session = createSession(host);
const created = [app, file1, file2, file3, config, lib]
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
@@ -79,8 +76,8 @@ ${'content' in file ? file.content : file.fileContent}`;
const fakehost = (session as any).host as ts.TestFSWithWatch.VirtualServerHost
assert.isDefined(project);
assert.equal(fakehost.fsWatches.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 0)
assert.equal(fakehost.watchedFiles.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 2)
assert.equal(fakehost.watchedFiles.size, 5)
verifyProjectVersion(project, 1);
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
@@ -92,14 +89,14 @@ ${'content' in file ? file.content : file.fileContent}`;
verifyProjectVersion(project, 2);
// Verify Texts
verifyFileSystem(service.fs, created)
verifyFileSystem(host, created)
verifyText(service, file1.file, file1.fileContent!);
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent!);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 0)
assert.equal(fakehost.watchedFiles.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 2)
assert.equal(fakehost.watchedFiles.size, 4)
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
command: protocol.CommandTypes.UpdateFileSystem,
@@ -114,14 +111,14 @@ ${'content' in file ? file.content : file.fileContent}`;
verifyProjectVersion(project, 2);
// Verify Texts
verifyFileSystem(service.fs, created)
verifyFileSystem(host, created)
verifyText(service, file1.file, file1.fileContent!);
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent!);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 0)
assert.equal(fakehost.watchedFiles.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 2)
assert.equal(fakehost.watchedFiles.size, 4)
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
command: protocol.CommandTypes.UpdateFileSystem,
@@ -132,18 +129,16 @@ ${'content' in file ? file.content : file.fileContent}`;
updated: [], //FileSystemRequestArgs[];
}
});
// also no change when deleting a file (changes session version but not project version?)
verifyProjectVersion(project, 2);
verifyProjectVersion(project, 3);
// Verify Texts
verifyFileSystem(service.fs, [app, file2, file3, config, lib])
verifyText(service, file1.file, file1.fileContent!);
verifyFileSystem(host, [app, file2, file3, config, lib])
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent!);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 0)
assert.equal(fakehost.watchedFiles.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 2)
assert.equal(fakehost.watchedFiles.size, 3)
session.executeCommandSeq<protocol.CloseRequest>({
command: protocol.CommandTypes.Close,
@@ -151,17 +146,16 @@ ${'content' in file ? file.content : file.fileContent}`;
});
// also no change when closing a file??? (changes session version but not project version?)
verifyProjectVersion(project, 2);
verifyProjectVersion(project, 3);
// Verify Texts
verifyFileSystem(service.fs, [app, file2, file3, config, lib])
verifyText(service, file1.file, file1.fileContent!);
verifyFileSystem(host, [app, file2, file3, config, lib])
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent!);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 0)
assert.equal(fakehost.watchedFiles.size, 0)
assert.equal(fakehost.fsWatchesRecursive.size, 2)
assert.equal(fakehost.watchedFiles.size, 4)
});
function verifyText(service: server.ProjectService, file: string, expected: string) {
+10
View File
@@ -79,6 +79,16 @@ namespace ts.server {
if (typeof process !== "undefined") {
start(initializeNodeSystem(), require("os").platform());
}
// TODO: Learn how to pass arguments to server
else if (findArgument("vfs")) {
// Get args from first message
const listener = (e: any) => {
removeEventListener("message", listener);
const args = e.data;
start(initializeVirtualFileSystem(args), "vfs");
};
addEventListener("message", listener);
}
else {
// Get args from first message
const listener = (e: any) => {
+1
View File
@@ -18,6 +18,7 @@
{ "path": "../jsTyping", "prepend": true },
{ "path": "../server", "prepend": true },
{ "path": "../webServer", "prepend": true },
{ "path": "../vfs", "prepend": true },
{ "path": "../deprecatedCompat", "prepend": true }
]
}
+49
View File
@@ -46,6 +46,18 @@ namespace ts.server {
};
}
export function initializeVirtualFileSystem(args: string[]): StartInput {
createVirtualFileSystem();
return {
args,
logger: createLogger(),
cancellationToken: nullCancellationToken,
// Only semantic mode right now
serverMode: LanguageServiceMode.Semantic,
startSession: startVirtualFileSystemSession
};
}
function createLogger() {
const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity"));
return cmdLineVerbosity !== undefined ? new MainProcessLogger(cmdLineVerbosity, { writeMessage }) : nullLogger;
@@ -81,6 +93,19 @@ namespace ts.server {
}
}
function createVirtualFileSystem() {
Debug.assert(ts.sys === undefined);
// TODO: I don't think I need the XMLHttpRequest code since vfs will get its files from messages sent by the owner
// ...but this means I may not need the webSession-copied code in startVirtualFileSystemSession
const vfshost = ts.TestFSWithWatch.createVirtualServerHost([])
setSys(vfshost);
const localeStr = findArgument("--locale");
if (localeStr) {
validateLocaleAndSetLanguage(localeStr, sys);
}
}
function hrtime(previous?: [number, number]) {
const now = self.performance.now() * 1e-3;
let seconds = Math.floor(now);
@@ -120,4 +145,28 @@ namespace ts.server {
// Start listening
session.listen();
}
function startVirtualFileSystemSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) {
class WorkerSession extends server.WorkerSession {
constructor() {
super(sys as ServerHost, { writeMessage }, options, logger, cancellationToken, hrtime);
}
exit() {
this.logger.info("Exiting...");
this.projectService.closeLog();
close();
}
listen() {
addEventListener("message", (message: any) => {
this.onMessage(message.data);
});
}
}
const session = new WorkerSession();
// Start listening
session.listen();
}
}
+2 -2
View File
@@ -10496,7 +10496,7 @@ declare namespace ts.server {
private ensureProjectForOpenFiles;
/**
* Open file whose contents is managed by the client
* @param filename is absolute pathname
* @param fileName is absolute pathname
* @param fileContent is a known version of the file content that is more up to date than the one on disk
*/
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult;
@@ -10512,7 +10512,7 @@ declare namespace ts.server {
private telemetryOnOpenFile;
/**
* Close file whose contents is managed by the client
* @param filename is absolute pathname
* @param uncheckedFileName is absolute pathname
*/
closeClientFile(uncheckedFileName: string): void;
private collectChanges;
@@ -168,13 +168,13 @@ tests/cases/conformance/types/asyncGenerators/types.asyncGenerators.es2018.2.ts(
async function * explicitReturnType10(): IterableIterator<number> {
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2741: Property '[Symbol.iterator]' is missing in type 'AsyncGenerator<number, any, undefined>' but required in type 'IterableIterator<number>'.
!!! related TS2728 /.ts/lib.es2015.iterable.d.ts:55:5: '[Symbol.iterator]' is declared here.
!!! related TS2728 /.ts/lib.es2015.iterable.d.ts:90:5: '[Symbol.iterator]' is declared here.
yield 1;
}
async function * explicitReturnType11(): Iterable<number> {
~~~~~~~~~~~~~~~~
!!! error TS2741: Property '[Symbol.iterator]' is missing in type 'AsyncGenerator<any, any, any>' but required in type 'Iterable<number>'.
!!! related TS2728 /.ts/lib.es2015.iterable.d.ts:51:5: '[Symbol.iterator]' is declared here.
!!! related TS2728 /.ts/lib.es2015.iterable.d.ts:82:5: '[Symbol.iterator]' is declared here.
yield 1;
}
async function * explicitReturnType12(): Iterator<number> {