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)); }