diff --git a/src/harness/compiler.ts b/src/harness/compiler.ts
index 9d0a07e707a..f9df4167276 100644
--- a/src/harness/compiler.ts
+++ b/src/harness/compiler.ts
@@ -3,7 +3,6 @@
///
///
///
-///
///
///
@@ -79,13 +78,13 @@ namespace compiler {
const dts = this.dts = new core.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" });
const maps = this.maps = new core.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" });
for (const document of this.host.outputs) {
- if (vfsutils.isJavaScript(document.file)) {
+ if (vpath.isJavaScript(document.file)) {
js.set(document.file, document);
}
- else if (vfsutils.isDeclaration(document.file)) {
+ else if (vpath.isDeclaration(document.file)) {
dts.set(document.file, document);
}
- else if (vfsutils.isSourceMap(document.file)) {
+ else if (vpath.isSourceMap(document.file)) {
maps.set(document.file, document);
}
}
@@ -100,7 +99,7 @@ namespace compiler {
if (sourceFile) {
const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text);
this._inputs.push(input);
- if (!vfsutils.isDeclaration(sourceFile.fileName)) {
+ if (!vpath.isDeclaration(sourceFile.fileName)) {
inputs.push(input);
}
}
@@ -126,7 +125,7 @@ namespace compiler {
if (sourceFile) {
const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text);
this._inputs.push(input);
- if (!vfsutils.isDeclaration(sourceFile.fileName)) {
+ if (!vpath.isDeclaration(sourceFile.fileName)) {
const extname = ts.getOutputExtension(sourceFile, this.options);
const outputs: CompilationOutput = {
inputs: [input],
@@ -198,7 +197,7 @@ namespace compiler {
}
public getSourceMap(path: string): documents.SourceMap | undefined {
- if (this.options.noEmit || vfsutils.isDeclaration(path)) return undefined;
+ if (this.options.noEmit || vpath.isDeclaration(path)) return undefined;
if (this.options.inlineSourceMap) {
const document = this.getOutput(path, "js");
return document && documents.SourceMap.fromSource(document.text);
diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts
index abac54e4ea9..9b1b83666cf 100644
--- a/src/harness/fakes.ts
+++ b/src/harness/fakes.ts
@@ -29,7 +29,7 @@ namespace fakes {
private readonly _executingFilePath: string | undefined;
private readonly _env: Record | undefined;
- constructor(vfs: vfs.FileSystem, { executingFilePath, newLine = "\n", env }: SystemOptions = {}) {
+ constructor(vfs: vfs.FileSystem, { executingFilePath, newLine = "\r\n", env }: SystemOptions = {}) {
this.vfs = vfs.isReadonly ? vfs.shadow() : vfs;
this.useCaseSensitiveFileNames = !this.vfs.ignoreCase;
this.newLine = newLine;
@@ -46,7 +46,7 @@ namespace fakes {
const content = this.vfs.readFileSync(path, "utf8");
return content === undefined ? undefined :
vpath.extname(path) === ".json" ? utils.removeComments(core.removeByteOrderMark(content), utils.CommentRemoval.leadingAndTrailing) :
- core.removeByteOrderMark(content);
+ core.removeByteOrderMark(content);
}
catch {
return undefined;
@@ -90,26 +90,28 @@ namespace fakes {
}
public readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] {
- return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => {
- const files: string[] = [];
- const directories: string[] = [];
- try {
- for (const file of this.vfs.readdirSync(path)) {
- try {
- const stats = this.vfs.statSync(vpath.combine(path, file));
- if (stats.isFile()) {
- files.push(file);
- }
- else if (stats.isDirectory()) {
- directories.push(file);
- }
+ return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path));
+ }
+
+ public getAccessibleFileSystemEntries(path: string): ts.FileSystemEntries {
+ const files: string[] = [];
+ const directories: string[] = [];
+ try {
+ for (const file of this.vfs.readdirSync(path)) {
+ try {
+ const stats = this.vfs.statSync(vpath.combine(path, file));
+ if (stats.isFile()) {
+ files.push(file);
+ }
+ else if (stats.isDirectory()) {
+ directories.push(file);
}
- catch { /*ignored*/ }
}
+ catch { /*ignored*/ }
}
- catch { /*ignored*/ }
- return { files, directories };
- });
+ }
+ catch { /*ignored*/ }
+ return { files, directories };
}
public exit(exitCode?: number) {
@@ -214,8 +216,8 @@ namespace fakes {
private _parseConfigHost: ParseConfigHost;
private _newLine: string;
- constructor(sys: System | vfs.FileSystem, options: ts.CompilerOptions, setParentNodes = false) {
- if (sys instanceof vfs.FileSystem) sys = new System(sys, { newLine: "\r\n" });
+ constructor(sys: System | vfs.FileSystem, options = ts.getDefaultCompilerOptions(), setParentNodes = false) {
+ if (sys instanceof vfs.FileSystem) sys = new System(sys);
this.sys = sys;
this.defaultLibLocation = sys.vfs.meta.get("defaultLibLocation") || "";
this._newLine = ts.getNewLineCharacter(options, () => this.sys.newLine);
diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts
index c6ab417fb19..951fa701157 100644
--- a/src/harness/fourslash.ts
+++ b/src/harness/fourslash.ts
@@ -276,7 +276,13 @@ namespace FourSlash {
if (configFileName) {
const baseDir = ts.normalizePath(ts.getDirectoryPath(configFileName));
- const host = new fakes.ParseConfigHost(vfsutils.createFromMap(baseDir, /*ignoreCase*/ true, this.inputFiles));
+ const files: vfs.FileSet = { [baseDir]: {} };
+ this.inputFiles.forEach((data, path) => {
+ const scriptInfo = new Harness.LanguageService.ScriptInfo(path, undefined, /*isRootFile*/ false);
+ files[path] = new vfs.File(data, { meta: { scriptInfo } });
+ });
+ const fs = new vfs.FileSystem(/*ignoreCase*/ true, { cwd: baseDir, files });
+ const host = new fakes.ParseConfigHost(fs);
const configJsonObj = ts.parseConfigFileTextToJson(configFileName, this.inputFiles.get(configFileName));
assert.isTrue(configJsonObj.config !== undefined);
diff --git a/src/harness/harness.ts b/src/harness/harness.ts
index 034c5f6b228..55da2cfac6c 100644
--- a/src/harness/harness.ts
+++ b/src/harness/harness.ts
@@ -21,7 +21,6 @@
///
///
///
-///
///
///
///
@@ -525,17 +524,12 @@ namespace Harness {
getExecutingFilePath(): string;
exit(exitCode?: number): void;
readDirectory(path: string, extension?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[];
- getAccessibleFileSystemEntries(dirname: string): FileSystemEntries;
+ getAccessibleFileSystemEntries(dirname: string): ts.FileSystemEntries;
tryEnableSourceMapsForHost?(): void;
getEnvironmentVariable?(name: string): string;
getMemoryUsage?(): number;
}
- export interface FileSystemEntries {
- files: string[];
- directories: string[];
- }
-
export let IO: IO;
// harness always uses one kind of new line
@@ -591,7 +585,7 @@ namespace Harness {
return filesInFolder(path);
}
- function getAccessibleFileSystemEntries(dirname: string): FileSystemEntries {
+ function getAccessibleFileSystemEntries(dirname: string): ts.FileSystemEntries {
try {
const entries: string[] = fs.readdirSync(dirname || ".").sort(ts.sys.useCaseSensitiveFileNames ? ts.compareStringsCaseSensitive : ts.compareStringsCaseInsensitive);
const files: string[] = [];
@@ -944,20 +938,22 @@ namespace Harness {
function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[], depth?: number) {
const fs = new vfs.FileSystem(!useCaseSensitiveFileNames(), { cwd: path, files: { [path]: { } } });
+ const sys = new fakes.System(fs);
for (const file of IO.listFiles(path)) {
fs.mkdirpSync(vpath.dirname(file));
fs.writeFileSync(file, "");
}
- return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), /*currentDirectory*/ "", depth, path => vfsutils.getAccessibleFileSystemEntries(fs, path));
+ return sys.readDirectory(path, extension, exclude, include, depth);
}
- function getAccessibleFileSystemEntries(dirname: string): FileSystemEntries {
+ function getAccessibleFileSystemEntries(dirname: string): ts.FileSystemEntries {
const fs = new vfs.FileSystem(!useCaseSensitiveFileNames(), { cwd: dirname, files: { [dirname]: {} } });
+ const sys = new fakes.System(fs);
for (const file of IO.listFiles(path)) {
fs.mkdirpSync(vpath.dirname(file));
fs.writeFileSync(file, "");
}
- return vfsutils.getAccessibleFileSystemEntries(fs, dirname);
+ return sys.getAccessibleFileSystemEntries(dirname);
}
return {
@@ -1220,7 +1216,7 @@ namespace Harness {
options.skipDefaultLibCheck = typeof options.skipDefaultLibCheck === "undefined" ? true : options.skipDefaultLibCheck;
if (typeof currentDirectory === "undefined") {
- currentDirectory = vfsutils.srcFolder;
+ currentDirectory = vfs.srcFolder;
}
// Parse settings
@@ -1237,27 +1233,20 @@ namespace Harness {
// Files from built\local that are requested by test "@includeBuiltFiles" to be in the context.
// Treat them as library files, so include them in build, but not in baselines.
if (options.includeBuiltFile) {
- programFileNames.push(vpath.combine(vfsutils.builtFolder, options.includeBuiltFile));
+ programFileNames.push(vpath.combine(vfs.builtFolder, options.includeBuiltFile));
}
// Files from tests\lib that are requested by "@libFiles"
if (options.libFiles) {
for (const fileName of options.libFiles.split(",")) {
- programFileNames.push(vpath.combine(vfsutils.testLibFolder, fileName));
+ programFileNames.push(vpath.combine(vfs.testLibFolder, fileName));
}
}
- return compiler.compileFiles(
- new fakes.CompilerHost(
- vfsutils.createFromDocuments(
- useCaseSensitiveFileNames,
- inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile),
- { currentDirectory, overwrite: true }
- ),
- options
- ),
- programFileNames,
- options);
+ const docs = inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile);
+ const fs = vfs.FileSystem.createFromFileSystem(IO, !useCaseSensitiveFileNames, { documents: docs, cwd: currentDirectory });
+ const host = new fakes.CompilerHost(fs, options);
+ return compiler.compileFiles(host, programFileNames, options);
}
export interface DeclarationCompilationContext {
@@ -1298,10 +1287,10 @@ namespace Harness {
}
function addDtsFile(file: TestFile, dtsFiles: TestFile[]) {
- if (vfsutils.isDeclaration(file.unitName)) {
+ if (vpath.isDeclaration(file.unitName)) {
dtsFiles.push(file);
}
- else if (vfsutils.isTypeScript(file.unitName)) {
+ else if (vpath.isTypeScript(file.unitName)) {
const declFile = findResultCodeFile(file.unitName);
if (declFile && !findUnit(declFile.file, declInputFiles) && !findUnit(declFile.file, declOtherFiles)) {
dtsFiles.push({ unitName: declFile.file, content: core.removeByteOrderMark(declFile.text) });
@@ -2114,7 +2103,7 @@ namespace Harness {
export function isBuiltFile(filePath: string): boolean {
return filePath.indexOf(libFolder) === 0 ||
- filePath.indexOf(vpath.addTrailingSeparator(vfsutils.builtFolder)) === 0;
+ filePath.indexOf(vpath.addTrailingSeparator(vfs.builtFolder)) === 0;
}
export function getDefaultLibraryFile(filePath: string, io: IO): Compiler.TestFile {
diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts
index b86dd7bc94e..5732d9fcfe1 100644
--- a/src/harness/harnessLanguageService.ts
+++ b/src/harness/harnessLanguageService.ts
@@ -117,7 +117,7 @@ namespace Harness.LanguageService {
}
export abstract class LanguageServiceAdapterHost {
- public readonly vfs = new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot });
+ public readonly sys = new fakes.System(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot }));
public typesRegistry: ts.Map | undefined;
private scriptInfos: core.SortedMap;
@@ -126,6 +126,10 @@ namespace Harness.LanguageService {
this.scriptInfos = new core.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" });
}
+ public get vfs() {
+ return this.sys.vfs;
+ }
+
public getNewLine(): string {
return harnessNewLine;
}
@@ -191,7 +195,7 @@ namespace Harness.LanguageService {
getCancellationToken() { return this.cancellationToken; }
getDirectories(path: string): string[] {
- return vfsutils.getDirectories(this.vfs, path);
+ return this.sys.getDirectories(path);
}
getCurrentDirectory(): string { return virtualFileSystemRoot; }
@@ -215,27 +219,23 @@ namespace Harness.LanguageService {
}
directoryExists(dirName: string): boolean {
- return vfsutils.directoryExists(this.vfs, dirName);
+ return this.sys.directoryExists(dirName);
}
fileExists(fileName: string): boolean {
- return vfsutils.fileExists(this.vfs, fileName);
+ return this.sys.fileExists(fileName);
}
readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] {
- return ts.matchFiles(path, extensions, exclude, include,
- /*useCaseSensitiveFileNames*/ false,
- this.getCurrentDirectory(),
- depth,
- (p) => vfsutils.getAccessibleFileSystemEntries(this.vfs, p));
+ return this.sys.readDirectory(path, extensions, exclude, include, depth);
}
readFile(path: string): string | undefined {
- return vfsutils.readFile(this.vfs, path);
+ return this.sys.readFile(path);
}
realpath(path: string): string {
- return this.vfs.realpathSync(path);
+ return this.sys.realpath(path);
}
getTypeRootsVersion() {
diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts
index 0de316f02bd..b7f738bdeb3 100644
--- a/src/harness/projectsRunner.ts
+++ b/src/harness/projectsRunner.ts
@@ -1,9 +1,10 @@
///
///
///
+///
///
///
-///
+///
namespace project {
// Test case is json of below type in tests/cases/project/
@@ -100,7 +101,7 @@ namespace project {
public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
const result = super.readDirectory(path, extensions, excludes, includes, depth);
- const projectRoot = vpath.resolve(vfsutils.srcFolder, this._testCase.projectRoot);
+ const projectRoot = vpath.resolve(vfs.srcFolder, this._testCase.projectRoot);
return result.map(item => vpath.relative(
projectRoot,
vpath.resolve(projectRoot, item),
@@ -123,7 +124,7 @@ namespace project {
class ProjectTestCase {
private testCase: ProjectRunnerTestCase & ts.CompilerOptions;
private testCaseJustName: string;
- private vfs: vfs.FileSystem;
+ private sys: fakes.System;
private compilerOptions: ts.CompilerOptions;
private compilerResult: BatchCompileProjectTestCaseResult;
@@ -131,7 +132,7 @@ namespace project {
this.testCase = testCase;
this.testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, "");
this.compilerOptions = createCompilerOptions(testCase, moduleKind);
- this.vfs = vfs.shadow();
+ this.sys = new fakes.System(vfs);
let configFileName: string;
let inputFiles = testCase.inputFiles;
@@ -141,22 +142,22 @@ namespace project {
assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together");
}
else if (!inputFiles || inputFiles.length === 0) {
- configFileName = ts.findConfigFile("", path => vfsutils.fileExists(this.vfs, path));
+ configFileName = ts.findConfigFile("", path => this.sys.fileExists(path));
}
let errors: ts.Diagnostic[];
const configFileSourceFiles: ts.SourceFile[] = [];
if (configFileName) {
- const result = ts.readJsonConfigFile(configFileName, path => vfsutils.readFile(this.vfs, path));
+ const result = ts.readJsonConfigFile(configFileName, path => this.sys.readFile(path));
configFileSourceFiles.push(result);
- const configParseHost = new ProjectParseConfigHost(new fakes.System(this.vfs), this.testCase);
+ const configParseHost = new ProjectParseConfigHost(this.sys, this.testCase);
const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), this.compilerOptions);
inputFiles = configParseResult.fileNames;
this.compilerOptions = configParseResult.options;
errors = result.parseDiagnostics.concat(configParseResult.errors);
}
- const compilerHost = new ProjectCompilerHost(this.vfs, this.compilerOptions, this.testCaseJustName, this.testCase, moduleKind);
+ const compilerHost = new ProjectCompilerHost(this.sys, this.compilerOptions, this.testCaseJustName, this.testCase, moduleKind);
const projectCompilerResult = this.compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, compilerHost, this.compilerOptions);
this.compilerResult = {
@@ -170,6 +171,10 @@ namespace project {
};
}
+ private get vfs() {
+ return this.sys.vfs;
+ }
+
public static getConfigurations(testCaseFileName: string): ProjectTestConfiguration[] {
let testCase: ProjectRunnerTestCase & ts.CompilerOptions;
@@ -188,10 +193,10 @@ namespace project {
assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message);
}
- const fs = vfsutils.createFromFileSystem(/*useCaseSensitiveFileNames*/ true);
- fs.mountSync(vpath.resolve(__dirname, "../../tests"), vpath.combine(vfsutils.srcFolder, "tests"), vfsutils.createResolver(Harness.IO));
- fs.mkdirpSync(vpath.combine(vfsutils.srcFolder, testCase.projectRoot));
- fs.chdir(vpath.combine(vfsutils.srcFolder, testCase.projectRoot));
+ const fs = vfs.FileSystem.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
+ fs.mountSync(vpath.resolve(__dirname, "../../tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO));
+ fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot));
+ fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot));
fs.makeReadonly();
return [
@@ -355,7 +360,7 @@ namespace project {
const rootFiles: string[] = [];
ts.forEach(compilerResult.program.getSourceFiles(), sourceFile => {
if (sourceFile.isDeclarationFile) {
- if (!vfsutils.isDefaultLibrary(sourceFile.fileName)) {
+ if (!vpath.isDefaultLibrary(sourceFile.fileName)) {
allInputFiles.unshift(new documents.TextDocument(sourceFile.fileName, sourceFile.text));
}
rootFiles.unshift(sourceFile.fileName);
@@ -388,8 +393,9 @@ namespace project {
}
});
- const _vfs = vfsutils.createFromDocuments(/*useCaseSensitiveFileNames*/ true, allInputFiles, {
- currentDirectory: vpath.combine(vfsutils.srcFolder, this.testCase.projectRoot)
+ const _vfs = vfs.FileSystem.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, {
+ documents: allInputFiles,
+ cwd: vpath.combine(vfs.srcFolder, this.testCase.projectRoot)
});
// Dont allow config files since we are compiling existing source options
@@ -438,11 +444,11 @@ namespace project {
moduleResolution: ts.ModuleResolutionKind.Classic,
module: moduleKind,
mapRoot: testCase.resolveMapRoot && testCase.mapRoot
- ? vpath.resolve(vfsutils.srcFolder, testCase.mapRoot)
+ ? vpath.resolve(vfs.srcFolder, testCase.mapRoot)
: testCase.mapRoot,
sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot
- ? vpath.resolve(vfsutils.srcFolder, testCase.sourceRoot)
+ ? vpath.resolve(vfs.srcFolder, testCase.sourceRoot)
: testCase.sourceRoot
};
diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json
index 7292ca921c3..5d736fbd2ed 100644
--- a/src/harness/tsconfig.json
+++ b/src/harness/tsconfig.json
@@ -143,7 +143,6 @@
"documents.ts",
"vpath.ts",
"vfs.ts",
- "vfsutils.ts",
"compiler.ts",
"fakes.ts",
diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts
index a217911b9c3..ca7b06c495d 100644
--- a/src/harness/unittests/configurationExtension.ts
+++ b/src/harness/unittests/configurationExtension.ts
@@ -1,6 +1,5 @@
///
///
-///
///
namespace ts {
diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts
index 96a387b7fcb..eaf90a211cc 100644
--- a/src/harness/unittests/extractTestHelpers.ts
+++ b/src/harness/unittests/extractTestHelpers.ts
@@ -153,7 +153,7 @@ namespace ts {
});
}
- function makeProgram(f: { path: string, content: string }, includeLib?: boolean) {
+ function makeProgram(f: {path: string, content: string }, includeLib?: boolean) {
const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts
index b65b14af964..efbf42e6004 100644
--- a/src/harness/unittests/programMissingFiles.ts
+++ b/src/harness/unittests/programMissingFiles.ts
@@ -37,10 +37,10 @@ namespace ts {
);
const testCompilerHost = new fakes.CompilerHost(
- vfsutils.createFromDocuments(
- /*useCaseSensitiveFileNames*/ false,
- [emptyFile, referenceFile],
- { currentDirectory: "d:\\pretend\\" }),
+ vfs.FileSystem.createFromFileSystem(
+ Harness.IO,
+ /*ignoreCase*/ true,
+ { documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }),
{ newLine: NewLineKind.LineFeed });
it("handles no missing root files", () => {
diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts
index 618fafb3d41..b88e7842884 100644
--- a/src/harness/vfs.ts
+++ b/src/harness/vfs.ts
@@ -1,5 +1,20 @@
// tslint:disable:no-null-keyword
namespace vfs {
+ /**
+ * Posix-style path to the TypeScript compiler build outputs (including tsc.js, lib.d.ts, etc.)
+ */
+ export const builtFolder = "/.ts";
+
+ /**
+ * Posix-style path to additional test libraries
+ */
+ export const testLibFolder = "/.lib";
+
+ /**
+ * Posix-style path to sources under test
+ */
+ export const srcFolder = "/.src";
+
// file type
const S_IFMT = 0o170000; // file type
const S_IFSOCK = 0o140000; // socket
@@ -107,7 +122,43 @@ namespace vfs {
}
/**
- * Gets a shadow of this file system.
+ * Create a virtual file system from a physical file system using the following path mappings:
+ *
+ * - `/.ts` is a directory mapped to `${workspaceRoot}/built/local`
+ * - `/.lib` is a directory mapped to `${workspaceRoot}/tests/lib`
+ * - `/.src` is a virtual directory to be used for tests.
+ *
+ * Unless overridden, `/.src` will be the current working directory for the virtual file system.
+ */
+ public static createFromFileSystem(host: FileSystemResolverHost, ignoreCase: boolean, { documents, cwd }: FileSystemCreateOptions = {}) {
+ const fs = getBuiltLocal(host, ignoreCase).shadow();
+ if (cwd) {
+ fs.mkdirpSync(cwd);
+ fs.chdir(cwd);
+ }
+ if (documents) {
+ for (const document of documents) {
+ fs.mkdirpSync(vpath.dirname(document.file));
+ fs.writeFileSync(document.file, document.text, "utf8");
+ fs.filemeta(document.file).set("document", document);
+ // Add symlinks
+ const symlink = document.meta.get("symlink");
+ if (symlink) {
+ for (const link of symlink.split(",").map(link => link.trim())) {
+ fs.mkdirpSync(vpath.dirname(link));
+ fs.symlinkSync(document.file, link);
+ fs.filemeta(link).set("document", document);
+ }
+ }
+ }
+ }
+ return fs;
+ }
+
+ /**
+ * Gets a shadow copy of this file system. Changes to the shadow copy do not affect the
+ * original, allowing multiple copies of the same core file system without multiple copies
+ * of the same data.
*/
public shadow(ignoreCase = this.ignoreCase) {
if (!this.isReadonly) throw new Error("Cannot shadow a mutable file system.");
@@ -124,7 +175,7 @@ namespace vfs {
* @link http://pubs.opengroup.org/onlinepubs/9699919799/functions/time.html
*/
public time(value?: number | Date | (() => number | Date)): number {
- if (value !== undefined && this.isReadonly) throw new IOError("EPERM");
+ if (value !== undefined && this.isReadonly) throw createIOError("EPERM");
let result = this._time;
if (typeof result === "function") result = result();
if (typeof result === "object") result = result.getTime();
@@ -141,7 +192,7 @@ namespace vfs {
*/
public filemeta(path: string): core.Metadata {
const { node } = this._walk(this._resolve(path));
- if (!node) throw new IOError("ENOENT");
+ if (!node) throw createIOError("ENOENT");
return this._filemeta(node);
}
@@ -161,8 +212,8 @@ namespace vfs {
public cwd() {
if (!this._cwd) throw new Error("The current working directory has not been set.");
const { node } = this._walk(this._cwd);
- if (!node) throw new IOError("ENOENT");
- if (!isDirectory(node)) throw new IOError("ENOTDIR");
+ if (!node) throw createIOError("ENOENT");
+ if (!isDirectory(node)) throw createIOError("ENOTDIR");
return this._cwd;
}
@@ -172,11 +223,11 @@ namespace vfs {
* @link http://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html
*/
public chdir(path: string) {
- if (this.isReadonly) throw new IOError("EPERM");
+ if (this.isReadonly) throw createIOError("EPERM");
path = this._resolve(path);
const { node } = this._walk(path);
- if (!node) throw new IOError("ENOENT");
- if (!isDirectory(node)) throw new IOError("ENOTDIR");
+ if (!node) throw createIOError("ENOENT");
+ if (!isDirectory(node)) throw createIOError("ENOTDIR");
this._cwd = path;
}
@@ -184,7 +235,7 @@ namespace vfs {
* Pushes the current directory onto the directory stack and changes the current working directory to the supplied path.
*/
public pushd(path?: string) {
- if (this.isReadonly) throw new IOError("EPERM");
+ if (this.isReadonly) throw createIOError("EPERM");
if (path) path = this._resolve(path);
if (this._cwd) {
if (!this._dirStack) this._dirStack = [];
@@ -199,7 +250,7 @@ namespace vfs {
* Pops the previous directory from the location stack and changes the current directory to that directory.
*/
public popd() {
- if (this.isReadonly) throw new IOError("EPERM");
+ if (this.isReadonly) throw createIOError("EPERM");
const path = this._dirStack && this._dirStack.pop();
if (path) {
this.chdir(path);
@@ -279,12 +330,12 @@ namespace vfs {
* @param resolver An object used to resolve files in `source`.
*/
public mountSync(source: string, target: string, resolver: FileSystemResolver) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
source = vpath.validate(source, vpath.ValidationFlags.Absolute);
const { parent, links, node: existingNode, basename } = this._walk(this._resolve(target), /*noFollow*/ true);
- if (existingNode) throw new IOError("EEXIST");
+ if (existingNode) throw createIOError("EEXIST");
const time = this.time();
const node = this._mknod(parent ? parent.dev : ++devCount, S_IFDIR, /*mode*/ 0o777, time);
@@ -394,7 +445,7 @@ namespace vfs {
private _stat(entry: WalkResult) {
const node = entry.node;
- if (!node) throw new IOError("ENOENT");
+ if (!node) throw createIOError("ENOENT");
return new Stats(
node.dev,
node.ino,
@@ -420,8 +471,8 @@ namespace vfs {
*/
public readdirSync(path: string) {
const { node } = this._walk(this._resolve(path));
- if (!node) throw new IOError("ENOENT");
- if (!isDirectory(node)) throw new IOError("ENOTDIR");
+ if (!node) throw createIOError("ENOENT");
+ if (!isDirectory(node)) throw createIOError("ENOTDIR");
return Array.from(this._getLinks(node).keys());
}
@@ -433,10 +484,10 @@ namespace vfs {
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public mkdirSync(path: string) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
const { parent, links, node: existingNode, basename } = this._walk(this._resolve(path), /*noFollow*/ true);
- if (existingNode) throw new IOError("EEXIST");
+ if (existingNode) throw createIOError("EEXIST");
const time = this.time();
const node = this._mknod(parent ? parent.dev : ++devCount, S_IFDIR, /*mode*/ 0o777, time);
@@ -451,13 +502,13 @@ namespace vfs {
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public rmdirSync(path: string) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
path = this._resolve(path);
const { parent, links, node, basename } = this._walk(path, /*noFollow*/ true);
- if (!parent) throw new IOError("EPERM");
- if (!isDirectory(node)) throw new IOError("ENOTDIR");
- if (this._getLinks(node).size !== 0) throw new IOError("ENOTEMPTY");
+ if (!parent) throw createIOError("EPERM");
+ if (!isDirectory(node)) throw createIOError("ENOTDIR");
+ if (this._getLinks(node).size !== 0) throw createIOError("ENOTEMPTY");
this._removeLink(parent, links, basename, node);
}
@@ -470,15 +521,15 @@ namespace vfs {
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public linkSync(oldpath: string, newpath: string) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
const { node } = this._walk(this._resolve(oldpath));
- if (!node) throw new IOError("ENOENT");
- if (isDirectory(node)) throw new IOError("EPERM");
+ if (!node) throw createIOError("ENOENT");
+ if (isDirectory(node)) throw createIOError("EPERM");
const { parent, links, basename, node: existingNode } = this._walk(this._resolve(newpath), /*noFollow*/ true);
- if (!parent) throw new IOError("EPERM");
- if (existingNode) throw new IOError("EEXIST");
+ if (!parent) throw createIOError("EPERM");
+ if (existingNode) throw createIOError("EEXIST");
this._addLink(parent, links, basename, node);
}
@@ -491,12 +542,12 @@ namespace vfs {
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public unlinkSync(path: string) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
const { parent, links, node, basename } = this._walk(this._resolve(path), /*noFollow*/ true);
- if (!parent) throw new IOError("EPERM");
- if (!node) throw new IOError("ENOENT");
- if (isDirectory(node)) throw new IOError("EISDIR");
+ if (!parent) throw createIOError("EPERM");
+ if (!node) throw createIOError("ENOENT");
+ if (isDirectory(node)) throw createIOError("EISDIR");
this._removeLink(parent, links, basename, node);
}
@@ -509,23 +560,23 @@ namespace vfs {
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public renameSync(oldpath: string, newpath: string) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
const { parent: oldParent, links: oldParentLinks, node, basename: oldBasename } = this._walk(this._resolve(oldpath), /*noFollow*/ true);
- if (!oldParent) throw new IOError("EPERM");
- if (!node) throw new IOError("ENOENT");
+ if (!oldParent) throw createIOError("EPERM");
+ if (!node) throw createIOError("ENOENT");
const { parent: newParent, links: newParentLinks, node: existingNode, basename: newBasename } = this._walk(this._resolve(newpath), /*noFollow*/ true);
- if (!newParent) throw new IOError("EPERM");
+ if (!newParent) throw createIOError("EPERM");
const time = this.time();
if (existingNode) {
if (isDirectory(node)) {
- if (!isDirectory(existingNode)) throw new IOError("ENOTDIR");
- if (this._getLinks(existingNode).size > 0) throw new IOError("ENOTEMPTY");
+ if (!isDirectory(existingNode)) throw createIOError("ENOTDIR");
+ if (this._getLinks(existingNode).size > 0) throw createIOError("ENOTEMPTY");
}
else {
- if (isDirectory(existingNode)) throw new IOError("EISDIR");
+ if (isDirectory(existingNode)) throw createIOError("EISDIR");
}
this._removeLink(newParent, newParentLinks, newBasename, existingNode, time);
}
@@ -541,11 +592,11 @@ namespace vfs {
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public symlinkSync(target: string, linkpath: string) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
const { parent, links, node: existingNode, basename } = this._walk(this._resolve(linkpath), /*noFollow*/ true);
- if (!parent) throw new IOError("EPERM");
- if (existingNode) throw new IOError("EEXIST");
+ if (!parent) throw createIOError("EPERM");
+ if (existingNode) throw createIOError("EEXIST");
const time = this.time();
const node = this._mknod(parent.dev, S_IFLNK, /*mode*/ 0o666, time);
@@ -553,20 +604,6 @@ namespace vfs {
this._addLink(parent, links, basename, node, time);
}
- /**
- * Read the contents of a symbolic link.
- *
- * @link http://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html
- *
- * NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
- */
- public readlinkSync(path: string) {
- const { node } = this._walk(this._resolve(path), /*noFollow*/ true);
- if (!node) throw new IOError("ENOENT");
- if (!isSymlink(node)) throw new IOError("EINVAL");
- return node.symlink;
- }
-
/**
* Resolve a pathname.
*
@@ -599,9 +636,9 @@ namespace vfs {
public readFileSync(path: string, encoding?: string | null): string | Buffer;
public readFileSync(path: string, encoding: string | null = null) {
const { node } = this._walk(this._resolve(path));
- if (!node) throw new IOError("ENOENT");
- if (isDirectory(node)) throw new IOError("EISDIR");
- if (!isFile(node)) throw new IOError("EBADF");
+ if (!node) throw createIOError("ENOENT");
+ if (isDirectory(node)) throw createIOError("EISDIR");
+ if (!isFile(node)) throw createIOError("EBADF");
const buffer = this._getBuffer(node).slice();
return encoding ? buffer.toString(encoding) : buffer;
@@ -613,10 +650,10 @@ namespace vfs {
* NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module.
*/
public writeFileSync(path: string, data: string | Buffer, encoding: string | null = null) {
- if (this.isReadonly) throw new IOError("EROFS");
+ if (this.isReadonly) throw createIOError("EROFS");
const { parent, links, node: existingNode, basename } = this._walk(this._resolve(path), /*noFollow*/ false);
- if (!parent) throw new IOError("EPERM");
+ if (!parent) throw createIOError("EPERM");
const time = this.time();
let node = existingNode;
@@ -625,8 +662,8 @@ namespace vfs {
this._addLink(parent, links, basename, node, time);
}
- if (isDirectory(node)) throw new IOError("EISDIR");
- if (!isFile(node)) throw new IOError("EBADF");
+ if (isDirectory(node)) throw createIOError("EISDIR");
+ if (!isFile(node)) throw createIOError("EBADF");
node.buffer = Buffer.isBuffer(data) ? data.slice() : Buffer.from("" + data, encoding || "utf8");
node.size = node.buffer.byteLength;
node.mtimeMs = time;
@@ -807,7 +844,7 @@ namespace vfs {
let step = 0;
let depth = 0;
while (true) {
- if (depth >= 40) throw new IOError("ELOOP");
+ if (depth >= 40) throw createIOError("ELOOP");
const lastStep = step === components.length - 1;
const basename = components[step];
const node = links.get(basename);
@@ -815,7 +852,7 @@ namespace vfs {
return { realpath: vpath.format(components), basename, parent, links, node };
}
if (node === undefined) {
- throw new IOError("ENOENT");
+ throw createIOError("ENOENT");
}
if (isSymlink(node)) {
const dirname = vpath.format(components.slice(0, step));
@@ -833,7 +870,7 @@ namespace vfs {
step++;
continue;
}
- throw new IOError("ENOTDIR");
+ throw createIOError("ENOTDIR");
}
}
@@ -928,16 +965,34 @@ namespace vfs {
}
export interface FileSystemOptions {
+ // Sets the initial timestamp for new files and directories, or the function used
+ // to calculate timestamps.
time?: number | Date | (() => number | Date);
+
+ // A set of file system entries to initially add to the file system.
files?: FileSet;
+
+ // Sets the initial working directory for the file system.
cwd?: string;
+
+ // Sets initial metadata attached to the file system.
meta?: Record;
}
+ export interface FileSystemCreateOptions {
+ // Sets the documents to add to the file system.
+ documents?: ReadonlyArray;
+
+ // Sets the initial working directory for the file system.
+ cwd?: string;
+ }
+
export type Axis = "ancestors" | "ancestors-or-self" | "self" | "descendants-or-self" | "descendants";
export interface Traversal {
+ /** A function called to choose whether to continue to traverse to either ancestors or descendants. */
traverse?(path: string, stats: Stats): boolean;
+ /** A function called to choose whether to accept a path as part of the result. */
accept?(path: string, stats: Stats): boolean;
}
@@ -947,6 +1002,38 @@ namespace vfs {
readFileSync(path: string): Buffer;
}
+ export interface FileSystemResolverHost {
+ useCaseSensitiveFileNames(): boolean;
+ getAccessibleFileSystemEntries(path: string): ts.FileSystemEntries;
+ directoryExists(path: string): boolean;
+ fileExists(path: string): boolean;
+ getFileSize(path: string): number;
+ readFile(path: string): string;
+ }
+
+ export function createResolver(host: FileSystemResolverHost): FileSystemResolver {
+ return {
+ readdirSync(path: string): string[] {
+ const { files, directories } = host.getAccessibleFileSystemEntries(path);
+ return directories.concat(files);
+ },
+ statSync(path: string): { mode: number; size: number; } {
+ if (host.directoryExists(path)) {
+ return { mode: S_IFDIR | 0o777, size: 0 };
+ }
+ else if (host.fileExists(path)) {
+ return { mode: S_IFREG | 0o666, size: host.getFileSize(path) };
+ }
+ else {
+ throw new Error("ENOENT: path does not exist");
+ }
+ },
+ readFileSync(path: string): Buffer {
+ return Buffer.from(host.readFile(path), "utf8");
+ }
+ };
+ }
+
export class Stats {
public dev: number;
public ino: number;
@@ -1015,14 +1102,10 @@ namespace vfs {
EROFS: "file system is read-only"
});
- export class IOError extends Error {
- public readonly code: string;
-
- constructor(code: keyof typeof IOErrorMessages) {
- super(`${code}: ${IOErrorMessages[code]}`);
- this.name = "Error";
- this.code = code;
- }
+ export function createIOError(code: keyof typeof IOErrorMessages) {
+ const err: NodeJS.ErrnoException = new Error(`${code}: ${IOErrorMessages[code]}`);
+ err.code = code;
+ return err;
}
/**
@@ -1156,5 +1239,55 @@ namespace vfs {
links: core.SortedMap | undefined;
node: Inode | undefined;
}
+
+ // TODO(rbuckton): This patches the baseline to replace lib.d.ts with lib.es5.d.ts.
+ // This is only to make the PR for this change easier to read. A follow-up PR will
+ // revert this change and accept the new baselines.
+ // See https://github.com/Microsoft/TypeScript/pull/20763#issuecomment-352553264
+ function patchResolver(io: FileSystemResolverHost, resolver: FileSystemResolver): FileSystemResolver {
+ const libFile = vpath.combine(__dirname, "lib.d.ts");
+ const es5File = vpath.combine(__dirname, "lib.es5.d.ts");
+ const stringComparer = io.useCaseSensitiveFileNames() ? vpath.compareCaseSensitive : vpath.compareCaseInsensitive;
+ return {
+ readdirSync: path => resolver.readdirSync(path),
+ statSync: path => resolver.statSync(fixPath(path)),
+ readFileSync: (path) => resolver.readFileSync(fixPath(path))
+ };
+
+ function fixPath(path: string) {
+ return stringComparer(path, libFile) === 0 ? es5File : path;
+ }
+ }
+
+ let builtLocalHost: FileSystemResolverHost | undefined;
+ let builtLocalCI: FileSystem | undefined;
+ let builtLocalCS: FileSystem | undefined;
+
+ function getBuiltLocal(host: FileSystemResolverHost, ignoreCase: boolean): FileSystem {
+ if (builtLocalHost !== host) {
+ builtLocalCI = undefined;
+ builtLocalCS = undefined;
+ builtLocalHost = host;
+ }
+ if (!builtLocalCI) {
+ const resolver = createResolver(host);
+ builtLocalCI = new FileSystem(/*ignoreCase*/ true, {
+ files: {
+ [builtFolder]: new Mount(__dirname, patchResolver(host, resolver)),
+ [testLibFolder]: new Mount(vpath.resolve(__dirname, "../../tests/lib"), resolver),
+ [srcFolder]: {}
+ },
+ cwd: srcFolder,
+ meta: { defaultLibLocation: builtFolder }
+ });
+ builtLocalCI.makeReadonly();
+ }
+ if (ignoreCase) return builtLocalCI;
+ if (!builtLocalCS) {
+ builtLocalCS = builtLocalCI.shadow(/*ignoreCase*/ false);
+ builtLocalCS.makeReadonly();
+ }
+ return builtLocalCS;
+ }
}
// tslint:enable:no-null-keyword
\ No newline at end of file
diff --git a/src/harness/vfsutils.ts b/src/harness/vfsutils.ts
deleted file mode 100644
index 6ed2f86a19a..00000000000
--- a/src/harness/vfsutils.ts
+++ /dev/null
@@ -1,290 +0,0 @@
-///
-///
-///
-///
-///
-///
-
-namespace vfsutils {
- // file mode flags used for computing Stats
- const S_IFREG = 0x8000; // regular file
- const S_IFDIR = 0x4000; // regular directory
-
- let builtLocalCI: vfs.FileSystem | undefined;
- let builtLocalCS: vfs.FileSystem | undefined;
-
- /**
- * Posix-style path to the TypeScript compiler build outputs (including tsc.js, lib.d.ts, etc.)
- */
- export const builtFolder = "/.ts";
- export const tscPath = builtFolder + "/tsc.js";
- export const libPath = builtFolder + "/lib.d.ts";
- export const safelistPath = "/safelist.json";
-
- /**
- * Posix-style path to additional test libraries
- */
- export const testLibFolder = "/.lib";
-
- /**
- * Posix-style path to sources under test
- */
- export const srcFolder = "/.src";
-
- /**
- * DOS-style path to the TypeScript compiler build outputs (including tsc.js, lib.d.ts, etc.)
- */
- export const dosBuiltFolder = "c:" + builtFolder;
- export const dosTscPath = dosBuiltFolder + "/tsc.js";
- export const dosLibPath = dosBuiltFolder + "/lib.d.ts";
- export const dosSafelistPath = dosBuiltFolder + "/safelist.json";
-
- /**
- * DOS-style path to additional test libraries
- */
- export const dosTestLibFolder = "c:" + testLibFolder;
-
- /**
- * DOS-style path to sources under test
- */
- export const dosSrcFolder = "c:" + srcFolder;
-
- /** Default safelist.json content used by a number of tests. */
- export const safelistContent = utils.dedent`
- {
- "commander": "commander",
- "express": "express",
- "jquery": "jquery",
- "lodash": "lodash",
- "moment": "moment",
- "chroma": "chroma-js"
- }`;
-
- /** A minimal lib.d.ts used by a number of tests. */
- export const emptyLibContent = utils.dedent`
- ///
- interface Boolean {}
- interface Function {}
- interface IArguments {}
- interface Number { toExponential: any; }
- interface Object {}
- interface RegExp {}
- interface String { charAt: any; }
- interface Array {}`;
-
- export function createResolver(io: Harness.IO): vfs.FileSystemResolver {
- return {
- readdirSync(path: string): string[] {
- const { files, directories } = io.getAccessibleFileSystemEntries(path);
- return directories.concat(files);
- },
- statSync(path: string): { mode: number; size: number; } {
- if (io.directoryExists(path)) {
- return { mode: S_IFDIR | 0o777, size: 0 };
- }
- else if (io.fileExists(path)) {
- return { mode: S_IFREG | 0o666, size: io.getFileSize(path) };
- }
- else {
- throw new Error("ENOENT: path does not exist");
- }
- },
- readFileSync(path: string): Buffer {
- return Buffer.from(io.readFile(path), "utf8");
- }
- };
- }
-
- // TODO(rbuckton): This patches the baseline to replace lib.d.ts with lib.es5.d.ts.
- // This is only to make the PR for this change easier to read. A follow-up PR will
- // revert this change and accept the new baselines.
- // See https://github.com/Microsoft/TypeScript/pull/20763#issuecomment-352553264
- function patchResolver(io: Harness.IO, resolver: vfs.FileSystemResolver): vfs.FileSystemResolver {
- const libFile = vpath.combine(__dirname, "lib.d.ts");
- const es5File = vpath.combine(__dirname, "lib.es5.d.ts");
- const stringComparer = io.useCaseSensitiveFileNames() ? vpath.compareCaseSensitive : vpath.compareCaseInsensitive;
- return {
- readdirSync: path => resolver.readdirSync(path),
- statSync: path => resolver.statSync(fixPath(path)),
- readFileSync: (path) => resolver.readFileSync(fixPath(path))
- };
-
- function fixPath(path: string) {
- return stringComparer(path, libFile) === 0 ? es5File : path;
- }
- }
-
- function getBuiltLocal(useCaseSensitiveFileNames: boolean): vfs.FileSystem {
- if (!builtLocalCI) {
- const resolver = createResolver(Harness.IO);
- builtLocalCI = new vfs.FileSystem(/*ignoreCase*/ true, {
- files: {
- [builtFolder]: new vfs.Mount(__dirname, patchResolver(Harness.IO, resolver)),
- [testLibFolder]: new vfs.Mount(vpath.resolve(__dirname, "../../tests/lib"), resolver),
- [srcFolder]: {}
- },
- cwd: srcFolder,
- meta: { defaultLibLocation: builtFolder }
- });
- builtLocalCI.makeReadonly();
- }
- if (!useCaseSensitiveFileNames) return builtLocalCI;
- if (!builtLocalCS) {
- builtLocalCS = builtLocalCI.shadow(/*ignoreCase*/ false);
- builtLocalCS.makeReadonly();
- }
- return builtLocalCS;
- }
-
- export function createFromFileSystem(useCaseSensitiveFileNames: boolean) {
- return getBuiltLocal(useCaseSensitiveFileNames).shadow();
- }
-
- export function createFromDocuments(useCaseSensitiveFileNames: boolean, documents: documents.TextDocument[], options?: { currentDirectory?: string, overwrite?: boolean }) {
- const fs = createFromFileSystem(useCaseSensitiveFileNames);
- if (options && options.currentDirectory) {
- fs.mkdirpSync(options.currentDirectory);
- fs.chdir(options.currentDirectory);
- }
- for (const document of documents) {
- fs.mkdirpSync(vpath.dirname(document.file));
- fs.writeFileSync(document.file, document.text, "utf8");
- fs.filemeta(document.file).set("document", document);
- // Add symlinks
- const symlink = document.meta.get("symlink");
- if (symlink) {
- for (const link of symlink.split(",").map(link => link.trim())) {
- fs.mkdirpSync(vpath.dirname(link));
- fs.symlinkSync(document.file, link);
- fs.filemeta(link).set("document", document);
- }
- }
- }
- return fs;
- }
-
- export function createFromMap(currentDirectory: string, ignoreCase: boolean, files: ts.Map) {
- const fs = new vfs.FileSystem(ignoreCase, { cwd: currentDirectory, files: { [currentDirectory]: {} } });
- files.forEach((fileContent, fileName) => {
- fs.mkdirpSync(vpath.dirname(fileName));
- fs.writeFileSync(fileName, fileContent);
- fs.filemeta(fileName).set("scriptInfo", new Harness.LanguageService.ScriptInfo(fileName, undefined, /*isRootFile*/ false));
- });
- return fs;
- }
-
- export function getAccessibleFileSystemEntries(fs: vfs.FileSystem, path: string) {
- const files: string[] = [];
- const directories: string[] = [];
- try {
- for (const file of fs.readdirSync(path)) {
- try {
- const stats = fs.statSync(vpath.combine(path, file));
- if (stats.isFile()) {
- files.push(file);
- }
- else if (stats.isDirectory()) {
- directories.push(file);
- }
- }
- catch { /*ignored*/ }
- }
- }
- catch { /*ignored*/ }
- return { files, directories };
- }
-
- function getStats(fs: vfs.FileSystem, path: string) {
- try {
- return fs.statSync(path);
- }
- catch {
- return undefined;
- }
- }
-
- export function getFileSize(fs: vfs.FileSystem, path: string) {
- const stats = getStats(fs, path);
- return stats && stats.isFile() ? stats.size : 0;
- }
-
- export function getModifiedTime(fs: vfs.FileSystem, path: string) {
- const stats = getStats(fs, path);
- return stats ? stats.mtime : undefined;
- }
-
- export function fileExists(fs: vfs.FileSystem, path: string) {
- const stats = getStats(fs, path);
- return stats ? stats.isFile() : false;
- }
-
- export function directoryExists(fs: vfs.FileSystem, path: string): boolean {
- const stats = getStats(fs, path);
- return stats ? stats.isDirectory() : false;
- }
-
- export function getDirectories(fs: vfs.FileSystem, path: string) {
- const result: string[] = [];
- try {
- for (const file of fs.readdirSync(path)) {
- if (fs.statSync(vpath.combine(path, file)).isDirectory()) {
- result.push(file);
- }
- }
- }
- catch { /*ignore*/ }
- return result;
- }
-
- export function readFile(fs: vfs.FileSystem, path: string): string | undefined {
- try {
- const content = fs.readFileSync(path, "utf8");
- return content === undefined ? undefined :
- vpath.extname(path) === ".json" ? utils.removeComments(core.removeByteOrderMark(content), utils.CommentRemoval.leadingAndTrailing) :
- core.removeByteOrderMark(content);
- }
- catch {
- return undefined;
- }
- }
-
- export function writeFile(fs: vfs.FileSystem, path: string, content: string, writeByteOrderMark?: boolean) {
- fs.mkdirpSync(vpath.dirname(path));
- fs.writeFileSync(path, writeByteOrderMark ? core.addUTF8ByteOrderMark(content) : content);
- }
-
- const typeScriptExtensions: ReadonlyArray = [".ts", ".tsx"];
-
- export function isTypeScript(path: string) {
- return vpath.extname(path, typeScriptExtensions, /*ignoreCase*/ false).length > 0;
- }
-
- const javaScriptExtensions: ReadonlyArray = [".js", ".jsx"];
-
- export function isJavaScript(path: string) {
- return vpath.extname(path, javaScriptExtensions, /*ignoreCase*/ false).length > 0;
- }
-
- export function isDeclaration(path: string) {
- return vpath.extname(path, ".d.ts", /*ignoreCase*/ false).length > 0;
- }
-
- export function isSourceMap(path: string) {
- return vpath.extname(path, ".map", /*ignoreCase*/ false).length > 0;
- }
-
- const javaScriptSourceMapExtensions: ReadonlyArray = [".js.map", ".jsx.map"];
-
- export function isJavaScriptSourceMap(path: string) {
- return vpath.extname(path, javaScriptSourceMapExtensions, /*ignoreCase*/ false).length > 0;
- }
-
- export function isJson(path: string) {
- return vpath.extname(path, ".json", /*ignoreCase*/ false).length > 0;
- }
-
- export function isDefaultLibrary(path: string) {
- return isDeclaration(path)
- && vpath.basename(path).startsWith("lib.");
- }
-}
diff --git a/src/harness/vpath.ts b/src/harness/vpath.ts
index 383d37bde0c..72c3f7d7bea 100644
--- a/src/harness/vpath.ts
+++ b/src/harness/vpath.ts
@@ -53,7 +53,7 @@ namespace vpath {
export function validate(path: string, flags: ValidationFlags = ValidationFlags.RelativeOrAbsolute) {
const components = parse(path);
const trailing = hasTrailingSeparator(path);
- if (!validateComponents(components, flags, trailing)) throw new vfs.IOError("ENOENT");
+ if (!validateComponents(components, flags, trailing)) throw vfs.createIOError("ENOENT");
return components.length > 1 && trailing ? format(reduce(components)) + sep : format(reduce(components));
}