From 0322d715154ac547da48ac8e4249694cbede911f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 19 Apr 2018 11:30:03 -0700 Subject: [PATCH] Cleanup and reorganize fakes --- src/harness/compiler.ts | 220 +---------- src/harness/core/strings.ts | 14 +- src/harness/fakes.ts | 361 ++++++++++++++---- src/harness/fourslash.ts | 2 +- src/harness/harness.ts | 2 +- src/harness/harnessLanguageService.ts | 13 +- src/harness/projectsRunner.ts | 18 +- .../unittests/configurationExtension.ts | 6 +- .../convertCompilerOptionsFromJson.ts | 2 +- .../convertTypeAcquisitionFromJson.ts | 2 +- src/harness/unittests/extractTestHelpers.ts | 24 +- src/harness/unittests/matchFiles.ts | 14 +- src/harness/unittests/programMissingFiles.ts | 2 +- src/harness/unittests/tsconfigParsing.ts | 4 +- 14 files changed, 329 insertions(+), 355 deletions(-) diff --git a/src/harness/compiler.ts b/src/harness/compiler.ts index 8b96ec38810..9d0a07e707a 100644 --- a/src/harness/compiler.ts +++ b/src/harness/compiler.ts @@ -5,229 +5,19 @@ /// /// /// +/// // NOTE: The contents of this file are all exported from the namespace 'compiler'. This is to // support the eventual conversion of harness into a modular system. namespace compiler { - /** - * A `ts.CompilerHost` that leverages a virtual file system. - */ - export class CompilerHost implements ts.CompilerHost { - public readonly vfs: vfs.FileSystem; - public readonly defaultLibLocation: string; - public readonly outputs: documents.TextDocument[] = []; - public readonly traces: string[] = []; - public readonly shouldAssertInvariants = !Harness.lightMode; - - private _setParentNodes: boolean; - private _sourceFiles: core.SortedMap; - private _newLine: string; - private _parseConfigHost: ParseConfigHost; - - constructor(vfs: vfs.FileSystem, options: ts.CompilerOptions, setParentNodes = false) { - this.vfs = vfs; - this.defaultLibLocation = vfs.meta.get("defaultLibLocation") || ""; - this._sourceFiles = new core.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" }); - this._newLine = options.newLine === ts.NewLineKind.LineFeed ? "\n" : "\r\n"; - this._setParentNodes = setParentNodes; - } - - public get parseConfigHost() { - return this._parseConfigHost || (this._parseConfigHost = new ParseConfigHost(this.vfs)); - } - - public getCurrentDirectory(): string { - return this.vfs.cwd(); - } - - public useCaseSensitiveFileNames(): boolean { - return !this.vfs.ignoreCase; - } - - public getNewLine(): string { - return this._newLine; - } - - public getCanonicalFileName(fileName: string): string { - return this.vfs.ignoreCase ? fileName.toLowerCase() : fileName; - } - - public fileExists(fileName: string): boolean { - return vfsutils.fileExists(this.vfs, fileName); - } - - public directoryExists(directoryName: string): boolean { - return vfsutils.directoryExists(this.vfs, directoryName); - } - - public getDirectories(path: string): string[] { - return vfsutils.getDirectories(this.vfs, path); - } - - public readFile(path: string): string | undefined { - if (path.endsWith("lib.d.ts")) debugger; - return vfsutils.readFile(this.vfs, path); - } - - public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) { - if (writeByteOrderMark) content = core.addUTF8ByteOrderMark(content); - vfsutils.writeFile(this.vfs, fileName, content); - const document = new documents.TextDocument(fileName, content); - document.meta.set("fileName", fileName); - this.vfs.filemeta(fileName).set("document", document); - const index = this.outputs.findIndex(output => this.vfs.stringComparer(document.file, output.file) === 0); - if (index < 0) { - this.outputs.push(document); - } - else { - this.outputs[index] = document; - } - } - - public trace(s: string): void { - this.traces.push(s); - } - - public realpath(path: string): string { - return this.vfs.realpathSync(path); - } - - public getDefaultLibLocation(): string { - return vpath.resolve(this.vfs.cwd(), this.defaultLibLocation); - } - - public getDefaultLibFileName(options: ts.CompilerOptions): string { - // return vpath.resolve(this.getDefaultLibLocation(), ts.getDefaultLibFileName(options)); - - // TODO(rbuckton): This patches the baseline to replace lib.es5.d.ts with lib.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 - return vpath.resolve(this.getDefaultLibLocation(), getDefaultLibFileName(options)); - function getDefaultLibFileName(options: ts.CompilerOptions) { - switch (options.target) { - case ts.ScriptTarget.ESNext: - case ts.ScriptTarget.ES2017: - return "lib.es2017.d.ts"; - case ts.ScriptTarget.ES2016: - return "lib.es2016.d.ts"; - case ts.ScriptTarget.ES2015: - return "lib.es2015.d.ts"; - - default: - return "lib.d.ts"; - } - } - } - - public getSourceFile(fileName: string, languageVersion: number): ts.SourceFile | undefined { - const canonicalFileName = this.getCanonicalFileName(vpath.resolve(this.vfs.cwd(), fileName)); - const existing = this._sourceFiles.get(canonicalFileName); - if (existing) return existing; - - const content = this.readFile(canonicalFileName); - if (content === undefined) return undefined; - - // A virtual file system may shadow another existing virtual file system. This - // allows us to reuse a common virtual file system structure across multiple - // tests. If a virtual file is a shadow, it is likely that the file will be - // reused across multiple tests. In that case, we cache the SourceFile we parse - // so that it can be reused across multiple tests to avoid the cost of - // repeatedly parsing the same file over and over (such as lib.d.ts). - const cacheKey = this.vfs.shadowRoot && `SourceFile[languageVersion=${languageVersion},setParentNodes=${this._setParentNodes}]`; - if (cacheKey) { - const meta = this.vfs.filemeta(canonicalFileName); - const sourceFileFromMetadata = meta.get(cacheKey) as ts.SourceFile | undefined; - if (sourceFileFromMetadata) { - this._sourceFiles.set(canonicalFileName, sourceFileFromMetadata); - return sourceFileFromMetadata; - } - } - - const parsed = ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes || this.shouldAssertInvariants); - if (this.shouldAssertInvariants) { - Utils.assertInvariants(parsed, /*parent*/ undefined); - } - - this._sourceFiles.set(canonicalFileName, parsed); - - if (cacheKey) { - // store the cached source file on the unshadowed file with the same version. - const stats = this.vfs.statSync(canonicalFileName); - - let fs = this.vfs; - while (fs.shadowRoot) { - try { - const shadowRootStats = fs.shadowRoot.statSync(canonicalFileName); - if (shadowRootStats.dev !== stats.dev || - shadowRootStats.ino !== stats.ino || - shadowRootStats.mtimeMs !== stats.mtimeMs) { - break; - } - - fs = fs.shadowRoot; - } - catch { - break; - } - } - - if (fs !== this.vfs) { - fs.filemeta(canonicalFileName).set(cacheKey, parsed); - } - } - - return parsed; - } - } - - /** - * A `ts.ParseConfigHost` that leverages a virtual file system. - */ - export class ParseConfigHost implements ts.ParseConfigHost { - public readonly vfs: vfs.FileSystem; - - constructor(vfs: vfs.FileSystem) { - this.vfs = vfs; - } - - public get useCaseSensitiveFileNames() { - return !this.vfs.ignoreCase; - } - - public fileExists(fileName: string): boolean { - return vfsutils.fileExists(this.vfs, fileName); - } - - public directoryExists(directoryName: string): boolean { - return vfsutils.directoryExists(this.vfs, directoryName); - } - - public readFile(path: string): string | undefined { - return vfsutils.readFile(this.vfs, path); - } - - public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - return ts.matchFiles( - path, - extensions, - excludes, - includes, - !this.vfs.ignoreCase, - this.vfs.cwd(), - depth, - path => vfsutils.getAccessibleFileSystemEntries(this.vfs, path)); - } - } - export interface Project { file: string; config?: ts.ParsedCommandLine; errors?: ts.Diagnostic[]; } - export function readProject(host: ParseConfigHost, project: string | undefined, existingOptions?: ts.CompilerOptions): Project | undefined { + export function readProject(host: fakes.ParseConfigHost, project: string | undefined, existingOptions?: ts.CompilerOptions): Project | undefined { if (project) { project = host.vfs.stringComparer(vpath.basename(project), "tsconfig.json") === 0 ? project : vpath.combine(project, "tsconfig.json"); @@ -265,7 +55,7 @@ namespace compiler { } export class CompilationResult { - public readonly host: CompilerHost; + public readonly host: fakes.CompilerHost; public readonly program: ts.Program | undefined; public readonly result: ts.EmitResult | undefined; public readonly options: ts.CompilerOptions; @@ -277,7 +67,7 @@ namespace compiler { private _inputs: documents.TextDocument[] = []; private _inputsAndOutputs: core.SortedMap; - constructor(host: CompilerHost, options: ts.CompilerOptions, program: ts.Program | undefined, result: ts.EmitResult | undefined, diagnostics: ts.Diagnostic[]) { + constructor(host: fakes.CompilerHost, options: ts.CompilerOptions, program: ts.Program | undefined, result: ts.EmitResult | undefined, diagnostics: ts.Diagnostic[]) { this.host = host; this.program = program; this.result = result; @@ -438,7 +228,7 @@ namespace compiler { } } - export function compileFiles(host: CompilerHost, rootFiles: string[] | undefined, compilerOptions: ts.CompilerOptions): CompilationResult { + export function compileFiles(host: fakes.CompilerHost, rootFiles: string[] | undefined, compilerOptions: ts.CompilerOptions): CompilationResult { if (compilerOptions.project || !rootFiles || rootFiles.length === 0) { const project = readProject(host.parseConfigHost, compilerOptions.project, compilerOptions); if (project) { diff --git a/src/harness/core/strings.ts b/src/harness/core/strings.ts index 9d1b270ae25..593ef23c83f 100644 --- a/src/harness/core/strings.ts +++ b/src/harness/core/strings.ts @@ -15,16 +15,12 @@ namespace core { } export function getByteOrderMarkLength(text: string): number { - if (text.length >= 2) { + if (text.length >= 1) { const ch0 = text.charCodeAt(0); - const ch1 = text.charCodeAt(1); - if ((ch0 === 0xff && ch1 === 0xfe) || - (ch0 === 0xfe && ch1 === 0xff)) { - return 2; - } - if (text.length >= 3 && ch0 === 0xef && ch1 === 0xbb && text.charCodeAt(2) === 0xbf) { - return 3; - } + if (ch0 === 0xfeff) return 1; + if (ch0 === 0xfe) return text.length >= 2 && text.charCodeAt(1) === 0xff ? 2 : 0; + if (ch0 === 0xff) return text.length >= 2 && text.charCodeAt(1) === 0xfe ? 2 : 0; + if (ch0 === 0xef) return text.length >= 3 && text.charCodeAt(1) === 0xbb && text.charCodeAt(2) === 0xbf ? 3 : 0; } return 0; } diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index f57f14cf007..abac54e4ea9 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -9,76 +9,63 @@ namespace fakes { const processExitSentinel = new Error("System exit"); - export interface ServerHostOptions { - /** - * The virtual path to tsc.js. If not specified, a default of `"/.ts/tsc.js"` is used. - */ + export interface SystemOptions { executingFilePath?: string; newLine?: "\r\n" | "\n"; - safeList?: boolean; - lib?: boolean; - dos?: boolean; + env?: Record; } - export class ServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost { + /** + * A fake `ts.System` that leverages a virtual file system. + */ + export class System implements ts.System { public readonly vfs: vfs.FileSystem; + public readonly args: string[] = []; + public readonly output: string[] = []; + public readonly newLine: string; + public readonly useCaseSensitiveFileNames: boolean; public exitCode: number; - private readonly _output: string[] = []; - private readonly _executingFilePath: string; - private readonly _getCanonicalFileName: (file: string) => string; - constructor(vfs: vfs.FileSystem, options: ServerHostOptions = {}) { - const { - dos = false, - executingFilePath = dos - ? vfsutils.dosTscPath - : vfsutils.tscPath, - newLine = "\n", - safeList = false, - lib = false, - } = options; + private readonly _executingFilePath: string | undefined; + private readonly _env: Record | undefined; + constructor(vfs: vfs.FileSystem, { executingFilePath, newLine = "\n", env }: SystemOptions = {}) { this.vfs = vfs.isReadonly ? vfs.shadow() : vfs; this.useCaseSensitiveFileNames = !this.vfs.ignoreCase; this.newLine = newLine; this._executingFilePath = executingFilePath; - this._getCanonicalFileName = ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames); - - if (safeList) { - const safelistPath = dos ? vfsutils.dosSafelistPath : vfsutils.safelistPath; - this.vfs.mkdirpSync(vpath.dirname(safelistPath)); - this.vfs.writeFileSync(safelistPath, vfsutils.safelistContent); - } - - if (lib) { - const libPath = dos ? vfsutils.dosLibPath : vfsutils.libPath; - this.vfs.mkdirpSync(vpath.dirname(libPath)); - this.vfs.writeFileSync(libPath, vfsutils.emptyLibContent); - } + this._env = env; } - // #region System members - public readonly newLine: string; - public readonly useCaseSensitiveFileNames: boolean; - public write(message: string) { - this._output.push(message); + this.output.push(message); } public readFile(path: string) { - return vfsutils.readFile(this.vfs, path); + try { + 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); + } + catch { + return undefined; + } } public writeFile(path: string, data: string, writeByteOrderMark?: boolean): void { - vfsutils.writeFile(this.vfs, path, data, writeByteOrderMark); + this.vfs.mkdirpSync(vpath.dirname(path)); + this.vfs.writeFileSync(path, writeByteOrderMark ? core.addUTF8ByteOrderMark(data) : data); } public fileExists(path: string) { - return vfsutils.fileExists(this.vfs, path); + const stats = this._getStats(path); + return stats ? stats.isFile() : false; } public directoryExists(path: string) { - return vfsutils.directoryExists(this.vfs, path); + const stats = this._getStats(path); + return stats ? stats.isDirectory() : false; } public createDirectory(path: string): void { @@ -90,12 +77,38 @@ namespace fakes { } public getDirectories(path: string) { - return vfsutils.getDirectories(this.vfs, path); + const result: string[] = []; + try { + for (const file of this.vfs.readdirSync(path)) { + if (this.vfs.statSync(vpath.combine(path, file)).isDirectory()) { + result.push(file); + } + } + } + catch { /*ignore*/ } + return result; } public readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.vfs.cwd(), depth, path => { - return vfsutils.getAccessibleFileSystemEntries(this.vfs, path); + 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); + } + } + catch { /*ignored*/ } + } + } + catch { /*ignored*/ } + return { files, directories }; }); } @@ -104,18 +117,9 @@ namespace fakes { throw processExitSentinel; } - public readonly args: string[] = []; - public getFileSize(path: string) { - return vfsutils.getFileSize(this.vfs, path); - } - - public watchFile(_path: string, _cb: ts.FileWatcherCallback): ts.FileWatcher { - return { close(): void { /*ignored*/ } }; - } - - public watchDirectory(_path: string, _cb: ts.DirectoryWatcherCallback, _recursive?: boolean): ts.FileWatcher { - return { close(): void { /*ignored*/ } }; + const stats = this._getStats(path); + return stats && stats.isFile() ? stats.size : 0; } public resolvePath(path: string) { @@ -123,11 +127,13 @@ namespace fakes { } public getExecutingFilePath() { + if (this._executingFilePath === undefined) return ts.notImplemented(); return this._executingFilePath; } public getModifiedTime(path: string) { - return vfsutils.getModifiedTime(this.vfs, path); + const stats = this._getStats(path); + return stats ? stats.mtime : undefined; } public createHash(data: string): string { @@ -143,38 +149,231 @@ namespace fakes { } } - public getEnvironmentVariable(_name: string): string | undefined { - return undefined; + public getEnvironmentVariable(name: string): string | undefined { + return this._env && this._env[name]; } - public setTimeout(_callback: (...args: any[]) => void, _timeout: number, ..._args: any[]) { - return ts.notImplemented(); + private _getStats(path: string) { + try { + return this.vfs.statSync(path); + } + catch { + return undefined; + } + } + } + + /** + * A fake `ts.ParseConfigHost` that leverages a virtual file system. + */ + export class ParseConfigHost implements ts.ParseConfigHost { + public readonly sys: System; + + constructor(sys: System | vfs.FileSystem) { + if (sys instanceof vfs.FileSystem) sys = new System(sys); + this.sys = sys; } - public clearTimeout(_timeoutId: any): void { - return ts.notImplemented(); - } - // #endregion System members - - // #region FormatDiagnosticsHost members - public getNewLine() { - return this.newLine; + public get vfs() { + return this.sys.vfs; } - public getCanonicalFileName(fileName: string) { - return this._getCanonicalFileName(fileName); - } - // #endregion FormatDiagnosticsHost members - - // #region ServerHost members - public setImmediate(_callback: (...args: any[]) => void, ..._args: any[]): any { - return ts.notImplemented(); + public get useCaseSensitiveFileNames() { + return this.sys.useCaseSensitiveFileNames; } - public clearImmediate(_timeoutId: any): void { - return ts.notImplemented(); + public fileExists(fileName: string): boolean { + return this.sys.fileExists(fileName); + } + + public directoryExists(directoryName: string): boolean { + return this.sys.directoryExists(directoryName); + } + + public readFile(path: string): string | undefined { + return this.sys.readFile(path); + } + + public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { + return this.sys.readDirectory(path, extensions, excludes, includes, depth); + } + } + + /** + * A fake `ts.CompilerHost` that leverages a virtual file system. + */ + export class CompilerHost implements ts.CompilerHost { + public readonly sys: System; + public readonly defaultLibLocation: string; + public readonly outputs: documents.TextDocument[] = []; + public readonly traces: string[] = []; + public readonly shouldAssertInvariants = !Harness.lightMode; + + private _setParentNodes: boolean; + private _sourceFiles: core.SortedMap; + 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" }); + this.sys = sys; + this.defaultLibLocation = sys.vfs.meta.get("defaultLibLocation") || ""; + this._newLine = ts.getNewLineCharacter(options, () => this.sys.newLine); + this._sourceFiles = new core.SortedMap({ comparer: sys.vfs.stringComparer, sort: "insertion" }); + this._setParentNodes = setParentNodes; + } + + public get vfs() { + return this.sys.vfs; + } + + public get parseConfigHost() { + return this._parseConfigHost || (this._parseConfigHost = new ParseConfigHost(this.sys)); + } + + public getCurrentDirectory(): string { + return this.sys.getCurrentDirectory(); + } + + public useCaseSensitiveFileNames(): boolean { + return this.sys.useCaseSensitiveFileNames; + } + + public getNewLine(): string { + return this._newLine; + } + + public getCanonicalFileName(fileName: string): string { + return this.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); + } + + public fileExists(fileName: string): boolean { + return this.sys.fileExists(fileName); + } + + public directoryExists(directoryName: string): boolean { + return this.sys.directoryExists(directoryName); + } + + public getDirectories(path: string): string[] { + return this.sys.getDirectories(path); + } + + public readFile(path: string): string | undefined { + return this.sys.readFile(path); + } + + public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) { + if (writeByteOrderMark) content = core.addUTF8ByteOrderMark(content); + this.sys.writeFile(fileName, content); + + const document = new documents.TextDocument(fileName, content); + document.meta.set("fileName", fileName); + this.vfs.filemeta(fileName).set("document", document); + const index = this.outputs.findIndex(output => this.vfs.stringComparer(document.file, output.file) === 0); + if (index < 0) { + this.outputs.push(document); + } + else { + this.outputs[index] = document; + } + } + + public trace(s: string): void { + this.traces.push(s); + } + + public realpath(path: string): string { + return this.sys.realpath(path); + } + + public getDefaultLibLocation(): string { + return vpath.resolve(this.getCurrentDirectory(), this.defaultLibLocation); + } + + public getDefaultLibFileName(options: ts.CompilerOptions): string { + // return vpath.resolve(this.getDefaultLibLocation(), ts.getDefaultLibFileName(options)); + + // TODO(rbuckton): This patches the baseline to replace lib.es5.d.ts with lib.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 + return vpath.resolve(this.getDefaultLibLocation(), getDefaultLibFileName(options)); + function getDefaultLibFileName(options: ts.CompilerOptions) { + switch (options.target) { + case ts.ScriptTarget.ESNext: + case ts.ScriptTarget.ES2017: + return "lib.es2017.d.ts"; + case ts.ScriptTarget.ES2016: + return "lib.es2016.d.ts"; + case ts.ScriptTarget.ES2015: + return "lib.es2015.d.ts"; + + default: + return "lib.d.ts"; + } + } + } + + public getSourceFile(fileName: string, languageVersion: number): ts.SourceFile | undefined { + const canonicalFileName = this.getCanonicalFileName(vpath.resolve(this.getCurrentDirectory(), fileName)); + const existing = this._sourceFiles.get(canonicalFileName); + if (existing) return existing; + + const content = this.readFile(canonicalFileName); + if (content === undefined) return undefined; + + // A virtual file system may shadow another existing virtual file system. This + // allows us to reuse a common virtual file system structure across multiple + // tests. If a virtual file is a shadow, it is likely that the file will be + // reused across multiple tests. In that case, we cache the SourceFile we parse + // so that it can be reused across multiple tests to avoid the cost of + // repeatedly parsing the same file over and over (such as lib.d.ts). + const cacheKey = this.vfs.shadowRoot && `SourceFile[languageVersion=${languageVersion},setParentNodes=${this._setParentNodes}]`; + if (cacheKey) { + const meta = this.vfs.filemeta(canonicalFileName); + const sourceFileFromMetadata = meta.get(cacheKey) as ts.SourceFile | undefined; + if (sourceFileFromMetadata) { + this._sourceFiles.set(canonicalFileName, sourceFileFromMetadata); + return sourceFileFromMetadata; + } + } + + const parsed = ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes || this.shouldAssertInvariants); + if (this.shouldAssertInvariants) { + Utils.assertInvariants(parsed, /*parent*/ undefined); + } + + this._sourceFiles.set(canonicalFileName, parsed); + + if (cacheKey) { + // store the cached source file on the unshadowed file with the same version. + const stats = this.vfs.statSync(canonicalFileName); + + let fs = this.vfs; + while (fs.shadowRoot) { + try { + const shadowRootStats = fs.shadowRoot.statSync(canonicalFileName); + if (shadowRootStats.dev !== stats.dev || + shadowRootStats.ino !== stats.ino || + shadowRootStats.mtimeMs !== stats.mtimeMs) { + break; + } + + fs = fs.shadowRoot; + } + catch { + break; + } + } + + if (fs !== this.vfs) { + fs.filemeta(canonicalFileName).set(cacheKey, parsed); + } + } + + return parsed; } - // #endregion ServerHost members } } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 9f6ba047bfa..c6ab417fb19 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -276,7 +276,7 @@ namespace FourSlash { if (configFileName) { const baseDir = ts.normalizePath(ts.getDirectoryPath(configFileName)); - const host = new compiler.ParseConfigHost(vfsutils.createFromMap(baseDir, /*ignoreCase*/ true, this.inputFiles)); + const host = new fakes.ParseConfigHost(vfsutils.createFromMap(baseDir, /*ignoreCase*/ true, this.inputFiles)); 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 710bf3fe068..034c5f6b228 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1248,7 +1248,7 @@ namespace Harness { } return compiler.compileFiles( - new compiler.CompilerHost( + new fakes.CompilerHost( vfsutils.createFromDocuments( useCaseSensitiveFileNames, inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile), diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 0dca198b8f4..b86dd7bc94e 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -117,8 +117,8 @@ namespace Harness.LanguageService { } export abstract class LanguageServiceAdapterHost { - public typesRegistry: ts.Map | undefined; public readonly vfs = new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot }); + public typesRegistry: ts.Map | undefined; private scriptInfos: core.SortedMap; constructor(protected cancellationToken = DefaultHostCancellationToken.instance, @@ -139,17 +139,6 @@ namespace Harness.LanguageService { fileNames.push(scriptInfo.fileName); } }); - // this.vfs.scanSync("/", "descendants-or-self", { - // accept: (path, stats) => { - // if (stats.isFile()) { - // const scriptInfo = this.vfs.filemeta(path).get("scriptInfo") as ScriptInfo; - // if (scriptInfo && scriptInfo.isRootFile) { - // fileNames.push(scriptInfo.fileName); - // } - // } - // return false; - // } - // }); return fileNames; } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 0253d2eaeac..0de316f02bd 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -72,17 +72,17 @@ namespace project { } } - class ProjectCompilerHost extends compiler.CompilerHost { + class ProjectCompilerHost extends fakes.CompilerHost { private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; private _projectParseConfigHost: ProjectParseConfigHost; - constructor(vfs: vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) { - super(vfs, compilerOptions); + constructor(sys: fakes.System | vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) { + super(sys, compilerOptions); this._testCase = testCase; } - public get parseConfigHost(): compiler.ParseConfigHost { - return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.vfs, this._testCase)); + public get parseConfigHost(): fakes.ParseConfigHost { + return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.sys, this._testCase)); } public getDefaultLibFileName(_options: ts.CompilerOptions) { @@ -90,11 +90,11 @@ namespace project { } } - class ProjectParseConfigHost extends compiler.ParseConfigHost { + class ProjectParseConfigHost extends fakes.ParseConfigHost { private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; - constructor(vfs: vfs.FileSystem, testCase: ProjectRunnerTestCase & ts.CompilerOptions) { - super(vfs); + constructor(sys: fakes.System, testCase: ProjectRunnerTestCase & ts.CompilerOptions) { + super(sys); this._testCase = testCase; } @@ -149,7 +149,7 @@ namespace project { if (configFileName) { const result = ts.readJsonConfigFile(configFileName, path => vfsutils.readFile(this.vfs, path)); configFileSourceFiles.push(result); - const configParseHost = new ProjectParseConfigHost(this.vfs, this.testCase); + const configParseHost = new ProjectParseConfigHost(new fakes.System(this.vfs), this.testCase); const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), this.compilerOptions); inputFiles = configParseResult.fileNames; this.compilerOptions = configParseResult.options; diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index ecfae90bf49..a217911b9c3 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -115,10 +115,10 @@ namespace ts { } const caseInsensitiveBasePath = "c:/dev/"; - const caseInsensitiveHost = new compiler.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/")); + const caseInsensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/")); const caseSensitiveBasePath = "/dev/"; - const caseSensitiveHost = new compiler.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/")); + const caseSensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/")); function verifyDiagnostics(actual: Diagnostic[], expected: {code: number, category: DiagnosticCategory, messageText: string}[]) { assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); @@ -132,7 +132,7 @@ namespace ts { } describe("configurationExtension", () => { - forEach<[string, string, compiler.ParseConfigHost], void>([ + forEach<[string, string, fakes.ParseConfigHost], void>([ ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] ], ([testName, basePath, host]) => { diff --git a/src/harness/unittests/convertCompilerOptionsFromJson.ts b/src/harness/unittests/convertCompilerOptionsFromJson.ts index 7a7409a1057..951136b7575 100644 --- a/src/harness/unittests/convertCompilerOptionsFromJson.ts +++ b/src/harness/unittests/convertCompilerOptionsFromJson.ts @@ -33,7 +33,7 @@ namespace ts { const result = parseJsonText(configFileName, fileText); assert(!result.parseDiagnostics.length); assert(!!result.endOfFileToken); - const host: ParseConfigHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); expectedResult.compilerOptions.configFilePath = configFileName; diff --git a/src/harness/unittests/convertTypeAcquisitionFromJson.ts b/src/harness/unittests/convertTypeAcquisitionFromJson.ts index f3844ac08fe..4b43d102756 100644 --- a/src/harness/unittests/convertTypeAcquisitionFromJson.ts +++ b/src/harness/unittests/convertTypeAcquisitionFromJson.ts @@ -45,7 +45,7 @@ namespace ts { const result = parseJsonText(configFileName, fileText); assert(!result.parseDiagnostics.length); assert(!!result.endOfFileToken); - const host: ParseConfigHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); verifyAcquisition(actualTypeAcquisition, expectedResult); diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts index f5598d4fd1f..96a387b7fcb 100644 --- a/src/harness/unittests/extractTestHelpers.ts +++ b/src/harness/unittests/extractTestHelpers.ts @@ -1,6 +1,5 @@ /// /// -/// namespace ts { interface Range { @@ -111,7 +110,7 @@ namespace ts { function runBaseline(extension: Extension) { const path = "/a" + extension; - const program = makeProgram(path, t.source, includeLib); + const program = makeProgram({ path, content: t.source }, includeLib); if (hasSyntacticDiagnostics(program)) { // Don't bother generating JS baselines for inputs that aren't valid JS. @@ -147,19 +146,17 @@ namespace ts { const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation); data.push(newTextWithRename); - const diagProgram = makeProgram(path, newText, includeLib); + const diagProgram = makeProgram({ path, content: newText }, includeLib); assert.isFalse(hasSyntacticDiagnostics(diagProgram)); } return data.join(newLineCharacter); }); } - function makeProgram(path: string, content: string, includeLib?: boolean) { - // libFile is expensive to parse repeatedly - only test when required - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { files: { [path]: content } }); - const host = new fakes.ServerHost(fs, { lib: includeLib }); + 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(path); + projectService.openClientFile(f.path); const program = projectService.inferredProjects[0].getLanguageService().getProgram(); return program; } @@ -177,12 +174,15 @@ namespace ts { if (!selectionRange) { throw new Error(`Test ${caption} does not specify selection range`); } - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { files: { "/a.ts": t.source } }); - const host = new fakes.ServerHost(fs, { lib: true }); + const f = { + path: "/a.ts", + content: t.source + }; + const host = projectSystem.createServerHost([f, projectSystem.libFile]); const projectService = projectSystem.createProjectService(host); - projectService.openClientFile("/a.ts"); + projectService.openClientFile(f.path); const program = projectService.inferredProjects[0].getLanguageService().getProgram(); - const sourceFile = program.getSourceFile("/a.ts"); + const sourceFile = program.getSourceFile(f.path); const context: RefactorContext = { cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, program, diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index f24d410637d..4662dd9f536 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -5,7 +5,7 @@ namespace ts { const caseInsensitiveBasePath = "c:/dev/"; const caseInsensitiveTsconfigPath = "c:/dev/tsconfig.json"; - const caseInsensitiveHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + const caseInsensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { "c:/dev/a.ts": "", "c:/dev/a.d.ts": "", "c:/dev/a.js": "", @@ -32,7 +32,7 @@ namespace ts { }})); const caseSensitiveBasePath = "/dev/"; - const caseSensitiveHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { + const caseSensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { "/dev/a.ts": "", "/dev/a.d.ts": "", "/dev/a.js": "", @@ -56,7 +56,7 @@ namespace ts { "/dev/js/b.js": "", }})); - const caseInsensitiveMixedExtensionHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + const caseInsensitiveMixedExtensionHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { "c:/dev/a.ts": "", "c:/dev/a.d.ts": "", "c:/dev/a.js": "", @@ -70,7 +70,7 @@ namespace ts { "c:/dev/f.other": "", }})); - const caseInsensitiveCommonFoldersHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + const caseInsensitiveCommonFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { "c:/dev/a.ts": "", "c:/dev/a.d.ts": "", "c:/dev/a.js": "", @@ -81,7 +81,7 @@ namespace ts { "c:/dev/jspm_packages/a.ts": "", }})); - const caseInsensitiveDottedFoldersHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + const caseInsensitiveDottedFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { "c:/dev/x/d.ts": "", "c:/dev/x/y/d.ts": "", "c:/dev/x/y/.e.ts": "", @@ -92,13 +92,13 @@ namespace ts { "c:/dev/g.min.js/.g/g.ts": "", }})); - const caseInsensitiveOrderingDiffersWithCaseHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + const caseInsensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { "c:/dev/xylophone.ts": "", "c:/dev/Yosemite.ts": "", "c:/dev/zebra.ts": "", }})); - const caseSensitiveOrderingDiffersWithCaseHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { + const caseSensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { "/dev/xylophone.ts": "", "/dev/Yosemite.ts": "", "/dev/zebra.ts": "", diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts index b58c47e17b6..b65b14af964 100644 --- a/src/harness/unittests/programMissingFiles.ts +++ b/src/harness/unittests/programMissingFiles.ts @@ -36,7 +36,7 @@ namespace ts { "/// \n" // No extension ); - const testCompilerHost = new compiler.CompilerHost( + const testCompilerHost = new fakes.CompilerHost( vfsutils.createFromDocuments( /*useCaseSensitiveFileNames*/ false, [emptyFile, referenceFile], diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index 5b61986058a..500204fbde8 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -34,14 +34,14 @@ namespace ts { function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { const parsed = parseConfigFileTextToJson(configFileName, jsonText); const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); - const host: ParseConfigHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); return parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName); } function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { const parsed = parseJsonText(configFileName, jsonText); const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); - const host: ParseConfigHost = new compiler.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); return parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); }