mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Copy file watching from virtualFileSystemWithWatch
Shortly to be undone, but whatever.
This commit is contained in:
@@ -1831,7 +1831,8 @@ namespace ts.server {
|
||||
if (this.serverMode !== LanguageServiceMode.Semantic) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO: My code leaves deleted files as: the scriptinfo doesn't list a containing project, but the contianing ProjectService.openFiles still has the file
|
||||
// OR maybe vice versa
|
||||
Debug.assert(!isOpenScriptInfo(info) || this.openFiles.has(info.path));
|
||||
const projectRootPath = this.openFiles.get(info.path);
|
||||
const scriptInfo = Debug.checkDefined(this.getScriptInfo(info.path));
|
||||
@@ -3691,11 +3692,6 @@ namespace ts.server {
|
||||
// TODO: Maybe it is somehow gauche or verboten to use protocol types but the translation in applyChangesInOpenFiles seems stupid
|
||||
// It is also WEIRD that we use iterators here not arrays
|
||||
// 1. set some internal tsserver state for mocked FS (if it hasn't already been set, this might not be the first message)
|
||||
// - change this.host at least
|
||||
this.host // no, it's readonly, we get this from a parent object
|
||||
// - ScriptInfo instances might need to update their host -- what is textStorage?
|
||||
// - they deffo have FileWatcher instances
|
||||
// - TextStorage.host needs to update
|
||||
if (!this.fs) {
|
||||
this.fs = new vfs.FileSystem(/*ignoreCase*/ true, {
|
||||
files: {
|
||||
@@ -3704,9 +3700,13 @@ namespace ts.server {
|
||||
cwd: "/", // maybe not needed
|
||||
meta: { } // probably not needed
|
||||
})
|
||||
// -nervous laugh-
|
||||
;(this as any).host = new fakes.FakeServerHost(this.fs, { executingFilePath: "TEST" })
|
||||
;(this.session as any).host = this.host
|
||||
}
|
||||
// 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)
|
||||
if (createdFiles) {
|
||||
let it
|
||||
while (!(it = createdFiles.next()).done) {
|
||||
@@ -3728,10 +3728,8 @@ namespace ts.server {
|
||||
this.fs.apply(fileset)
|
||||
}
|
||||
if (deletedFiles) {
|
||||
// TODO: - closeClientFile -> closeOpenFile -> ScriptInfo.close
|
||||
// (maybe -- it may be enough to just delete it from the filesystem)
|
||||
// these files should already be closed, so I THINK deleting it is enough
|
||||
for (const file of deletedFiles) {
|
||||
this.closeClientFile(file) // maybe copy stuff from applyChanges -- mostly I just want to leave a pointer to more code
|
||||
this.fs.rimrafSync(file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,22 +40,35 @@ interface Array<T> { length: number; [n: number]: T; }`
|
||||
return `// some copy right notice
|
||||
${'content' in file ? file.content : file.fileContent}`;
|
||||
}
|
||||
it("with updateOpen request", () => {
|
||||
function verifyFileSystem(fs: vfs.FileSystem | undefined, files: protocol.FileSystemRequestArgs[]) {
|
||||
// 1. make sure that everything in files is there
|
||||
assert.isDefined(fs)
|
||||
const result = fs!.scanSync('.', "descendants-or-self", {
|
||||
accept: (_, stats) => stats.isFile()
|
||||
})
|
||||
assert.equal(result.length, files.length)
|
||||
let i = 0
|
||||
for (const { file, fileContent } of files) {
|
||||
assert.equal(result[i], file)
|
||||
assert.equal(fs!.readFileSync(file, 'utf8'), fileContent)
|
||||
i++
|
||||
}
|
||||
// 2. make sure nothing else is
|
||||
}
|
||||
it("with updateFileSystem request", () => {
|
||||
// 1. Create a server host with no files, then updateFS and make sure everything works as before
|
||||
// Things still to test
|
||||
// 2. Send another updateFS request and assert that the vfs content changes
|
||||
// 3. Send another updateFS request and assert that the internal reported content changes (such as projectversion)
|
||||
// 4. Send a close message (or whatever will write a file?) and make sure that the vfs state is updated
|
||||
// after file watchers are implemented:
|
||||
// 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
|
||||
const session = createSession(host);
|
||||
const created = [app, file1, file2, file3, config, lib]
|
||||
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
|
||||
command: protocol.CommandTypes.UpdateFileSystem,
|
||||
arguments:{
|
||||
fileSystem: 'memfs',
|
||||
created: [app, file3, file1, file2, lib, config],
|
||||
created,
|
||||
deleted: [], // string[];
|
||||
updated: [], //FileSystemRequestArgs[];
|
||||
}
|
||||
@@ -64,11 +77,15 @@ ${'content' in file ? file.content : file.fileContent}`;
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: { file: app.file }
|
||||
});
|
||||
const service = session.getProjectService();
|
||||
const service = session.getProjectService(); // session -> service -> project
|
||||
const project = service.configuredProjects.get(config.file)!;
|
||||
const vfs = (session as any).host.vfs
|
||||
const fakehost = (session as any).host as fakes.FakeServerHost
|
||||
assert.isDefined(vfs);
|
||||
assert.isDefined(project);
|
||||
assert.equal(fakehost.fsWatches.size, 0)
|
||||
assert.equal(fakehost.fsWatchesRecursive.size, 0)
|
||||
assert.equal(fakehost.watchedFiles.size, 0)
|
||||
verifyProjectVersion(project, 1);
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
@@ -80,10 +97,14 @@ ${'content' in file ? file.content : file.fileContent}`;
|
||||
verifyProjectVersion(project, 2);
|
||||
|
||||
// Verify Texts
|
||||
verifyFileSystem(service.fs, 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)
|
||||
|
||||
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
|
||||
command: protocol.CommandTypes.UpdateFileSystem,
|
||||
@@ -98,10 +119,14 @@ ${'content' in file ? file.content : file.fileContent}`;
|
||||
verifyProjectVersion(project, 2);
|
||||
|
||||
// Verify Texts
|
||||
verifyFileSystem(service.fs, 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)
|
||||
|
||||
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
|
||||
command: protocol.CommandTypes.UpdateFileSystem,
|
||||
@@ -112,14 +137,36 @@ ${'content' in file ? file.content : file.fileContent}`;
|
||||
updated: [], //FileSystemRequestArgs[];
|
||||
}
|
||||
});
|
||||
verifyProjectVersion(project, 3);
|
||||
// also no change when deleting a file (changes session version but not project version?)
|
||||
verifyProjectVersion(project, 2);
|
||||
|
||||
// Verify Texts
|
||||
verifyFileSystem(service.fs, [app, file2, file3, config, lib])
|
||||
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)
|
||||
|
||||
session.executeCommandSeq<protocol.CloseRequest>({
|
||||
command: protocol.CommandTypes.Close,
|
||||
arguments: { file: app.file }
|
||||
});
|
||||
|
||||
// also no change when closing a file??? (changes session version but not project version?)
|
||||
verifyProjectVersion(project, 2);
|
||||
|
||||
// Verify Texts
|
||||
verifyFileSystem(service.fs, [app, file2, file3, config, lib])
|
||||
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, 1)
|
||||
});
|
||||
|
||||
function verifyText(service: server.ProjectService, file: string, expected: string) {
|
||||
|
||||
+152
-20
@@ -218,35 +218,167 @@ namespace fakes {
|
||||
* @implements {server.ServerHost} but that would create a circular dependency
|
||||
*/
|
||||
export class FakeServerHost extends System {
|
||||
watchFile(/*path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, options?: ts.WatchOptions*/): ts.FileWatcher {
|
||||
throw new Error("Not implemented: Still need to steal implementation from virtualFileSystemWithWatch.ts")
|
||||
return {
|
||||
close() {
|
||||
}
|
||||
}
|
||||
timeoutCallbacks = new Callbacks()
|
||||
immediateCallbacks = new Callbacks()
|
||||
readonly watchedFiles = ts.createMultiMap<ts.Path, TestFileWatcher>();
|
||||
readonly fsWatches = ts.createMultiMap<ts.Path, TestFsWatcher>();
|
||||
readonly fsWatchesRecursive = ts.createMultiMap<ts.Path, TestFsWatcher>();
|
||||
readonly currentDirectory: string // TODO: Needed? Probably not
|
||||
readonly toPath: (f: string) => ts.Path;
|
||||
watchFile: ts.HostWatchFile
|
||||
watchDirectory: ts.HostWatchDirectory
|
||||
constructor(vfs: vfs.FileSystem, options: SystemOptions = {}) {
|
||||
super(vfs, options)
|
||||
|
||||
this.currentDirectory = '/'; // THIS IS FINE
|
||||
this.toPath = s => ts.toPath(s, this.currentDirectory, s => s);
|
||||
|
||||
const { watchFile, watchDirectory } = ts.createSystemWatchFunctions({
|
||||
// We dont have polling watch file
|
||||
// it is essentially fsWatch but lets get that separate from fsWatch and
|
||||
// into watchedFiles for easier testing
|
||||
pollingWatchFile: /*tscWatchFile === Tsc_WatchFile.SingleFileWatcherPerName ?
|
||||
createSingleFileWatcherPerName(
|
||||
this.watchFileWorker.bind(this),
|
||||
this.useCaseSensitiveFileNames
|
||||
) :*/
|
||||
this.watchFileWorker.bind(this),
|
||||
getModifiedTime: this.getModifiedTime.bind(this),
|
||||
setTimeout: this.setTimeout.bind(this),
|
||||
clearTimeout: this.clearTimeout.bind(this),
|
||||
fsWatch: this.fsWatch.bind(this),
|
||||
fileExists: this.fileExists.bind(this),
|
||||
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
|
||||
getCurrentDirectory: this.getCurrentDirectory.bind(this),
|
||||
// TODO: Tests usually set "run without recursive watches" to true, except for two tests
|
||||
// watchOptions/with excludeFiles option${...}
|
||||
// and the same, but excludeDirectories
|
||||
// .............guessing that it's true on a real FS
|
||||
fsSupportsRecursiveFsWatch: true, /*tscWatchDirectory ? false : !runWithoutRecursiveWatches */
|
||||
directoryExists: this.directoryExists.bind(this),
|
||||
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
|
||||
realpath: this.realpath.bind(this),
|
||||
tscWatchFile: undefined,
|
||||
tscWatchDirectory: undefined,
|
||||
defaultWatchFileKind: () => undefined, // () => this.defaultWatchFileKind?.(),
|
||||
})
|
||||
this.watchFile = watchFile
|
||||
this.watchDirectory = watchDirectory
|
||||
}
|
||||
watchDirectory(/*path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, options?: ts.WatchOptions*/): ts.FileWatcher {
|
||||
throw new Error("Not implemented: Still need to steal implementation from virtualFileSystemWithWatch.ts")
|
||||
return {
|
||||
close() {
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
|
||||
// TODO: Probably want to do a fake thing, actually (or parametrised by the constructor)
|
||||
return setTimeout(callback, ms, ...args)
|
||||
setTimeout(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any {
|
||||
return this.timeoutCallbacks.register(callback, args)
|
||||
}
|
||||
clearTimeout(timeoutId: any): void {
|
||||
clearTimeout(timeoutId)
|
||||
this.timeoutCallbacks.unregister(timeoutId)
|
||||
}
|
||||
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
|
||||
return setImmediate(callback, ...args)
|
||||
return this.immediateCallbacks.register(callback, args)
|
||||
}
|
||||
clearImmediate(timeoutId: any): void {
|
||||
clearImmediate(timeoutId)
|
||||
this.immediateCallbacks.unregister(timeoutId)
|
||||
}
|
||||
watchFileWorker(fileName: string, cb: ts.FileWatcherCallback, pollingInterval: ts.PollingInterval) {
|
||||
console.log('watchfileworker')
|
||||
return createWatcher(
|
||||
this.watchedFiles,
|
||||
this.toFullPath(fileName),
|
||||
{ fileName, cb, pollingInterval }
|
||||
);
|
||||
}
|
||||
|
||||
fsWatch(
|
||||
fileOrDirectory: string,
|
||||
_entryKind: ts.FileSystemEntryKind,
|
||||
cb: ts.FsWatchCallback,
|
||||
recursive: boolean,
|
||||
fallbackPollingInterval: ts.PollingInterval,
|
||||
fallbackOptions: ts.WatchOptions | undefined): ts.FileWatcher {
|
||||
console.log('fswatch')
|
||||
/*return this.runWithFallbackPolling ?
|
||||
this.watchFile(
|
||||
fileOrDirectory,
|
||||
createFileWatcherCallback(cb),
|
||||
fallbackPollingInterval,
|
||||
fallbackOptions
|
||||
) :*/
|
||||
return createWatcher(
|
||||
recursive ? this.fsWatchesRecursive : this.fsWatches,
|
||||
this.toFullPath(fileOrDirectory),
|
||||
{
|
||||
directoryName: fileOrDirectory,
|
||||
cb,
|
||||
fallbackPollingInterval,
|
||||
fallbackOptions
|
||||
}
|
||||
);
|
||||
}
|
||||
toFullPath(s: string) {
|
||||
return this.toPath(this.toNormalizedAbsolutePath(s));
|
||||
}
|
||||
toNormalizedAbsolutePath(s: string) {
|
||||
return ts.getNormalizedAbsolutePath(s, this.currentDirectory);
|
||||
}
|
||||
}
|
||||
function createWatcher<T>(map: ts.MultiMap<ts.Path, T>, path: ts.Path, callback: T): ts.FileWatcher {
|
||||
map.add(path, callback);
|
||||
return { close: () => map.remove(path, callback) };
|
||||
}
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
type TimeOutCallback = () => any;
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
interface TestFileWatcher {
|
||||
cb: ts.FileWatcherCallback;
|
||||
fileName: string;
|
||||
pollingInterval: ts.PollingInterval;
|
||||
}
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
interface TestFsWatcher {
|
||||
cb: ts.FsWatchCallback;
|
||||
directoryName: string;
|
||||
fallbackPollingInterval: ts.PollingInterval;
|
||||
fallbackOptions: ts.WatchOptions | undefined;
|
||||
}
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
class Callbacks {
|
||||
private map: TimeOutCallback[] = [];
|
||||
private nextId = 1;
|
||||
getNextId() {
|
||||
return this.nextId;
|
||||
}
|
||||
register(cb: (...args: any[]) => void, args: any[]) {
|
||||
const timeoutId = this.nextId;
|
||||
this.nextId++;
|
||||
this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args);
|
||||
return timeoutId;
|
||||
}
|
||||
unregister(id: any) {
|
||||
if (typeof id === "number") {
|
||||
delete this.map[id];
|
||||
}
|
||||
}
|
||||
count() {
|
||||
// ??????????????????????????????????????????????????????????????????
|
||||
let n = 0;
|
||||
for (const _ in this.map) {
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
invoke(invokeKey?: number) {
|
||||
if (invokeKey) {
|
||||
this.map[invokeKey]();
|
||||
delete this.map[invokeKey];
|
||||
return;
|
||||
}
|
||||
// Note: invoking a callback may result in new callbacks been queued,
|
||||
// so do not clear the entire callback list regardless. Only remove the
|
||||
// ones we have invoked.
|
||||
for (const key in this.map) {
|
||||
this.map[key]();
|
||||
delete this.map[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fake `ts.CompilerHost` that leverages a virtual file system.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user