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