mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Partial deprecation of non-vfs TestServerHost
This commit is contained in:
parent
646c32cb09
commit
4fbc74ec26
@ -479,4 +479,76 @@ namespace core {
|
||||
splitLinesWorker(text, lineStarts, /*lines*/ undefined, /*removeEmptyElements*/ false);
|
||||
return lineStarts;
|
||||
}
|
||||
|
||||
//
|
||||
// Cryptography
|
||||
//
|
||||
|
||||
const H = new Uint32Array(5);
|
||||
const W = new Uint8Array(80);
|
||||
const B = new Uint8Array(64);
|
||||
const BLOCK_SIZE = 64;
|
||||
|
||||
export function sha1(message: string): string {
|
||||
let buffer = B;
|
||||
const textSize = message.length;
|
||||
const messageSize = textSize * 2;
|
||||
const finalBlockSize = messageSize % BLOCK_SIZE;
|
||||
const padSize = (finalBlockSize < BLOCK_SIZE - 8 - 1 ? BLOCK_SIZE : BLOCK_SIZE * 2) - finalBlockSize;
|
||||
const byteLength = messageSize + padSize;
|
||||
if (byteLength > BLOCK_SIZE) {
|
||||
buffer = new Uint8Array(byteLength);
|
||||
}
|
||||
|
||||
const bufferView = new DataView(buffer.buffer);
|
||||
for (let i = 0; i < textSize; ++i) {
|
||||
bufferView.setUint16(i * 2, message.charCodeAt(i));
|
||||
}
|
||||
|
||||
buffer[messageSize] = 0x80;
|
||||
bufferView.setUint32(byteLength - 4, messageSize * 8);
|
||||
H[0] = 0x67452301, H[1] = 0xefcdab89, H[2] = 0x98badcfe, H[3] = 0x10325476, H[4] = 0xc3d2e1f0;
|
||||
for (let offset = 0; offset < byteLength; offset += BLOCK_SIZE) {
|
||||
let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4];
|
||||
for (let i = 0; i < 80; ++i) {
|
||||
if (i < 16) {
|
||||
const x = offset + i * 4;
|
||||
W[i] = buffer[x] << 24 | buffer[x + 1] << 16 | buffer[x + 2] << 8 | buffer[x + 3];
|
||||
}
|
||||
else {
|
||||
const x = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
|
||||
W[i] = (x << 1 | x >>> 31) >>> 0;
|
||||
}
|
||||
|
||||
let t = (a << 5 | a >>> 27) >>> 0 + e + W[i];
|
||||
if (i < 20) {
|
||||
t += ((b & c) | (~b & d)) + 0x5A827999;
|
||||
}
|
||||
else if (i < 40) {
|
||||
t += (b ^ c ^ d) + 0x6ED9EBA1;
|
||||
}
|
||||
else if (i < 60) {
|
||||
t += ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC;
|
||||
}
|
||||
else {
|
||||
t += (b ^ c ^ d) + 0xCA62C1D6;
|
||||
}
|
||||
|
||||
e = d, d = c, c = (b << 30 | b >>> 2) >>> 0, b = a, a = t;
|
||||
}
|
||||
|
||||
H[0] += a, H[1] += b, H[2] += c, H[3] += d, H[4] += e;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
bufferView.setUint32(i * 4, H[i]);
|
||||
}
|
||||
|
||||
let result = "";
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
result += (buffer[i] < 16 ? "0" : "") + buffer[i].toString(16);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
339
src/harness/mocks.ts
Normal file
339
src/harness/mocks.ts
Normal file
@ -0,0 +1,339 @@
|
||||
/// <reference path="./core.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
|
||||
// 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.
|
||||
|
||||
namespace mocks {
|
||||
const MAX_INT32 = 2 ** 31 - 1;
|
||||
|
||||
export interface Immediate {
|
||||
readonly kind: "immediate";
|
||||
readonly callback: (...args: any[]) => void;
|
||||
readonly args: ReadonlyArray<any>;
|
||||
}
|
||||
|
||||
export interface Timeout {
|
||||
readonly kind: "timeout";
|
||||
readonly callback: (...args: any[]) => void;
|
||||
readonly args: ReadonlyArray<any>;
|
||||
readonly due: number;
|
||||
}
|
||||
|
||||
export interface Interval {
|
||||
readonly kind: "interval";
|
||||
readonly callback: (...args: any[]) => void;
|
||||
readonly args: ReadonlyArray<any>;
|
||||
readonly due: number;
|
||||
readonly interval: number;
|
||||
}
|
||||
|
||||
export type Timer = Immediate | Timeout | Interval;
|
||||
|
||||
interface InternalInterval extends Interval {
|
||||
due: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatic control over timers.
|
||||
*/
|
||||
export class Timers {
|
||||
public static readonly MAX_DEPTH = MAX_INT32;
|
||||
private _immediates = new Set<Immediate>();
|
||||
private _timeouts = new Set<Timeout>();
|
||||
private _intervals = new Set<InternalInterval>();
|
||||
private _time: number;
|
||||
|
||||
constructor(startTime = Date.now()) {
|
||||
this._time = startTime;
|
||||
|
||||
// bind each timer method so that it can be detached from this instance.
|
||||
this.setImmediate = this.setImmediate.bind(this);
|
||||
this.clearImmedate = this.clearImmedate.bind(this);
|
||||
this.setTimeout = this.setTimeout.bind(this);
|
||||
this.clearTimeout = this.clearImmedate.bind(this);
|
||||
this.setInterval = this.setInterval.bind(this);
|
||||
this.clearInterval = this.clearInterval.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time.
|
||||
*/
|
||||
public get time(): number {
|
||||
return this._time;
|
||||
}
|
||||
|
||||
public getPending(kind: "immediate", ms?: number): Immediate[];
|
||||
public getPending(kind: "timeout", ms?: number): Timeout[];
|
||||
public getPending(kind: "interval", ms?: number): Interval[];
|
||||
public getPending(kind?: Timer["kind"], ms?: number): Timer[];
|
||||
public getPending(kind?: Timer["kind"], ms = 0) {
|
||||
if (ms < 0) throw new TypeError("Argument 'ms' out of range.");
|
||||
const pending: Timer[] = [];
|
||||
if (!kind || kind === "immediate") this.appendImmediates(pending);
|
||||
if (!kind || kind === "timeout") this.appendDueTimeouts(pending, this._time + ms);
|
||||
if (!kind || kind === "interval") this.appendDueIntervals(pending, this._time + ms, /*expand*/ false);
|
||||
return core.stableSort(pending, compareTimers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the current time and trigger callbacks, returning the number of callbacks triggered.
|
||||
* @param ms The number of milliseconds to advance.
|
||||
* @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing.
|
||||
* - Use `0` (default) to disable processing of nested `setImmediate` calls.
|
||||
* - Use `Timer.NO_MAX_DEPTH` to continue processing all nested `setImmediate` calls.
|
||||
*/
|
||||
public advance(ms: number, maxDepth = 0): number {
|
||||
if (ms < 0) throw new TypeError("Argument 'ms' out of range.");
|
||||
if (maxDepth < 0) throw new TypeError("Argument 'maxDepth' out of range.");
|
||||
this._time += ms;
|
||||
return this.executePending(maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute any pending timers, returning the number of timers triggered.
|
||||
* @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing.
|
||||
* - Use `0` (default) to disable processing of nested `setImmediate` calls.
|
||||
* - Use `Timer.NO_MAX_DEPTH` to continue processing all nested `setImmediate` calls.
|
||||
*/
|
||||
public executePending(maxDepth = 0): number {
|
||||
if (maxDepth < 0) throw new TypeError("Argument 'maxDepth' out of range.");
|
||||
const pending: Timer[] = [];
|
||||
this.appendImmediates(pending);
|
||||
this.appendDueTimeouts(pending, this._time);
|
||||
this.appendDueIntervals(pending, this._time, /*expand*/ true);
|
||||
let count = this.execute(pending);
|
||||
for (let depth = 0; depth < maxDepth && this._immediates.size > 0; depth++) {
|
||||
pending.length = 0;
|
||||
this.appendImmediates(pending);
|
||||
count += this.execute(pending);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
|
||||
const timer: Immediate = { kind: "immediate", callback, args };
|
||||
this._immediates.add(timer);
|
||||
return timer;
|
||||
}
|
||||
|
||||
public clearImmedate(timerId: any): void {
|
||||
this._immediates.delete(timerId);
|
||||
}
|
||||
|
||||
public setTimeout(callback: (...args: any[]) => void, timeout: number, ...args: any[]): any {
|
||||
if (timeout < 0) timeout = 0;
|
||||
const due = this._time + timeout;
|
||||
const timer: Timeout = { kind: "timeout", callback, args, due };
|
||||
this._timeouts.add(timer);
|
||||
return timer;
|
||||
}
|
||||
|
||||
public clearTimeout(timerId: any): void {
|
||||
this._timeouts.delete(timerId);
|
||||
}
|
||||
|
||||
public setInterval(callback: (...args: any[]) => void, interval: number, ...args: any[]): any {
|
||||
if (interval < 0) interval = 0;
|
||||
const due = this._time + interval;
|
||||
const timer: Interval = { kind: "interval", callback, args, due, interval };
|
||||
this._intervals.add(timer);
|
||||
return timer;
|
||||
}
|
||||
|
||||
public clearInterval(timerId: any): void {
|
||||
this._intervals.delete(timerId);
|
||||
}
|
||||
|
||||
private appendImmediates(pending: Timer[]) {
|
||||
this._immediates.forEach(timer => {
|
||||
pending.push(timer);
|
||||
});
|
||||
}
|
||||
|
||||
private appendDueTimeouts(timers: Timer[], dueTime: number) {
|
||||
this._timeouts.forEach(timer => {
|
||||
if (timer.due <= dueTime) {
|
||||
timers.push(timer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private appendDueIntervals(timers: Timer[], dueTime: number, expand: boolean) {
|
||||
this._intervals.forEach(timer => {
|
||||
while (timer.due <= dueTime) {
|
||||
timers.push(timer);
|
||||
if (!expand) break;
|
||||
timer.due += timer.interval;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private execute(timers: Timer[]) {
|
||||
for (const timer of core.stableSort(timers, compareTimers)) {
|
||||
switch (timer.kind) {
|
||||
case "immediate": this._immediates.delete(timer); break;
|
||||
case "timeout": this._timeouts.delete(timer); break;
|
||||
}
|
||||
const { callback, args } = timer;
|
||||
callback(...args);
|
||||
}
|
||||
return timers.length;
|
||||
}
|
||||
}
|
||||
|
||||
function compareTimers(a: Immediate | Timeout, b: Immediate | Timeout) {
|
||||
return (a.kind === "immediate" ? -1 : a.due) - (b.kind === "immediate" ? -1 : b.due);
|
||||
}
|
||||
|
||||
export class MockServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost {
|
||||
public readonly exitMessage = "System Exit";
|
||||
public readonly timers = new Timers();
|
||||
public readonly vfs: vfs.VirtualFileSystem;
|
||||
public exitCode: number;
|
||||
|
||||
private readonly _output: string[] = [];
|
||||
private readonly _executingFilePath: string;
|
||||
private readonly _getCanonicalFileName: (file: string) => string;
|
||||
|
||||
constructor(vfs: vfs.VirtualFileSystem, executingFilePath = "/.ts/tsc.js", newLine = "\n") {
|
||||
this.vfs = vfs;
|
||||
this.useCaseSensitiveFileNames = vfs.useCaseSensitiveFileNames;
|
||||
this.newLine = newLine;
|
||||
this._executingFilePath = executingFilePath;
|
||||
this._getCanonicalFileName = ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames);
|
||||
}
|
||||
|
||||
// #region DirectoryStructureHost members
|
||||
public readonly newLine: string;
|
||||
public readonly useCaseSensitiveFileNames: boolean;
|
||||
|
||||
public write(message: string) {
|
||||
this._output.push(message);
|
||||
}
|
||||
|
||||
public readFile(path: string) {
|
||||
return this.vfs.readFile(path);
|
||||
}
|
||||
|
||||
public writeFile(path: string, data: string): void {
|
||||
this.vfs.writeFile(path, data);
|
||||
}
|
||||
|
||||
public fileExists(path: string) {
|
||||
return this.vfs.fileExists(path);
|
||||
}
|
||||
|
||||
public directoryExists(path: string) {
|
||||
return this.vfs.directoryExists(path);
|
||||
}
|
||||
|
||||
public createDirectory(path: string): void {
|
||||
this.vfs.addDirectory(path);
|
||||
}
|
||||
|
||||
public getCurrentDirectory() {
|
||||
return this.vfs.currentDirectory;
|
||||
}
|
||||
|
||||
public getDirectories(path: string) {
|
||||
return this.vfs.getDirectoryNames(path);
|
||||
}
|
||||
|
||||
public readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
|
||||
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.vfs.currentDirectory, depth, path => {
|
||||
return this.vfs.getAccessibleFileSystemEntries(path);
|
||||
});
|
||||
}
|
||||
|
||||
public exit(exitCode?: number) {
|
||||
this.exitCode = exitCode;
|
||||
throw new Error("System exit");
|
||||
}
|
||||
// #endregion DirectoryStructureHost members
|
||||
|
||||
// #region System members
|
||||
public readonly args: string[] = [];
|
||||
|
||||
public getFileSize(path: string) {
|
||||
const stats = this.vfs.getStats(path);
|
||||
return stats && stats.isFile() ? stats.size : 0;
|
||||
}
|
||||
|
||||
public watchFile(path: string, cb: ts.FileWatcherCallback) {
|
||||
return this.vfs.watchFile(path, (path, change) => {
|
||||
cb(path, change === "added" ? ts.FileWatcherEventKind.Created :
|
||||
change === "removed" ? ts.FileWatcherEventKind.Deleted :
|
||||
ts.FileWatcherEventKind.Changed);
|
||||
});
|
||||
}
|
||||
|
||||
public watchDirectory(path: string, cb: ts.DirectoryWatcherCallback, recursive: boolean): ts.FileWatcher {
|
||||
return this.vfs.watchDirectory(path, cb, recursive);
|
||||
}
|
||||
|
||||
public resolvePath(path: string) {
|
||||
return vpath.resolve(this.vfs.currentDirectory, path);
|
||||
}
|
||||
|
||||
public getExecutingFilePath() {
|
||||
return this._executingFilePath;
|
||||
}
|
||||
|
||||
public getModifiedTime(path: string) {
|
||||
const stats = this.vfs.getStats(path);
|
||||
return stats && stats.mtime;
|
||||
}
|
||||
|
||||
public createHash(data: string): string {
|
||||
return core.sha1(data);
|
||||
}
|
||||
|
||||
public realpath(path: string) {
|
||||
const entry = this.vfs.getRealEntry(this.vfs.getEntry(path));
|
||||
return entry && entry.path;
|
||||
}
|
||||
|
||||
public getEnvironmentVariable(_name: string): string | undefined {
|
||||
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);
|
||||
}
|
||||
|
||||
public clearTimeout(timeoutId: any): void {
|
||||
this.timers.clearTimeout(timeoutId);
|
||||
}
|
||||
// #endregion System members
|
||||
|
||||
// #region FormatDiagnosticsHost members
|
||||
public getNewLine() {
|
||||
return this.newLine;
|
||||
}
|
||||
|
||||
public getCanonicalFileName(fileName: string) {
|
||||
return this._getCanonicalFileName(fileName);
|
||||
}
|
||||
// #endregion FormatDiagnosticsHost members
|
||||
|
||||
// #region ServerHost members
|
||||
public setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
|
||||
return this.timers.setImmediate(callback, args);
|
||||
}
|
||||
|
||||
public clearImmediate(timeoutId: any): void {
|
||||
this.timers.clearImmedate(timeoutId);
|
||||
}
|
||||
// #endregion ServerHost members
|
||||
|
||||
public getOutput(): ReadonlyArray<string> {
|
||||
return this._output;
|
||||
}
|
||||
|
||||
public clearOutput() {
|
||||
this._output.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,7 +73,7 @@
|
||||
"../services/codefixes/disableJsDiagnostics.ts",
|
||||
|
||||
"harness.ts",
|
||||
|
||||
|
||||
"core.ts",
|
||||
"utils.ts",
|
||||
"events.ts",
|
||||
@ -81,7 +81,8 @@
|
||||
"vpath.ts",
|
||||
"vfs.ts",
|
||||
"compiler.ts",
|
||||
|
||||
"mocks.ts",
|
||||
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"sourceMapRecorder.ts",
|
||||
"harnessLanguageService.ts",
|
||||
|
||||
@ -871,9 +871,6 @@ namespace ts {
|
||||
});
|
||||
});
|
||||
|
||||
import TestSystem = ts.TestFSWithWatch.TestServerHost;
|
||||
type FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
|
||||
import createTestSystem = ts.TestFSWithWatch.createWatchedSystem;
|
||||
import libFile = ts.TestFSWithWatch.libFile;
|
||||
|
||||
describe("isProgramUptoDate should return true when there is no change in compiler options and", () => {
|
||||
@ -897,7 +894,7 @@ namespace ts {
|
||||
return JSON.parse(JSON.stringify(filesOrOptions));
|
||||
}
|
||||
|
||||
function createWatchingSystemHost(host: TestSystem) {
|
||||
function createWatchingSystemHost(host: ts.System) {
|
||||
return ts.createWatchingSystemHost(/*pretty*/ undefined, host);
|
||||
}
|
||||
|
||||
@ -917,48 +914,29 @@ namespace ts {
|
||||
verifyProgramIsUptoDate(program, fileNames, options);
|
||||
}
|
||||
|
||||
function verifyProgram(files: FileOrFolder[], rootFiles: string[], options: CompilerOptions, configFile: string) {
|
||||
const watchingSystemHost = createWatchingSystemHost(createTestSystem(files));
|
||||
function verifyProgram(vfs: vfs.VirtualFileSystem, rootFiles: string[], options: CompilerOptions, configFile: string) {
|
||||
const watchingSystemHost = createWatchingSystemHost(new mocks.MockServerHost(vfs));
|
||||
verifyProgramWithoutConfigFile(watchingSystemHost, rootFiles, options);
|
||||
verifyProgramWithConfigFile(watchingSystemHost, configFile);
|
||||
}
|
||||
|
||||
it("has empty options", () => {
|
||||
const file1: FileOrFolder = {
|
||||
path: "/a/b/file1.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const file2: FileOrFolder = {
|
||||
path: "/a/b/file2.ts",
|
||||
content: "let y = 1"
|
||||
};
|
||||
const configFile: FileOrFolder = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path);
|
||||
const fs = new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true);
|
||||
const file1 = fs.addFile("/a/b/file1.ts", "let x = 1");
|
||||
const file2 = fs.addFile("/a/b/file2.ts", "let y = 1");
|
||||
const configFile = fs.addFile("/a/b/tsconfig.json", "{}");
|
||||
fs.addFile(libFile.path, libFile.content);
|
||||
verifyProgram(fs, [file1.path, file2.path], {}, configFile.path);
|
||||
});
|
||||
|
||||
it("has lib specified in the options", () => {
|
||||
const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] };
|
||||
const app: FileOrFolder = {
|
||||
path: "/src/app.ts",
|
||||
content: "var x: Promise<string>;"
|
||||
};
|
||||
const configFile: FileOrFolder = {
|
||||
path: "/src/tsconfig.json",
|
||||
content: JSON.stringify({ compilerOptions })
|
||||
};
|
||||
const es5Lib: FileOrFolder = {
|
||||
path: "/compiler/lib.es5.d.ts",
|
||||
content: "declare const eval: any"
|
||||
};
|
||||
const es2015Promise: FileOrFolder = {
|
||||
path: "/compiler/lib.es2015.promise.d.ts",
|
||||
content: "declare class Promise<T> {}"
|
||||
};
|
||||
|
||||
verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path);
|
||||
const fs = new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true);
|
||||
const app = fs.addFile("/src/app.ts", "var x: Promise<string>;");
|
||||
const configFile = fs.addFile("/src/tsconfig.json", JSON.stringify({ compilerOptions }));
|
||||
fs.addFile("/compiler/lib.es5.d.ts", "declare const eval: any;");
|
||||
fs.addFile("/compiler/lib.es2015.promise.d.ts", "declare class Promise<T> {}");
|
||||
verifyProgram(fs, [app.path], compilerOptions, configFile.path);
|
||||
});
|
||||
|
||||
it("has paths specified in the options", () => {
|
||||
@ -972,31 +950,23 @@ namespace ts {
|
||||
]
|
||||
}
|
||||
};
|
||||
const app: FileOrFolder = {
|
||||
path: "/src/packages/framework/app.ts",
|
||||
content: 'import classc from "module1/lib/file1";\
|
||||
import classD from "module3/file3";\
|
||||
let x = new classc();\
|
||||
let y = new classD();'
|
||||
};
|
||||
const module1: FileOrFolder = {
|
||||
path: "/src/packages/mail/data/module1/lib/file1.ts",
|
||||
content: 'import classc from "module2/file2";export default classc;',
|
||||
};
|
||||
const module2: FileOrFolder = {
|
||||
path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
|
||||
content: 'class classc { method2() { return "hello"; } }\nexport default classc',
|
||||
};
|
||||
const module3: FileOrFolder = {
|
||||
path: "/src/packages/styles/module3/file3.ts",
|
||||
content: "class classD { method() { return 10; } }\nexport default classD;"
|
||||
};
|
||||
const configFile: FileOrFolder = {
|
||||
path: "/src/tsconfig.json",
|
||||
content: JSON.stringify({ compilerOptions })
|
||||
};
|
||||
|
||||
verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path);
|
||||
const fs = new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true);
|
||||
const app = fs.addFile("/src/packages/framework/app.ts",
|
||||
`import classC from "module1/lib/file1";\n` +
|
||||
`import classD from "module3/file3";\n` +
|
||||
`let x = new classC();\n` +
|
||||
`let y = new classD();`);
|
||||
fs.addFile("/src/packages/mail/data/module1/lib/file1.ts",
|
||||
`import classC from "module2/file2";\n` +
|
||||
`export default classC;`);
|
||||
fs.addFile("/src/packages/mail/data/module1/lib/module2/file2.ts",
|
||||
`class classC { method2() { return "hello"; } }\n` +
|
||||
`export default classC;`);
|
||||
fs.addFile("/src/packages/styles/module3/file3.ts",
|
||||
`class classD { method() { return 10; } }\n` +
|
||||
`export default classD;`);
|
||||
const configFile = fs.addFile("/src/tsconfig.json", JSON.stringify({ compilerOptions }));
|
||||
verifyProgram(fs, [app.path], compilerOptions, configFile.path);
|
||||
});
|
||||
|
||||
it("has include paths specified in tsconfig file", () => {
|
||||
@ -1010,31 +980,24 @@ namespace ts {
|
||||
]
|
||||
}
|
||||
};
|
||||
const app: FileOrFolder = {
|
||||
path: "/src/packages/framework/app.ts",
|
||||
content: 'import classc from "module1/lib/file1";\
|
||||
import classD from "module3/file3";\
|
||||
let x = new classc();\
|
||||
let y = new classD();'
|
||||
};
|
||||
const module1: FileOrFolder = {
|
||||
path: "/src/packages/mail/data/module1/lib/file1.ts",
|
||||
content: 'import classc from "module2/file2";export default classc;',
|
||||
};
|
||||
const module2: FileOrFolder = {
|
||||
path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
|
||||
content: 'class classc { method2() { return "hello"; } }\nexport default classc',
|
||||
};
|
||||
const module3: FileOrFolder = {
|
||||
path: "/src/packages/styles/module3/file3.ts",
|
||||
content: "class classD { method() { return 10; } }\nexport default classD;"
|
||||
};
|
||||
const configFile: FileOrFolder = {
|
||||
path: "/src/tsconfig.json",
|
||||
content: JSON.stringify({ compilerOptions, include: ["packages/**/ *.ts"] })
|
||||
};
|
||||
|
||||
const watchingSystemHost = createWatchingSystemHost(createTestSystem([app, module1, module2, module3, libFile, configFile]));
|
||||
const fs = new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true);
|
||||
fs.addFile("/src/packages/framework/app.ts",
|
||||
`import classC from "module1/lib/file1";\n` +
|
||||
`import classD from "module3/file3";\n` +
|
||||
`let x = new classC();\n` +
|
||||
`let y = new classD();`);
|
||||
fs.addFile("/src/packages/mail/data/module1/lib/file1.ts",
|
||||
`import classC from "module2/file2";\n` +
|
||||
`export default classC;`);
|
||||
fs.addFile("/src/packages/mail/data/module1/lib/module2/file2.ts",
|
||||
`class classC { method2() { return "hello"; } }\n` +
|
||||
`export default classC;`);
|
||||
fs.addFile("/src/packages/styles/module3/file3.ts",
|
||||
`class classD { method() { return 10; } }\n` +
|
||||
`export default classD;`);
|
||||
const configFile = fs.addFile("/src/tsconfig.json",
|
||||
JSON.stringify({ compilerOptions, include: ["packages/**/ *.ts"] }));
|
||||
const watchingSystemHost = createWatchingSystemHost(new mocks.MockServerHost(fs));
|
||||
verifyProgramWithConfigFile(watchingSystemHost, configFile.path);
|
||||
});
|
||||
});
|
||||
|
||||
@ -22,7 +22,7 @@ namespace ts.tscWatch {
|
||||
checkFileNames(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
|
||||
}
|
||||
|
||||
function createWatchingSystemHost(system: WatchedSystem) {
|
||||
function createWatchingSystemHost(system: ts.System) {
|
||||
return ts.createWatchingSystemHost(/*pretty*/ undefined, system);
|
||||
}
|
||||
|
||||
@ -30,22 +30,22 @@ namespace ts.tscWatch {
|
||||
return ts.parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic);
|
||||
}
|
||||
|
||||
function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem) {
|
||||
function createWatchModeWithConfigFile(configFilePath: string, host: ts.System) {
|
||||
const watchingSystemHost = createWatchingSystemHost(host);
|
||||
const configFileResult = parseConfigFile(configFilePath, watchingSystemHost);
|
||||
return ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost);
|
||||
}
|
||||
|
||||
function createWatchModeWithoutConfigFile(fileNames: string[], host: WatchedSystem, options: CompilerOptions = {}) {
|
||||
function createWatchModeWithoutConfigFile(fileNames: string[], host: ts.System, options: CompilerOptions = {}) {
|
||||
const watchingSystemHost = createWatchingSystemHost(host);
|
||||
return ts.createWatchModeWithoutConfigFile(fileNames, options, watchingSystemHost);
|
||||
}
|
||||
|
||||
function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: WatchedSystem) {
|
||||
function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: ts.System) {
|
||||
return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`;
|
||||
}
|
||||
|
||||
function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) {
|
||||
function getEmittedLineForSingleFileOutput(filename: string, host: ts.System) {
|
||||
return `TSFILE: ${filename}${host.newLine}`;
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,11 @@
|
||||
// support the eventual conversion of harness into a modular system.
|
||||
|
||||
namespace vfs {
|
||||
const S_IFMT = 0xf000;
|
||||
const S_IFLNK = 0xa000;
|
||||
const S_IFREG = 0x8000;
|
||||
const S_IFDIR = 0x4000;
|
||||
|
||||
export interface PathMappings {
|
||||
[path: string]: string;
|
||||
}
|
||||
@ -348,7 +353,7 @@ namespace vfs {
|
||||
*/
|
||||
public readFile(path: string): string | undefined {
|
||||
const file = this.getFile(vpath.resolve(this.currentDirectory, path));
|
||||
return file && file.content;
|
||||
return file && file.readContent();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -357,9 +362,7 @@ namespace vfs {
|
||||
public writeFile(path: string, content: string): void {
|
||||
path = vpath.resolve(this.currentDirectory, path);
|
||||
const file = this.getFile(path) || this.addFile(path);
|
||||
if (file) {
|
||||
file.content = content;
|
||||
}
|
||||
if (file) file.writeContent(content);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -376,6 +379,35 @@ namespace vfs {
|
||||
return this.getEntry(path) instanceof VirtualFile;
|
||||
}
|
||||
|
||||
public rename(oldpath: string, newpath: string) {
|
||||
oldpath = vpath.resolve(this.currentDirectory, oldpath);
|
||||
newpath = vpath.resolve(this.currentDirectory, newpath);
|
||||
return this.root.replaceEntry(newpath, this.getEntry(oldpath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file stats
|
||||
*/
|
||||
public getStats(path: string, options?: { noFollowSymlinks?: boolean }) {
|
||||
let entry = this.getEntry(path);
|
||||
if (entry && !(options && options.noFollowSymlinks)) {
|
||||
entry = this.getRealEntry(entry);
|
||||
}
|
||||
return entry && entry.getStats();
|
||||
}
|
||||
|
||||
public setStats(path: string, atime: number | Date, mtime: number | Date, options?: { noFollowSymlinks?: boolean }) {
|
||||
let entry = this.getEntry(path);
|
||||
if (entry && !(options && options.noFollowSymlinks)) {
|
||||
entry = this.getRealEntry(entry);
|
||||
}
|
||||
if (entry && !entry.isReadOnly) {
|
||||
entry.setStats(atime, mtime);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If an entry is a symbolic link, gets the resolved target of the link. Otherwise, returns the entry.
|
||||
*/
|
||||
@ -425,6 +457,39 @@ namespace vfs {
|
||||
return this.root.getDirectory(vpath.resolve(this.currentDirectory, path), options);
|
||||
}
|
||||
|
||||
public getEntries(path: string, options: { recursive?: boolean, pattern?: RegExp, kind: "file" }): VirtualFile[];
|
||||
public getEntries(path: string, options: { recursive?: boolean, pattern?: RegExp, kind: "directory" }): VirtualDirectory[];
|
||||
public getEntries(path: string, options?: { recursive?: boolean, pattern?: RegExp, kind?: "file" | "directory" }): VirtualEntry[];
|
||||
public getEntries(path: string, options?: { recursive?: boolean, pattern?: RegExp, kind?: "file" | "directory" }) {
|
||||
const dir = this.root.getDirectory(vpath.resolve(this.currentDirectory, path));
|
||||
return dir ? dir.getEntries(options) : [];
|
||||
}
|
||||
|
||||
public getDirectories(path: string, options?: { recursive?: boolean, pattern?: RegExp }) {
|
||||
const dir = this.root.getDirectory(vpath.resolve(this.currentDirectory, path));
|
||||
return dir ? dir.getDirectories(options) : [];
|
||||
}
|
||||
|
||||
public getFiles(path: string, options?: { recursive?: boolean, pattern?: RegExp }) {
|
||||
const dir = this.root.getDirectory(vpath.resolve(this.currentDirectory, path));
|
||||
return dir ? dir.getFiles(options) : [];
|
||||
}
|
||||
|
||||
public getEntryNames(path: string, options?: { recursive?: boolean, qualified?: boolean, pattern?: RegExp, kind?: "file" | "directory" }) {
|
||||
const dir = this.root.getDirectory(vpath.resolve(this.currentDirectory, path));
|
||||
return dir ? dir.getEntryNames(options) : [];
|
||||
}
|
||||
|
||||
public getDirectoryNames(path: string, options?: { recursive?: boolean, qualified?: boolean, pattern?: RegExp }) {
|
||||
const dir = this.root.getDirectory(vpath.resolve(this.currentDirectory, path));
|
||||
return dir ? dir.getDirectoryNames(options) : [];
|
||||
}
|
||||
|
||||
public getFileNames(path: string, options?: { recursive?: boolean, qualified?: boolean, pattern?: RegExp }) {
|
||||
const dir = this.root.getDirectory(vpath.resolve(this.currentDirectory, path));
|
||||
return dir ? dir.getFileNames(options) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the accessible file system entries from a path relative to the current directory.
|
||||
*/
|
||||
@ -587,17 +652,29 @@ namespace vfs {
|
||||
}
|
||||
|
||||
export abstract class VirtualFileSystemEntry extends VirtualFileSystemObject {
|
||||
private static _nextId = 1;
|
||||
private _path: string;
|
||||
private _name: string;
|
||||
private _parent: VirtualDirectory | undefined;
|
||||
private _metadata: core.Metadata;
|
||||
private _atimeMS: number = Date.now();
|
||||
private _mtimeMS: number = Date.now();
|
||||
private _ctimeMS: number = Date.now();
|
||||
private _birthtimeMS: number = Date.now();
|
||||
|
||||
public readonly id = VirtualFileSystemEntry._nextId++;
|
||||
|
||||
constructor(parent: VirtualDirectory | undefined, name: string) {
|
||||
super();
|
||||
this._parent = parent;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this entry.
|
||||
*/
|
||||
public readonly name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
super();
|
||||
this.name = name;
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -608,10 +685,19 @@ namespace vfs {
|
||||
return this.parent.fileSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root directory for this entry.
|
||||
*/
|
||||
public get root(): VirtualDirectory | undefined {
|
||||
return this.parent.root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent directory for this entry.
|
||||
*/
|
||||
public abstract get parent(): VirtualDirectory | undefined;
|
||||
public get parent(): VirtualDirectory | undefined {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entry that this entry shadows.
|
||||
@ -665,6 +751,27 @@ namespace vfs {
|
||||
*/
|
||||
public abstract shadow(parent: VirtualDirectory): VirtualEntry;
|
||||
|
||||
public abstract getStats(): VirtualStats;
|
||||
|
||||
public setStats(atime: number | Date, mtime: number | Date) {
|
||||
this.writePreamble();
|
||||
this._atimeMS = typeof atime === "object" ? atime.getTime() :
|
||||
atime < 0 ? Date.now() :
|
||||
atime;
|
||||
this._mtimeMS = typeof mtime === "object" ? mtime.getTime() :
|
||||
mtime < 0 ? Date.now() :
|
||||
mtime;
|
||||
this._ctimeMS = Date.now();
|
||||
}
|
||||
|
||||
protected static _setNameUnsafe(entry: VirtualFileSystemEntry, name: string) {
|
||||
entry._name = name;
|
||||
}
|
||||
|
||||
protected static _setParentUnsafe(entry: VirtualFileSystemEntry, parent: VirtualDirectory) {
|
||||
entry._parent = parent;
|
||||
}
|
||||
|
||||
protected shadowPreamble(parent: VirtualDirectory): void {
|
||||
this.checkShadowParent(parent);
|
||||
this.checkShadowFileSystem(parent.fileSystem);
|
||||
@ -681,6 +788,30 @@ namespace vfs {
|
||||
fileSystem = fileSystem.shadowRoot;
|
||||
}
|
||||
}
|
||||
|
||||
protected getStatsCore(mode: number, size: number) {
|
||||
return new VirtualStats(
|
||||
this.root.id,
|
||||
this.id,
|
||||
mode,
|
||||
size,
|
||||
this._atimeMS,
|
||||
this._mtimeMS,
|
||||
this._ctimeMS,
|
||||
this._birthtimeMS);
|
||||
}
|
||||
|
||||
protected updateAccessTime() {
|
||||
if (!this.isReadOnly) {
|
||||
this._atimeMS = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
protected updateModificationTime() {
|
||||
if (!this.isReadOnly) {
|
||||
this._mtimeMS = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface VirtualDirectory {
|
||||
@ -721,15 +852,13 @@ namespace vfs {
|
||||
|
||||
export class VirtualDirectory extends VirtualFileSystemEntry {
|
||||
protected _shadowRoot: VirtualDirectory | undefined;
|
||||
private _parent: VirtualDirectory;
|
||||
private _entries: core.KeyedCollection<string, VirtualEntry> | undefined;
|
||||
private _resolver: FileSystemResolver | undefined;
|
||||
private _onChildFileSystemChange: (path: string, change: FileSystemChange) => void;
|
||||
|
||||
constructor(parent: VirtualDirectory | undefined, name: string, resolver?: FileSystemResolver) {
|
||||
super(name);
|
||||
super(parent, name);
|
||||
if (parent === undefined && !(this instanceof VirtualRoot)) throw new TypeError();
|
||||
this._parent = parent;
|
||||
this._entries = undefined;
|
||||
this._resolver = resolver;
|
||||
this._shadowRoot = undefined;
|
||||
@ -737,10 +866,10 @@ namespace vfs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the container for this entry.
|
||||
* Gets the root directory for this entry.
|
||||
*/
|
||||
public get parent(): VirtualDirectory | undefined {
|
||||
return this._parent;
|
||||
public get root(): VirtualDirectory | undefined {
|
||||
return this.parent instanceof VirtualRoot ? this : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -911,7 +1040,6 @@ namespace vfs {
|
||||
* Adds a directory (and all intermediate directories) for a path relative to this directory.
|
||||
*/
|
||||
public addDirectory(path: string, resolver?: FileSystemResolver): VirtualDirectory | undefined {
|
||||
this.writePreamble();
|
||||
const components = this.parsePath(path);
|
||||
const directory = this.walkContainers(components, /*create*/ true);
|
||||
return directory && directory.addOwnDirectory(components[components.length - 1], resolver);
|
||||
@ -921,7 +1049,6 @@ namespace vfs {
|
||||
* Adds a file (and all intermediate directories) for a path relative to this directory.
|
||||
*/
|
||||
public addFile(path: string, content?: FileSystemResolver | ContentResolver | string, options?: { overwrite?: boolean }): VirtualFile | undefined {
|
||||
this.writePreamble();
|
||||
const components = this.parsePath(path);
|
||||
const directory = this.walkContainers(components, /*create*/ true);
|
||||
return directory && directory.addOwnFile(components[components.length - 1], content, options);
|
||||
@ -940,7 +1067,6 @@ namespace vfs {
|
||||
*/
|
||||
public addSymlink(path: string, target: string | VirtualEntry): VirtualSymlink | undefined;
|
||||
public addSymlink(path: string, target: string | VirtualEntry): VirtualSymlink | undefined {
|
||||
this.writePreamble();
|
||||
const targetEntry = typeof target === "string" ? this.fileSystem.getEntry(vpath.resolve(this.path, target)) : target;
|
||||
if (targetEntry === undefined) return undefined;
|
||||
const components = this.parsePath(path);
|
||||
@ -952,7 +1078,6 @@ namespace vfs {
|
||||
* Removes a directory (and all of its contents) at a path relative to this directory.
|
||||
*/
|
||||
public removeDirectory(path: string): boolean {
|
||||
this.writePreamble();
|
||||
const components = this.parsePath(path);
|
||||
const directory = this.walkContainers(components, /*create*/ false);
|
||||
return directory ? directory.removeOwnDirectory(components[components.length - 1]) : false;
|
||||
@ -962,13 +1087,17 @@ namespace vfs {
|
||||
* Removes a file at a path relative to this directory.
|
||||
*/
|
||||
public removeFile(path: string): boolean {
|
||||
this.writePreamble();
|
||||
this.writePreamble();
|
||||
const components = this.parsePath(path);
|
||||
const directory = this.walkContainers(components, /*create*/ false);
|
||||
return directory ? directory.removeOwnFile(components[components.length - 1]) : false;
|
||||
}
|
||||
|
||||
public replaceEntry(path: string, entry: VirtualEntry) {
|
||||
const components = this.parsePath(path);
|
||||
const directory = this.walkContainers(components, /*create*/ false);
|
||||
return directory ? directory.replaceOwnEntry(components[components.length - 1], entry) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shadow copy of this directory. Changes made to the shadow do not affect
|
||||
* this directory.
|
||||
@ -980,6 +1109,10 @@ namespace vfs {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
public getStats() {
|
||||
return super.getStatsCore(S_IFDIR, 0);
|
||||
}
|
||||
|
||||
protected makeReadOnlyCore(): void {
|
||||
if (this._entries) {
|
||||
this._entries.forEach(entry => entry.makeReadOnly());
|
||||
@ -1026,69 +1159,97 @@ namespace vfs {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.writePreamble();
|
||||
const entry = new VirtualDirectory(this, name, resolver);
|
||||
this.getOwnEntries().set(entry.name, entry);
|
||||
this.emit("childAdded", entry);
|
||||
entry.emit("fileSystemChange", entry.path, "added");
|
||||
entry.addListener("fileSystemChange", this._onChildFileSystemChange);
|
||||
this.addOwnEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
protected addOwnFile(name: string, content?: FileSystemResolver | ContentResolver | string, options: { overwrite?: boolean } = {}): VirtualFile | undefined {
|
||||
const existing = this.getOwnEntry(name);
|
||||
if (existing) {
|
||||
if (!options.overwrite || !(existing instanceof VirtualFile)) {
|
||||
return undefined;
|
||||
}
|
||||
if (existing && (!options.overwrite || !(existing instanceof VirtualFile))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.writePreamble();
|
||||
if (existing) {
|
||||
// Remove the existing entry
|
||||
this.getOwnEntries().delete(name);
|
||||
}
|
||||
|
||||
const entry = new VirtualFile(this, name, content);
|
||||
this.getOwnEntries().set(entry.name, entry);
|
||||
this.emit("childAdded", entry);
|
||||
entry.emit("fileSystemChange", entry.path, "added");
|
||||
entry.addListener("fileSystemChange", this._onChildFileSystemChange);
|
||||
this.addOwnEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
protected addOwnSymlink(name: string, target: VirtualEntry): VirtualSymlink | undefined {
|
||||
if (this.getOwnEntry(name)) return undefined;
|
||||
this.writePreamble();
|
||||
const entry = target instanceof VirtualFile ? new VirtualFileSymlink(this, name, target.path) : new VirtualDirectorySymlink(this, name, target.path);
|
||||
this.getOwnEntries().set(entry.name, entry);
|
||||
this.emit("childAdded", entry);
|
||||
entry.emit("fileSystemChange", entry.path, "added");
|
||||
entry.addListener("fileSystemChange", this._onChildFileSystemChange);
|
||||
this.addOwnEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
protected removeOwnDirectory(name: string) {
|
||||
const entries = this.getOwnEntries();
|
||||
const entry = entries.get(name);
|
||||
const entry = this.getOwnEntries().get(name);
|
||||
if (entry instanceof VirtualDirectory) {
|
||||
entries.delete(name);
|
||||
this.emit("childRemoved", entry);
|
||||
this.emit("fileSystemChange", entry.path, "removed");
|
||||
entry.removeListener("fileSystemChange", this._onChildFileSystemChange);
|
||||
this.writePreamble();
|
||||
this.removeOwnEntry(entry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected removeOwnFile(name: string) {
|
||||
const entries = this.getOwnEntries();
|
||||
const entry = entries.get(name);
|
||||
const entry = this.getOwnEntries().get(name);
|
||||
if (entry instanceof VirtualFile) {
|
||||
entries.delete(name);
|
||||
this.emit("childRemoved", entry);
|
||||
this.emit("fileSystemChange", entry.path, "removed");
|
||||
entry.removeListener("fileSystemChange", this._onChildFileSystemChange);
|
||||
this.writePreamble();
|
||||
this.removeOwnEntry(entry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected replaceOwnEntry(name: string, entry: VirtualEntry) {
|
||||
const existing = this.getOwnEntry(name);
|
||||
// cannot replace yourself
|
||||
// cannot move a file or directory into a read-only container.
|
||||
if (entry === existing ||
|
||||
this.isReadOnly) {
|
||||
return false;
|
||||
}
|
||||
else if (entry instanceof VirtualDirectory) {
|
||||
// cannot move a directory on top of a file.
|
||||
// cannot move a directory on top of a non-empty directory.
|
||||
// cannot move a directory if its parent is read-only
|
||||
// cannot move a directory underneath itself.
|
||||
if (existing instanceof VirtualFile ||
|
||||
existing instanceof VirtualDirectory && existing.getEntries().length > 0 ||
|
||||
entry.parent.isReadOnly ||
|
||||
vpath.beneath(entry.path, vpath.combine(this.path, name), !this.fileSystem.useCaseSensitiveFileNames)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (entry instanceof VirtualFile) {
|
||||
// cannot move a file on top of a directory.
|
||||
// cannot move a file if its parent is read-only.
|
||||
if (existing instanceof VirtualDirectory ||
|
||||
entry.parent.isReadOnly) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// delete any existing file or directory
|
||||
if (existing) this.removeOwnEntry(existing);
|
||||
|
||||
// move and rename the entry
|
||||
entry.parent.removeOwnEntry(entry);
|
||||
VirtualFileSystemEntry._setNameUnsafe(entry, name);
|
||||
VirtualFileSystemEntry._setParentUnsafe(entry, this);
|
||||
this.addOwnEntry(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
private parsePath(path: string) {
|
||||
return vpath.parse(vpath.normalize(path));
|
||||
}
|
||||
@ -1126,6 +1287,25 @@ namespace vfs {
|
||||
return this.getOwnDirectory(name) || this.addOwnDirectory(name);
|
||||
}
|
||||
|
||||
private addOwnEntry(entry: VirtualEntry) {
|
||||
this.getOwnEntries().set(entry.name, entry);
|
||||
this.updateAccessTime();
|
||||
this.updateModificationTime();
|
||||
this.emit("childAdded", entry);
|
||||
entry.emit("fileSystemChange", entry.path, "added");
|
||||
entry.addListener("fileSystemChange", this._onChildFileSystemChange);
|
||||
}
|
||||
|
||||
private removeOwnEntry(entry: VirtualEntry) {
|
||||
const entries = this.getOwnEntries();
|
||||
entries.delete(entry.name);
|
||||
this.updateAccessTime();
|
||||
this.updateModificationTime();
|
||||
this.emit("childRemoved", entry);
|
||||
this.emit("fileSystemChange", entry.path, "removed");
|
||||
entry.removeListener("fileSystemChange", this._onChildFileSystemChange);
|
||||
}
|
||||
|
||||
private onChildFileSystemChange(path: string, change: FileSystemChange) {
|
||||
this.emit("fileSystemChange", path, change);
|
||||
}
|
||||
@ -1195,6 +1375,21 @@ namespace vfs {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
public readTargetPath() {
|
||||
const targetPath = this.targetPath;
|
||||
if (targetPath) this.updateAccessTime();
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
public writeTargetPath(targetPath: string) {
|
||||
this.targetPath = targetPath;
|
||||
if (targetPath) this.updateModificationTime();
|
||||
}
|
||||
|
||||
public getStats() {
|
||||
return super.getStatsCore(S_IFLNK, this.targetPath.length);
|
||||
}
|
||||
|
||||
protected addOwnDirectory(name: string, resolver?: FileSystemResolver): VirtualDirectory | undefined {
|
||||
const target = this.target;
|
||||
const child = target && target.addDirectory(name, resolver);
|
||||
@ -1338,6 +1533,10 @@ namespace vfs {
|
||||
return this._fileSystem;
|
||||
}
|
||||
|
||||
public get root(): VirtualDirectory | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return "";
|
||||
}
|
||||
@ -1396,27 +1595,18 @@ namespace vfs {
|
||||
|
||||
export class VirtualFile extends VirtualFileSystemEntry {
|
||||
protected _shadowRoot: VirtualFile | undefined;
|
||||
private _parent: VirtualDirectory;
|
||||
private _content: string | undefined;
|
||||
private _contentWasSet: boolean;
|
||||
private _resolver: FileSystemResolver | ContentResolver | undefined;
|
||||
|
||||
constructor(parent: VirtualDirectory, name: string, content?: FileSystemResolver | ContentResolver | string) {
|
||||
super(name);
|
||||
this._parent = parent;
|
||||
super(parent, name);
|
||||
this._content = typeof content === "string" ? content : undefined;
|
||||
this._resolver = typeof content !== "string" ? content : undefined;
|
||||
this._shadowRoot = undefined;
|
||||
this._contentWasSet = this._content !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent directory for this entry.
|
||||
*/
|
||||
public get parent(): VirtualDirectory {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entry that this entry shadows.
|
||||
*/
|
||||
@ -1470,6 +1660,27 @@ namespace vfs {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content and updates the file's access time.
|
||||
*/
|
||||
public readContent() {
|
||||
const content = this.content;
|
||||
if (content) this.updateAccessTime();
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the provided content and updates the file's modification time.
|
||||
*/
|
||||
public writeContent(content: string) {
|
||||
this.content = content;
|
||||
if (content) this.updateModificationTime();
|
||||
}
|
||||
|
||||
public getStats() {
|
||||
return super.getStatsCore(S_IFREG, this.content ? this.content.length : 0);
|
||||
}
|
||||
|
||||
protected makeReadOnlyCore(): void { /*ignored*/ }
|
||||
}
|
||||
|
||||
@ -1548,6 +1759,29 @@ namespace vfs {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
public readContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public writeContent(content: string) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public readTargetPath() {
|
||||
const targetPath = this.targetPath;
|
||||
if (targetPath) this.updateAccessTime();
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
public writeTargetPath(targetPath: string) {
|
||||
this.targetPath = targetPath;
|
||||
if (targetPath) this.updateModificationTime();
|
||||
}
|
||||
|
||||
public getStats() {
|
||||
return super.getStatsCore(S_IFLNK, this.targetPath.length);
|
||||
}
|
||||
|
||||
private resolveTarget() {
|
||||
if (!this._target) {
|
||||
const entry = findTarget(this.fileSystem, this.targetPath);
|
||||
@ -1598,6 +1832,48 @@ namespace vfs {
|
||||
protected onTargetFileSystemChange() { /* views do not propagate file system events */ }
|
||||
}
|
||||
|
||||
export class VirtualStats {
|
||||
public readonly dev: number;
|
||||
public readonly ino: number;
|
||||
public readonly mode: number;
|
||||
public readonly size: number;
|
||||
public readonly atimeMS: number;
|
||||
public readonly mtimeMS: number;
|
||||
public readonly ctimeMS: number;
|
||||
public readonly birthtimeMS: number;
|
||||
public readonly atime: Date;
|
||||
public readonly mtime: Date;
|
||||
public readonly ctime: Date;
|
||||
public readonly birthtime: Date;
|
||||
constructor(dev: number, ino: number, mode: number, size: number,
|
||||
atimeMS: number, mtimeMS: number, ctimeMS: number, birthtimeMS: number) {
|
||||
this.dev = dev;
|
||||
this.ino = ino;
|
||||
this.mode = mode;
|
||||
this.size = size;
|
||||
this.atimeMS = atimeMS;
|
||||
this.mtimeMS = mtimeMS;
|
||||
this.ctimeMS = ctimeMS;
|
||||
this.birthtimeMS = birthtimeMS;
|
||||
this.atime = new Date(atimeMS + 0.5);
|
||||
this.mtime = new Date(mtimeMS + 0.5);
|
||||
this.ctime = new Date(ctimeMS + 0.5);
|
||||
this.birthtime = new Date(birthtimeMS + 0.5);
|
||||
}
|
||||
|
||||
public isFile() {
|
||||
return (this.mode & S_IFMT) === S_IFREG;
|
||||
}
|
||||
|
||||
public isDirectory() {
|
||||
return (this.mode & S_IFMT) === S_IFDIR;
|
||||
}
|
||||
|
||||
public isSymbolicLink() {
|
||||
return (this.mode & S_IFMT) === S_IFLNK;
|
||||
}
|
||||
}
|
||||
|
||||
function findTarget(vfs: VirtualFileSystem, target: string, set?: Set<VirtualSymlink>): VirtualEntry | undefined {
|
||||
const entry = vfs.getEntry(target);
|
||||
if (entry instanceof VirtualFileSymlink || entry instanceof VirtualDirectorySymlink) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
/// <reference path="harness.ts" />
|
||||
/// <reference path="./mocks.ts" />
|
||||
|
||||
// TODO(rbuckton): Migrate this to use vfs.
|
||||
|
||||
@ -160,7 +161,7 @@ interface Array<T> {}`
|
||||
checkMapKeys(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
|
||||
}
|
||||
|
||||
export function checkOutputContains(host: TestServerHost, expected: ReadonlyArray<string>) {
|
||||
export function checkOutputContains(host: TestServerHost | mocks.MockServerHost, expected: ReadonlyArray<string>) {
|
||||
const mapExpected = arrayToSet(expected);
|
||||
const mapSeen = createMap<true>();
|
||||
for (const f of host.getOutput()) {
|
||||
@ -173,7 +174,7 @@ interface Array<T> {}`
|
||||
assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`);
|
||||
}
|
||||
|
||||
export function checkOutputDoesNotContain(host: TestServerHost, expectedToBeAbsent: string[] | ReadonlyArray<string>) {
|
||||
export function checkOutputDoesNotContain(host: TestServerHost | mocks.MockServerHost, expectedToBeAbsent: string[] | ReadonlyArray<string>) {
|
||||
const mapExpectedToBeAbsent = arrayToSet(expectedToBeAbsent);
|
||||
for (const f of host.getOutput()) {
|
||||
assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(host.getOutput())}`);
|
||||
@ -686,4 +687,5 @@ interface Array<T> {}`
|
||||
}
|
||||
readonly getEnvironmentVariable = notImplemented;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// @lib: es5
|
||||
// @lib: es2015
|
||||
// @strict: true
|
||||
// @target: es2015
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user