mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
More cleanup and reorganization
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
/// <reference path="./core.ts" />
|
||||
/// <reference path="./vpath.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
/// <reference path="./vfsutils.ts" />
|
||||
/// <reference path="./utils.ts" />
|
||||
/// <reference path="./fakes.ts" />
|
||||
|
||||
@@ -79,13 +78,13 @@ namespace compiler {
|
||||
const dts = this.dts = new core.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const maps = this.maps = new core.SortedMap<string, documents.TextDocument>({ 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);
|
||||
|
||||
+23
-21
@@ -29,7 +29,7 @@ namespace fakes {
|
||||
private readonly _executingFilePath: string | undefined;
|
||||
private readonly _env: Record<string, string> | 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<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+17
-28
@@ -21,7 +21,6 @@
|
||||
/// <reference path="sourceMapRecorder.ts"/>
|
||||
/// <reference path="runnerbase.ts"/>
|
||||
/// <reference path="./vfs.ts" />
|
||||
/// <reference path="./vfsutils.ts" />
|
||||
/// <reference types="node" />
|
||||
/// <reference types="mocha" />
|
||||
/// <reference types="chai" />
|
||||
@@ -525,17 +524,12 @@ namespace Harness {
|
||||
getExecutingFilePath(): string;
|
||||
exit(exitCode?: number): void;
|
||||
readDirectory(path: string, extension?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, 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 {
|
||||
|
||||
@@ -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<void> | undefined;
|
||||
private scriptInfos: core.SortedMap<string, ScriptInfo>;
|
||||
|
||||
@@ -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<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, 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() {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/// <reference path="harness.ts" />
|
||||
/// <reference path="runnerbase.ts" />
|
||||
/// <reference path="./vpath.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
/// <reference path="./documents.ts" />
|
||||
/// <reference path="./compiler.ts" />
|
||||
/// <reference path="./vfsutils.ts" />
|
||||
/// <reference path="./fakes.ts" />
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
@@ -143,7 +143,6 @@
|
||||
"documents.ts",
|
||||
"vpath.ts",
|
||||
"vfs.ts",
|
||||
"vfsutils.ts",
|
||||
"compiler.ts",
|
||||
"fakes.ts",
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/// <reference path="../harness.ts" />
|
||||
/// <reference path="../vfs.ts" />
|
||||
/// <reference path="../vfsutils.ts" />
|
||||
/// <reference path="../compiler.ts" />
|
||||
|
||||
namespace 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);
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
+205
-72
@@ -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<string, any>;
|
||||
}
|
||||
|
||||
export interface FileSystemCreateOptions {
|
||||
// Sets the documents to add to the file system.
|
||||
documents?: ReadonlyArray<documents.TextDocument>;
|
||||
|
||||
// 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<string, Inode> | 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
|
||||
@@ -1,290 +0,0 @@
|
||||
/// <reference path="./harness.ts" />
|
||||
/// <reference path="./vpath.ts" />
|
||||
/// <reference path="./core/strings.ts" />
|
||||
/// <reference path="./utils.ts" />
|
||||
/// <reference path="./documents.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
|
||||
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`
|
||||
/// <reference no-default-lib="true"/>
|
||||
interface Boolean {}
|
||||
interface Function {}
|
||||
interface IArguments {}
|
||||
interface Number { toExponential: any; }
|
||||
interface Object {}
|
||||
interface RegExp {}
|
||||
interface String { charAt: any; }
|
||||
interface Array<T> {}`;
|
||||
|
||||
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<string>) {
|
||||
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<string> = [".ts", ".tsx"];
|
||||
|
||||
export function isTypeScript(path: string) {
|
||||
return vpath.extname(path, typeScriptExtensions, /*ignoreCase*/ false).length > 0;
|
||||
}
|
||||
|
||||
const javaScriptExtensions: ReadonlyArray<string> = [".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<string> = [".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.");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user