microsoft / TypeScript Public
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tsserver support for a virtual filesystem #48497
base: main
Are you sure you want to change the base?
Conversation
Except for the new test I added.
I should have done this as two commits. Sorry.
I'm pretty sure I need to delete and re-number some of the rest though.
Plus fix associated mistakes in the code found by the test.
Shortly to be undone, but whatever.
Doesn't quite build yet, uploading so I can use github to view the diff.
But editorServices needs some fixes. At least, createDirectory needs a recursive version.
Next: Make a way to create a vfs server from the start.
|
Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @amcasey, @mjbvz, @minestarks for you. Feel free to loop in other consumers/maintainers if necessary |
| let it; | ||
| while (!(it = createdFiles.next()).done) { | ||
| const document = it.value; | ||
| if (document.fileContent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can do fs.ensureFileOrFolder?
| if (deletedFiles) { | ||
| // TODO: Probably want to delete empty parent folders while they are empty too | ||
| for (const file of deletedFiles) { | ||
| fs.deleteFile(file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removeFileOrFolder has option to determine if its leaf node that you can use.
| if (fs.fileExists(it.value.file)) { | ||
| fs.modifyFile(it.value.file, it.value.fileContent); | ||
| } | ||
| else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldnt be the case right.. we can just call modifyFiles.. So we dont have to check if file exists or not
| * Used to specify the script kind of the file explicitly. It could be one of the following: | ||
| * "TS", "JS", "TSX", "JSX" | ||
| */ | ||
| scriptKindName?: ScriptKindName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the use of this
| updated: FileSystemRequestArgs[]; | ||
| } | ||
|
|
||
| export interface FileSystemRequestArgs extends FileRequestArgs { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should just create this without deriving from FileRequestArgs as we just need fileName and fileContent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably, it should resemble OpenRequestArgs.
| // TODO: This is almost certainly in harness/virtualFileSystemHost.ts, or should be. | ||
| function verifyFileSystem(host: TestFSWithWatch.VirtualServerHost | undefined, files: protocol.FileSystemRequestArgs[]) { | ||
| // 1. make sure that everything in files is there | ||
| assert.isDefined(host); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think instead of this you probably want to use baselines.. this becomes hard to maintain later on..
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() });
// You can diff the vfs using vfs.snap and vfs.diff to add to the baseline
// Do your things here
baselineTsserverLogs("configuredProjects", "when multiple projects are open detects correct default project", session);| @@ -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. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come this changed?
src/tsserver/webServer.ts
Outdated
| @@ -120,4 +145,28 @@ namespace ts.server { | |||
| // Start listening | |||
| session.listen(); | |||
| } | |||
|
|
|||
| function startVirtualFileSystemSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think instead of this you would want to create session with optional option fsSystem which defaults to host. And then in Editor services and session you want to verify all FS events or on the FS host.. So we dont need two sessions and this can be done from any type of Session.
When testing you would want to send the VFS instance and test few operations that use FS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VFS instance should the host project and services use but server host is used for everything else.. So GC and other things work as before
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And then --vfs options can be parsed in both NodeServer as well as WebServer
| { "path": "../jsTyping" }, | ||
| { "path": "../services" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably want to strip down so that you are not depending on these but its not important as anyways this is used in server which uses these dependencies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, it's not at all clear why a file system would depend on these.
I'm not sure why this fails locally, I suspect I need to `npm ci`.
If we're already reading arguments here, I'd probably just check for Refers to: src/tsserver/webServer.ts:88 in f67ef37. [](commit_id = f67ef37, deletion_comment = False) |
| { "path": "../jsTyping" }, | ||
| { "path": "../services" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, it's not at all clear why a file system would depend on these.
| "compilerOptions": { | ||
| "outFile": "../../built/local/vfs.js", | ||
| "types": [ | ||
| "node", "mocha", "chai" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want product code to depend on test frameworks?
| updated: FileSystemRequestArgs[]; | ||
| } | ||
|
|
||
| export interface FileSystemRequestArgs extends FileRequestArgs { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably, it should resemble OpenRequestArgs.
| /** For now, only 'memfs', initially for exclusive in-memory operation, but it could be other in-memory names later */ | ||
| fileSystem: string; | ||
| /** List of newly created or newly available files. */ | ||
| created: FileSystemRequestArgs[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it an error to create a file that already exists? If not, what's the point of having a separate updated property? Do those files provide diffs?
| [CommandNames.UpdateFileSystem]: (request: protocol.UpdateFileSystemRequest) => { | ||
| this.changeSeq++; | ||
| this.projectService.updateFileSystem( | ||
| request.arguments.created && arrayIterator(request.arguments.created), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we turning the arrays into iterators?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(It looks like you're following a precedent, so I'm probably missing something.)
src/tsserver/server.ts
Outdated
| @@ -79,6 +79,16 @@ namespace ts.server { | |||
| if (typeof process !== "undefined") { | |||
| start(initializeNodeSystem(), require("os").platform()); | |||
| } | |||
| // TODO: Not sure this is right | |||
| else if (findArgument("vfs")) { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasArgument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I don't know if we validate command line arguments but, if we do, we should probably warn/error if you pass this to a node-based server.
src/tsserver/webServer.ts
Outdated
| @@ -81,6 +93,19 @@ namespace ts.server { | |||
| } | |||
| } | |||
|
|
|||
|
|
|||
| function createVirtualFileSystem() { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: consider inlining this or, at least, moving it closer to the only caller.
src/tsserver/webServer.ts
Outdated
| Debug.assert(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 = TestFSWithWatch.createVirtualServerHost([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably rename TestFSWithWatch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does it relate to server.createWebSystem? Does one delegate to the other?
Project, editor services, session, etc all take an optional fshost and preferentially use it for filesystem operations.


Partly fixes #47600 -- there is probably more work needed here and definitely in vs code.