diff --git a/src/harness/mocks.ts b/src/harness/mocks.ts
index 051fdc094a4..315677b7117 100644
--- a/src/harness/mocks.ts
+++ b/src/harness/mocks.ts
@@ -1,5 +1,7 @@
///
-///
+/// import { debug } from "util";
+
+
// NOTE: The contents of this file are all exported from the namespace 'mocks'. This is to
// support the eventual conversion of harness into a modular system.
@@ -298,7 +300,6 @@ namespace mocks {
return undefined;
}
- // TOOD: record and invoke callbacks to simulate timer events
public setTimeout(callback: (...args: any[]) => void, timeout: number, ...args: any[]) {
return this.timers.setTimeout(callback, timeout, ...args);
}
diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts
index 7ef3e2aa1af..36f9d1c366b 100644
--- a/src/harness/unittests/compileOnSave.ts
+++ b/src/harness/unittests/compileOnSave.ts
@@ -1,6 +1,7 @@
///
///
///
+///
namespace ts.projectSystem {
import CommandNames = server.CommandNames;
@@ -58,6 +59,24 @@ namespace ts.projectSystem {
// A compile on save affected file request using file1
let moduleFile1FileListRequest: server.protocol.Request;
+ let sharedFs: vfs.VirtualFileSystem;
+ before(() => {
+ const fs = new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true);
+ fs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`);
+ fs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`);
+ fs.addFile("/a/b/file1Consumer2.ts", `import {Foo} from "./moduleFile1"; let z = 10;`);
+ fs.addFile("/a/b/globalFile3.ts", `interface GlobalFoo { age: number }`);
+ fs.addFile("/a/b/moduleFile2.ts", `export var Foo4 = 10;`);
+ fs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`);
+ fs.addFile(libFile.path, libFile.content);
+ fs.makeReadOnly();
+ sharedFs = fs;
+ });
+
+ after(() => {
+ sharedFs = undefined;
+ });
+
beforeEach(() => {
moduleFile1 = {
path: "/a/b/moduleFile1.ts",
@@ -115,7 +134,7 @@ namespace ts.projectSystem {
});
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 = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -140,7 +159,7 @@ namespace ts.projectSystem {
});
it("should be up-to-date with the reference map changes", () => {
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -187,7 +206,7 @@ namespace ts.projectSystem {
});
it("should be up-to-date with changes made in non-open files", () => {
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -196,15 +215,13 @@ namespace ts.projectSystem {
// Send an initial compileOnSave request
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
- file1Consumer1.content = `let y = 10;`;
- host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
-
+ host.vfs.writeFile(file1Consumer1.path, `let y = 10;`);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
});
it("should be up-to-date with deleted files", () => {
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -212,13 +229,14 @@ namespace ts.projectSystem {
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
session.executeCommand(changeModuleFile1ShapeRequest1);
- // Delete file1Consumer2
- host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]);
+
+ host.vfs.removeFile(file1Consumer2.path);
+
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
});
it("should be up-to-date with newly created files", () => {
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -229,8 +247,7 @@ namespace ts.projectSystem {
path: "/a/b/file1Consumer3.ts",
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
};
- host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3, globalFile3, configFile, libFile]);
- host.runQueuedTimeoutCallbacks();
+ host.vfs.writeFile(file1Consumer3.path, file1Consumer3.content);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]);
});
@@ -254,7 +271,12 @@ namespace ts.projectSystem {
}`
};
- const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(moduleFile1.path, moduleFile1.content);
+ host.vfs.addFile(file1Consumer1.path, file1Consumer1.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+ host.vfs.addFile(libFile.path, libFile.content);
+
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -271,7 +293,7 @@ namespace ts.projectSystem {
});
it("should return all files if a global file changed shape", () => {
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -297,7 +319,8 @@ namespace ts.projectSystem {
content: `{}`
};
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
+ host.vfs.writeFile(configFile.path, configFile.content);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
@@ -319,7 +342,10 @@ namespace ts.projectSystem {
}`
};
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]);
+ const host = new mocks.MockServerHost(sharedFs.shadow());
+ host.vfs.writeFile(configFile.path, configFile.content);
+ host.vfs.addFile(configFile2.path, configFile2.content);
+
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -338,7 +364,12 @@ namespace ts.projectSystem {
}`
};
- const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(moduleFile1.path, moduleFile1.content);
+ host.vfs.addFile(file1Consumer1.path, file1Consumer1.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+ host.vfs.addFile(libFile.path, libFile.content);
+
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
@@ -367,7 +398,12 @@ namespace ts.projectSystem {
}`
};
- const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(moduleFile1.path, moduleFile1.content);
+ host.vfs.addFile(file1Consumer1.path, file1Consumer1.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+ host.vfs.addFile(libFile.path, libFile.content);
+
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
@@ -389,7 +425,16 @@ namespace ts.projectSystem {
path: "/a/b/file1Consumer1Consumer1.ts",
content: `import {y} from "./file1Consumer1";`
};
- const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]);
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(moduleFile1.path, moduleFile1.content);
+ host.vfs.addFile(file1Consumer1.path, file1Consumer1.content);
+ host.vfs.addFile(file1Consumer1Consumer1.path, file1Consumer1Consumer1.content);
+ host.vfs.addFile(globalFile3.path, globalFile3.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+ host.vfs.addFile(libFile.path, libFile.content);
+ host.vfs.addFile(file1Consumer1Consumer1.path, file1Consumer1Consumer1.content);
+
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -422,7 +467,12 @@ namespace ts.projectSystem {
///
export var t2 = 10;`
};
- const host = createServerHost([file1, file2, configFile]);
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(file1.path, file1.content);
+ host.vfs.addFile(file2.path, file2.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
@@ -438,7 +488,13 @@ namespace ts.projectSystem {
const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
- const host = createServerHost([file1, file2, file3, configFile1, configFile2]);
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(file1.path, file1.content);
+ host.vfs.addFile(file2.path, file2.content);
+ host.vfs.addFile(file3.path, file3.content);
+ host.vfs.addFile(configFile1.path, configFile1.content);
+ host.vfs.addFile(configFile2.path, configFile2.content);
+
const session = createSession(host);
openFilesForSession([file1, file2, file3], session);
@@ -457,16 +513,23 @@ namespace ts.projectSystem {
///
export var x = Foo();`
};
- const host = createServerHost([moduleFile1, referenceFile1, configFile]);
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(moduleFile1.path, moduleFile1.content);
+ host.vfs.addFile(referenceFile1.path, referenceFile1.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+
const session = createSession(host);
openFilesForSession([referenceFile1], session);
- host.reloadFS([referenceFile1, configFile]);
+
+ host.vfs.removeFile(moduleFile1.path);
const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
{ projectFileName: configFile.path, files: [referenceFile1] }
]);
+
const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path });
sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []);
});
@@ -478,10 +541,15 @@ namespace ts.projectSystem {
///
export var x = Foo();`
};
- const host = createServerHost([referenceFile1, configFile]);
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(referenceFile1.path, referenceFile1.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+
const session = createSession(host);
openFilesForSession([referenceFile1], session);
+
const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
{ projectFileName: configFile.path, files: [referenceFile1] }
@@ -502,7 +570,10 @@ namespace ts.projectSystem {
path: path + ts.Extension.Ts,
content: lines.join(newLine)
};
- const host = createServerHost([f], { newLine });
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true), /*executingFilePath*/ undefined, newLine);
+ host.vfs.addFile(f.path, f.content);
+
const session = createSession(host);
const openRequest: server.protocol.OpenRequest = {
seq: 1,
@@ -536,7 +607,13 @@ namespace ts.projectSystem {
path: "/a/b/tsconfig.json",
content: `{}`
};
- const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" });
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true), /*executingFilePath*/ undefined, "\r\n");
+ host.vfs.addFile(file1.path, file1.content);
+ host.vfs.addFile(file2.path, file2.content);
+ host.vfs.addFile(configFile.path, configFile.content);
+ host.vfs.addFile(libFile.path, libFile.content);
+
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, { typingsInstaller });
@@ -564,7 +641,13 @@ namespace ts.projectSystem {
content: "console.log('file3');"
};
const externalProjectName = "/a/b/externalproject";
- const host = createServerHost([file1, file2, file3, libFile]);
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(file1.path, file1.content);
+ host.vfs.addFile(file2.path, file2.content);
+ host.vfs.addFile(file3.path, file3.content);
+ host.vfs.addFile(libFile.path, libFile.content);
+
const session = createSession(host);
const projectService = session.getProjectService();
@@ -596,7 +679,11 @@ namespace ts.projectSystem {
content: "consonle.log('file1');"
};
const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj";
- const host = createServerHost([file1, libFile]);
+
+ const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true));
+ host.vfs.addFile(file1.path, file1.content);
+ host.vfs.addFile(libFile.path, libFile.content);
+
const session = createSession(host);
const projectService = session.getProjectService();
diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts
index 0fcaef05443..92f34f6b832 100644
--- a/src/harness/vfs.ts
+++ b/src/harness/vfs.ts
@@ -869,7 +869,7 @@ namespace vfs {
* Gets the root directory for this entry.
*/
public get root(): VirtualDirectory | undefined {
- return this.parent instanceof VirtualRoot ? this : undefined;
+ return this.parent instanceof VirtualRoot ? this : this.parent.root;
}
/**
@@ -1129,11 +1129,13 @@ namespace vfs {
const { files, directories } = resolver.getEntries(this);
for (const dir of directories) {
const vdir = new VirtualDirectory(this, dir, resolver);
+ vdir.addListener("fileSystemChange", this._onChildFileSystemChange);
if (this.isReadOnly) vdir.makeReadOnly();
entries.set(vdir.name, vdir);
}
for (const file of files) {
const vfile = new VirtualFile(this, file, resolver);
+ vfile.addListener("fileSystemChange", this._onChildFileSystemChange);
if (this.isReadOnly) vfile.makeReadOnly();
entries.set(vfile.name, vfile);
}
@@ -1141,6 +1143,7 @@ namespace vfs {
else if (shadowRoot) {
shadowRoot.getOwnEntries().forEach(entry => {
const clone = entry.shadow(this);
+ clone.addListener("fileSystemChange", this._onChildFileSystemChange);
if (this.isReadOnly) clone.makeReadOnly();
entries.set(clone.name, clone);
});
@@ -1292,7 +1295,7 @@ namespace vfs {
this.updateAccessTime();
this.updateModificationTime();
this.emit("childAdded", entry);
- entry.emit("fileSystemChange", entry.path, "added");
+ this.emit("fileSystemChange", entry.path, "added");
entry.addListener("fileSystemChange", this._onChildFileSystemChange);
}
@@ -1618,34 +1621,14 @@ namespace vfs {
* Gets the text content of this file.
*/
public get content(): string | undefined {
- if (!this._contentWasSet) {
- const resolver = this._resolver;
- const shadowRoot = this._shadowRoot;
- if (resolver) {
- this._resolver = undefined;
- this._content = typeof resolver === "function" ? resolver(this) : resolver.getContent(this);
- this._contentWasSet = true;
- }
- else if (shadowRoot) {
- this._content = shadowRoot.content;
- this._contentWasSet = true;
- }
- }
- return this._content;
+ return this.readContent(/*updateAccessTime*/ false);
}
/**
* Sets the text content of this file.
*/
public set content(value: string | undefined) {
- if (this.content !== value) {
- this.writePreamble();
- this._resolver = undefined;
- this._content = value;
- this._contentWasSet = true;
- this.emit("contentChanged", this);
- this.emit("fileSystemChange", this.path, "modified");
- }
+ this.writeContent(value, /*updateModificationType*/ false);
}
/**
@@ -1663,18 +1646,41 @@ namespace vfs {
/**
* Reads the content and updates the file's access time.
*/
- public readContent() {
- const content = this.content;
- if (content) this.updateAccessTime();
- return content;
+ public readContent(updateAccessTime = true) {
+ if (!this._contentWasSet) {
+ const resolver = this._resolver;
+ const shadowRoot = this._shadowRoot;
+ if (resolver) {
+ this._resolver = undefined;
+ this._content = typeof resolver === "function" ? resolver(this) : resolver.getContent(this);
+ this._contentWasSet = true;
+ }
+ else if (shadowRoot) {
+ this._content = shadowRoot.content;
+ this._contentWasSet = true;
+ }
+ }
+ if (this._content && updateAccessTime) {
+ this.updateAccessTime();
+ }
+ return this._content;
}
/**
* Writes the provided content and updates the file's modification time.
*/
- public writeContent(content: string) {
- this.content = content;
- if (content) this.updateModificationTime();
+ public writeContent(content: string | undefined, updateModificationTime = true) {
+ if (this.content !== content) {
+ this.writePreamble();
+ this._resolver = undefined;
+ this._content = content;
+ this._contentWasSet = true;
+ if (content && updateModificationTime) {
+ this.updateModificationTime();
+ }
+ this.emit("contentChanged", this);
+ this.emit("fileSystemChange", this.path, "modified");
+ }
}
public getStats() {