diff --git a/src/harness/compiler.ts b/src/harness/compiler.ts index 680f1f60978..24bd14ecfe9 100644 --- a/src/harness/compiler.ts +++ b/src/harness/compiler.ts @@ -227,14 +227,22 @@ namespace compiler { public readonly program: ts.Program | undefined; public readonly result: ts.EmitResult | undefined; public readonly options: ts.CompilerOptions; - public readonly diagnostics: ts.Diagnostic[]; - public readonly js: core.KeyedCollection; - public readonly dts: core.KeyedCollection; - public readonly maps: core.KeyedCollection; + public readonly diagnostics: ReadonlyArray; + public readonly js: core.ReadonlyKeyedCollection; + public readonly dts: core.ReadonlyKeyedCollection; + public readonly maps: core.ReadonlyKeyedCollection; private _inputs: documents.TextDocument[] = []; private _inputsAndOutputs: core.KeyedCollection; + // from CompilerResult + public readonly files: ReadonlyArray; + public readonly declFilesCode: ReadonlyArray; + public readonly sourceMaps: ReadonlyArray; + public readonly errors: ReadonlyArray; + public readonly currentDirectoryForProgram: string; + public readonly traceResults: ReadonlyArray; + constructor(host: CompilerHost, options: ts.CompilerOptions, program: ts.Program | undefined, result: ts.EmitResult | undefined, diagnostics: ts.Diagnostic[]) { this.host = host; this.program = program; @@ -243,18 +251,18 @@ namespace compiler { this.options = program ? program.getCompilerOptions() : options; // collect outputs - this.js = new core.KeyedCollection(this.vfs.pathComparer); - this.dts = new core.KeyedCollection(this.vfs.pathComparer); - this.maps = new core.KeyedCollection(this.vfs.pathComparer); + const js = this.js = new core.KeyedCollection(this.vfs.pathComparer); + const dts = this.dts = new core.KeyedCollection(this.vfs.pathComparer); + const maps = this.maps = new core.KeyedCollection(this.vfs.pathComparer); for (const document of this.host.outputs) { if (vpath.isJavaScript(document.file)) { - this.js.set(document.file, document); + js.set(document.file, document); } else if (vpath.isDeclaration(document.file)) { - this.dts.set(document.file, document); + dts.set(document.file, document); } else if (vpath.isSourceMap(document.file)) { - this.maps.set(document.file, document); + maps.set(document.file, document); } } @@ -268,9 +276,9 @@ namespace compiler { if (!vpath.isDeclaration(sourceFile.fileName)) { const outputs = { input, - js: this.js.get(this.getOutputPath(sourceFile.fileName, ts.getOutputExtension(sourceFile, this.options))), - dts: this.dts.get(this.getOutputPath(sourceFile.fileName, ".d.ts", this.options.declarationDir)), - map: this.maps.get(this.getOutputPath(sourceFile.fileName, ts.getOutputExtension(sourceFile, this.options) + ".map")) + js: js.get(this.getOutputPath(sourceFile.fileName, ts.getOutputExtension(sourceFile, this.options))), + dts: dts.get(this.getOutputPath(sourceFile.fileName, ".d.ts", this.options.declarationDir)), + map: maps.get(this.getOutputPath(sourceFile.fileName, ts.getOutputExtension(sourceFile, this.options) + ".map")) }; this._inputsAndOutputs.set(sourceFile.fileName, outputs); @@ -281,9 +289,17 @@ namespace compiler { } } } + + // from CompilerResult + this.files = Array.from(this.js.values(), file => file.asGeneratedFile()); + this.declFilesCode = Array.from(this.dts.values(), file => file.asGeneratedFile()); + this.sourceMaps = Array.from(this.maps.values(), file => file.asGeneratedFile()); + this.errors = diagnostics; + this.currentDirectoryForProgram = host.vfs.currentDirectory; + this.traceResults = host.traces; } - public get vfs() { + public get vfs(): vfs.VirtualFileSystem { return this.host.vfs; } @@ -326,6 +342,12 @@ namespace compiler { return outputs && outputs[kind]; } + public getSourceMapRecord(): string | undefined { + if (this.result.sourceMaps && this.result.sourceMaps.length > 0) { + return Harness.SourceMapRecorder.getSourceMapRecord(this.result.sourceMaps, this.program, this.files); + } + } + public getSourceMap(path: string): documents.SourceMap | undefined { if (this.options.noEmit || vpath.isDeclaration(path)) return undefined; if (this.options.inlineSourceMap) { @@ -338,7 +360,7 @@ namespace compiler { } } - public getOutputPath(path: string, ext: string, outDir: string | undefined = this.options.outDir) { + public getOutputPath(path: string, ext: string, outDir: string | undefined = this.options.outDir): string { if (outDir) { path = vpath.resolve(this.vfs.currentDirectory, path); const common = this.commonSourceDirectory; @@ -352,8 +374,7 @@ namespace compiler { } } - export function compileFiles(host: CompilerHost, rootFiles: string[] | undefined, compilerOptions: ts.CompilerOptions) { - // establish defaults (aligns with old harness) + export function compileFiles(host: 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) { @@ -368,6 +389,7 @@ namespace compiler { delete compilerOptions.project; } + // establish defaults (aligns with old harness) if (compilerOptions.target === undefined) compilerOptions.target = ts.ScriptTarget.ES3; if (compilerOptions.newLine === undefined) compilerOptions.newLine = ts.NewLineKind.CarriageReturnLineFeed; if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true; diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index 5eb78f746df..6d0ce05083e 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -108,7 +108,7 @@ class CompilerTest { private lastUnit: Harness.TestCaseParser.TestUnitData; private harnessSettings: Harness.TestCaseParser.CompilerSettings; private hasNonDtsFiles: boolean; - private result: Harness.Compiler.CompilerResult; + private result: compiler.CompilationResult; private options: ts.CompilerOptions; private tsConfigFiles: Harness.Compiler.TestFile[]; // equivalent to the files that will be passed on the command line diff --git a/src/harness/core.ts b/src/harness/core.ts index 9efe53c4d8a..259e05d6765 100644 --- a/src/harness/core.ts +++ b/src/harness/core.ts @@ -1,5 +1,3 @@ -/// - // NOTE: The contents of this file are all exported from the namespace 'core'. This is to // support the eventual conversion of harness into a modular system. @@ -63,10 +61,20 @@ namespace core { // Collections // + export interface ReadonlyKeyedCollection { + readonly size: number; + has(key: K): boolean; + get(key: K): V | undefined; + forEach(callback: (value: V, key: K, collection: this) => void): void; + keys(): IterableIterator; + values(): IterableIterator; + entries(): IterableIterator<[K, V]>; + } + /** * A collection of key/value pairs internally sorted by key. */ - export class KeyedCollection { + export class KeyedCollection implements ReadonlyKeyedCollection { private _comparer: (a: K, b: K) => number; private _keys: K[] = []; private _values: V[] = []; @@ -101,7 +109,7 @@ namespace core { insertAt(this._keys, ~index, key); insertAt(this._values, ~index, value); insertAt(this._order, ~index, this._version); - this._version++; + this.writePostScript(); } return this; } @@ -113,7 +121,7 @@ namespace core { removeAt(this._keys, index); removeAt(this._values, index); removeAt(this._order, index); - this._version++; + this.writePostScript(); return true; } return false; @@ -125,7 +133,7 @@ namespace core { this._keys.length = 0; this._values.length = 0; this._order.length = 0; - this._version = 0; + this.writePostScript(); } } @@ -143,6 +151,46 @@ namespace core { } } + public * keys() { + const keys = this._keys; + const order = this.getInsertionOrder(); + const version = this._version; + this._copyOnWrite = true; + for (const index of order) { + yield keys[index]; + } + if (version === this._version) { + this._copyOnWrite = false; + } + } + + public * values() { + const values = this._values; + const order = this.getInsertionOrder(); + const version = this._version; + this._copyOnWrite = true; + for (const index of order) { + yield values[index]; + } + if (version === this._version) { + this._copyOnWrite = false; + } + } + + public * entries() { + const keys = this._keys; + const values = this._values; + const order = this.getInsertionOrder(); + const version = this._version; + this._copyOnWrite = true; + for (const index of order) { + yield [keys[index], values[index]] as [K, V]; + } + if (version === this._version) { + this._copyOnWrite = false; + } + } + private writePreamble() { if (this._copyOnWrite) { this._keys = this._keys.slice(); @@ -152,10 +200,14 @@ namespace core { } } + private writePostScript() { + this._version++; + } + private getInsertionOrder() { return this._order .map((_, i) => i) - .sort((x, y) => compareNumbers(this._order[x], this._order[y])); + .sort((x, y) => this._order[x] - this._order[y]); } } @@ -325,6 +377,10 @@ namespace core { return length ? text.slice(length) : text; } + export function addUTF8ByteOrderMark(text: string) { + return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text; + } + function splitLinesWorker(text: string, lineStarts: number[] | undefined, lines: string[] | undefined, removeEmptyElements: boolean) { let pos = 0; let end = 0; diff --git a/src/harness/documents.ts b/src/harness/documents.ts index a5df3a6af52..9857716ad41 100644 --- a/src/harness/documents.ts +++ b/src/harness/documents.ts @@ -10,6 +10,8 @@ namespace documents { public readonly text: string; private _lineStarts: core.LineStarts | undefined; + private _testFile: Harness.Compiler.TestFile | undefined; + private _generatedFile: Harness.Compiler.GeneratedFile | undefined; constructor(file: string, text: string, meta?: Map) { this.file = file; @@ -20,6 +22,37 @@ namespace documents { public get lineStarts(): core.LineStarts { return this._lineStarts || (this._lineStarts = core.computeLineStarts(this.text)); } + + public static fromTestFile(file: Harness.Compiler.TestFile) { + return new TextDocument( + file.unitName, + file.content, + file.fileOptions && Object.keys(file.fileOptions) + .reduce((meta, key) => meta.set(key, file.fileOptions[key]), new Map())); + } + + public asTestFile() { + return this._testFile || (this._testFile = { + unitName: this.file, + content: this.text, + fileOptions: Array.from(this.meta) + .reduce((obj, [key, value]) => (obj[key] = value, obj), {} as Record) + }); + } + + public static fromGeneratedFile(file: Harness.Compiler.GeneratedFile) { + return new TextDocument( + file.fileName, + file.writeByteOrderMark ? core.addUTF8ByteOrderMark(file.code) : file.code); + } + + public asGeneratedFile() { + return this._generatedFile || (this._generatedFile = { + fileName: this.file, + code: core.removeByteOrderMark(this.text), + writeByteOrderMark: core.getByteOrderMarkLength(this.text) > 0 + }); + } } export interface RawSourceMap { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 1d17c784783..88fcaa329e7 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -482,7 +482,7 @@ namespace Utils { } namespace Harness { - // tslint:disable-next-line:interface-name + // tslint:disable-next-line:interface-name export interface IO { newLine(): string; getCurrentDirectory(): string; @@ -1163,7 +1163,7 @@ namespace Harness { } export interface CompilationOutput { - result: CompilerResult; + result: compiler.CompilationResult; options: ts.CompilerOptions & HarnessOptions; } @@ -1208,29 +1208,24 @@ namespace Harness { } } - const compilation = compiler.compileFiles( + const result = compiler.compileFiles( new compiler.CompilerHost( - vfs.VirtualFileSystem.createFromTestFiles( - { useCaseSensitiveFileNames, currentDirectory }, - inputFiles.concat(otherFiles), - { overwrite: true } + vfs.VirtualFileSystem.createFromDocuments( + useCaseSensitiveFileNames, + inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile), + { currentDirectory, overwrite: true } ), options ), programFileNames, options); - const fileOutputs = compilation.outputs ? compilation.outputs.map(output => ({ - fileName: output.file, - code: core.removeByteOrderMark(output.text), - writeByteOrderMark: core.getByteOrderMarkLength(output.text) > 0 - })) : []; - - const traceResults = compilation.traces && compilation.traces.slice(); - const program = compilation.program; - const emitResult = compilation.result; - const errors = compilation.diagnostics; - const result = new CompilerResult(fileOutputs, errors, program, compilation.vfs.currentDirectory, emitResult.sourceMaps, traceResults); + // const fileOutputs = compilation.outputs.map(output => output.asGeneratedFile()); + // const traceResults = compilation.traces && compilation.traces.slice(); + // const program = compilation.program; + // const emitResult = compilation.result; + // const errors = compilation.diagnostics; + // const result = new CompilerResult(fileOutputs, errors, program, compilation.vfs.currentDirectory, emitResult.sourceMaps, traceResults); return { result, options }; } @@ -1242,9 +1237,9 @@ namespace Harness { currentDirectory: string; } - export function prepareDeclarationCompilationContext(inputFiles: TestFile[], - otherFiles: TestFile[], - result: CompilerResult, + export function prepareDeclarationCompilationContext(inputFiles: ReadonlyArray, + otherFiles: ReadonlyArray, + result: compiler.CompilationResult, harnessSettings: TestCaseParser.CompilerSettings & HarnessOptions, options: ts.CompilerOptions, // Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file @@ -1264,10 +1259,10 @@ namespace Harness { } function addDtsFile(file: TestFile, dtsFiles: TestFile[]) { - if (isDTS(file.unitName)) { + if (vpath.isDeclaration(file.unitName)) { dtsFiles.push(file); } - else if (isTS(file.unitName)) { + else if (vpath.isTypeScript(file.unitName)) { const declFile = findResultCodeFile(file.unitName); if (declFile && !findUnit(declFile.fileName, declInputFiles) && !findUnit(declFile.fileName, declOtherFiles)) { dtsFiles.push({ unitName: declFile.fileName, content: declFile.code }); @@ -1465,7 +1460,7 @@ namespace Harness { assert.equal(totalErrorsReportedInNonLibraryFiles + numLibraryDiagnostics + numTest262HarnessDiagnostics, diagnostics.length, "total number of errors"); } - export function doErrorBaseline(baselinePath: string, inputFiles: TestFile[], errors: ts.Diagnostic[], pretty?: boolean) { + export function doErrorBaseline(baselinePath: string, inputFiles: ReadonlyArray, errors: ReadonlyArray, pretty?: boolean) { Harness.Baseline.runBaseline(baselinePath.replace(/\.tsx?$/, ".errors.txt"), (): string => { if (!errors || (errors.length === 0)) { /* tslint:disable:no-null-keyword */ @@ -1611,7 +1606,7 @@ namespace Harness { return file.writeByteOrderMark ? "\u00EF\u00BB\u00BF" : ""; } - export function doSourcemapBaseline(baselinePath: string, options: ts.CompilerOptions, result: CompilerResult, harnessSettings: Harness.TestCaseParser.CompilerSettings) { + export function doSourcemapBaseline(baselinePath: string, options: ts.CompilerOptions, result: compiler.CompilationResult, harnessSettings: Harness.TestCaseParser.CompilerSettings) { if (options.inlineSourceMap) { if (result.sourceMaps.length > 0) { throw new Error("No sourcemap files should be generated if inlineSourceMaps was set."); @@ -1642,7 +1637,7 @@ namespace Harness { } } - export function doJsEmitBaseline(baselinePath: string, header: string, options: ts.CompilerOptions, result: CompilerResult, tsConfigFiles: Harness.Compiler.TestFile[], toBeCompiled: Harness.Compiler.TestFile[], otherFiles: Harness.Compiler.TestFile[], harnessSettings: Harness.TestCaseParser.CompilerSettings) { + export function doJsEmitBaseline(baselinePath: string, header: string, options: ts.CompilerOptions, result: compiler.CompilationResult, tsConfigFiles: ReadonlyArray, toBeCompiled: ReadonlyArray, otherFiles: ReadonlyArray, harnessSettings: Harness.TestCaseParser.CompilerSettings) { if (!options.noEmit && result.files.length === 0 && result.errors.length === 0) { throw new Error("Expected at least one js file to be emitted or at least one error to be created."); } @@ -1698,7 +1693,7 @@ namespace Harness { return "//// [" + fileName + "]\r\n" + getByteOrderMarkText(file) + utils.removeTestPathPrefixes(file.code); } - export function collateOutputs(outputFiles: Harness.Compiler.GeneratedFile[]): string { + export function collateOutputs(outputFiles: ReadonlyArray): string { const gen = iterateOutputs(outputFiles); // Emit them let result = ""; @@ -1714,9 +1709,9 @@ namespace Harness { return result; } - export function *iterateOutputs(outputFiles: Harness.Compiler.GeneratedFile[]): IterableIterator<[string, string]> { + export function *iterateOutputs(outputFiles: ReadonlyArray): IterableIterator<[string, string]> { // Collect, test, and sort the fileNames - outputFiles.sort((a, b) => ts.compareStringsCaseSensitive(cleanName(a.fileName), cleanName(b.fileName))); + outputFiles.slice().sort((a, b) => ts.compareStringsCaseSensitive(cleanName(a.fileName), cleanName(b.fileName))); const dupeCase = ts.createMap(); // Yield them for (const outputFile of outputFiles) { @@ -1751,78 +1746,11 @@ namespace Harness { return path; } - // This does not need to exist strictly speaking, but many tests will need to be updated if it's removed - export function compileString(_code: string, _unitName: string, _callback: (result: CompilerResult) => void) { - // NEWTODO: Re-implement 'compileString' - return ts.notImplemented(); - } - export interface GeneratedFile { fileName: string; code: string; writeByteOrderMark: boolean; } - - export function isTS(fileName: string) { - return ts.endsWith(fileName, ts.Extension.Ts); - } - - export function isTSX(fileName: string) { - return ts.endsWith(fileName, ts.Extension.Tsx); - } - - export function isDTS(fileName: string) { - return ts.endsWith(fileName, ts.Extension.Dts); - } - - export function isJS(fileName: string) { - return ts.endsWith(fileName, ts.Extension.Js); - } - export function isJSX(fileName: string) { - return ts.endsWith(fileName, ts.Extension.Jsx); - } - - export function isJSMap(fileName: string) { - return ts.endsWith(fileName, ".js.map") || ts.endsWith(fileName, ".jsx.map"); - } - - /** Contains the code and errors of a compilation and some helper methods to check its status. */ - export class CompilerResult { - public files: GeneratedFile[] = []; - public errors: ts.Diagnostic[] = []; - public declFilesCode: GeneratedFile[] = []; - public sourceMaps: GeneratedFile[] = []; - - /** @param fileResults an array of strings for the fileName and an ITextWriter with its code */ - constructor(fileResults: GeneratedFile[], errors: ts.Diagnostic[], public program: ts.Program, - public currentDirectoryForProgram: string, private sourceMapData: ts.SourceMapData[], public traceResults: string[]) { - - for (const emittedFile of fileResults) { - if (isDTS(emittedFile.fileName)) { - // .d.ts file, add to declFiles emit - this.declFilesCode.push(emittedFile); - } - else if (isJS(emittedFile.fileName) || isJSX(emittedFile.fileName)) { - // .js file, add to files - this.files.push(emittedFile); - } - else if (isJSMap(emittedFile.fileName)) { - this.sourceMaps.push(emittedFile); - } - else { - throw new Error("Unrecognized file extension for file " + emittedFile.fileName); - } - } - - this.errors = errors; - } - - public getSourceMapRecord() { - if (this.sourceMapData && this.sourceMapData.length > 0) { - return Harness.SourceMapRecorder.getSourceMapRecord(this.sourceMapData, this.program, this.files); - } - } - } } export namespace TestCaseParser { diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index c617cc7a0a3..73098ba3507 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -1,5 +1,6 @@ -/// -/// +/// +/// +/// // Test case is json of below type in tests/cases/project/ interface ProjectRunnerTestCase { @@ -319,19 +320,19 @@ class ProjectRunner extends RunnerBase { // we need to instead create files that can live in the project reference folder // but make sure extension of these files matches with the fileName the compiler asked to write diskRelativeName = "diskFile" + nonSubfolderDiskFiles + - (Harness.Compiler.isDTS(fileName) ? ts.Extension.Dts : - Harness.Compiler.isJS(fileName) ? ts.Extension.Js : ".js.map"); + (vpath.isDeclaration(fileName) ? ts.Extension.Dts : + vpath.isJavaScript(fileName) ? ts.Extension.Js : ".js.map"); nonSubfolderDiskFiles++; } - if (Harness.Compiler.isJS(fileName)) { + if (vpath.isJavaScript(fileName)) { // Make sure if there is URl we have it cleaned up const indexOfSourceMapUrl = data.lastIndexOf(`//# ${"sourceMappingURL"}=`); // This line can be seen as a sourceMappingURL comment if (indexOfSourceMapUrl !== -1) { data = data.substring(0, indexOfSourceMapUrl + 21) + cleanProjectUrl(data.substring(indexOfSourceMapUrl + 21)); } } - else if (Harness.Compiler.isJSMap(fileName)) { + else if (vpath.isJavaScriptSourceMap(fileName)) { // Make sure sources list is cleaned const sourceMapData = JSON.parse(data); for (let i = 0; i < sourceMapData.sources.length; i++) { diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 31985529f90..16beb92a547 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -30,7 +30,7 @@ namespace RWC { let inputFiles: Harness.Compiler.TestFile[] = []; let otherFiles: Harness.Compiler.TestFile[] = []; let tsconfigFiles: Harness.Compiler.TestFile[] = []; - let compilerResult: Harness.Compiler.CompilerResult; + let compilerResult: compiler.CompilationResult; let compilerOptions: ts.CompilerOptions; const baselineOpts: Harness.Baseline.BaselineOptions = { Subfolder: "rwc", diff --git a/src/harness/sourceMapRecorder.ts b/src/harness/sourceMapRecorder.ts index 606d4e67acd..330ac1743ac 100644 --- a/src/harness/sourceMapRecorder.ts +++ b/src/harness/sourceMapRecorder.ts @@ -434,7 +434,7 @@ namespace Harness.SourceMapRecorder { } } - export function getSourceMapRecord(sourceMapDataList: ts.SourceMapData[], program: ts.Program, jsFiles: Compiler.GeneratedFile[]) { + export function getSourceMapRecord(sourceMapDataList: ReadonlyArray, program: ts.Program, jsFiles: ReadonlyArray) { const sourceMapRecorder = new Compiler.WriterAggregator(); for (let i = 0; i < sourceMapDataList.length; i++) { diff --git a/src/harness/test262Runner.ts b/src/harness/test262Runner.ts index 6c5b186f2b8..0d32d2f2513 100644 --- a/src/harness/test262Runner.ts +++ b/src/harness/test262Runner.ts @@ -31,7 +31,7 @@ class Test262BaselineRunner extends RunnerBase { // Everything declared here should be cleared out in the "after" callback. let testState: { filename: string; - compilerResult: Harness.Compiler.CompilerResult; + compilerResult: compiler.CompilationResult; inputFiles: Harness.Compiler.TestFile[]; }; diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts index 8c042f2afbc..f78bf134389 100644 --- a/src/harness/unittests/programMissingFiles.ts +++ b/src/harness/unittests/programMissingFiles.ts @@ -1,4 +1,5 @@ /// +/// /// namespace ts { @@ -23,27 +24,23 @@ namespace ts { const emptyFileName = "empty.ts"; const emptyFileRelativePath = "./" + emptyFileName; - const emptyFile: Harness.Compiler.TestFile = { - unitName: emptyFileName, - content: "" - }; + const emptyFile = new documents.TextDocument(emptyFileName, ""); const referenceFileName = "reference.ts"; const referenceFileRelativePath = "./" + referenceFileName; - const referenceFile: Harness.Compiler.TestFile = { - unitName: referenceFileName, - content: - "/// \n" + // Absolute - "/// \n" + // Relative - "/// \n" + // Unqualified - "/// \n" // No extension - }; + const referenceFile = new documents.TextDocument(referenceFileName, + "/// \n" + // Absolute + "/// \n" + // Relative + "/// \n" + // Unqualified + "/// \n" // No extension + ); const testCompilerHost = new compiler.CompilerHost( - vfs.VirtualFileSystem.createFromTestFiles( - { useCaseSensitiveFileNames: false, currentDirectory: "d:\\pretend\\" }, - [emptyFile, referenceFile]), + vfs.VirtualFileSystem.createFromDocuments( + /*useCaseSensitiveFileNames*/ false, + [emptyFile, referenceFile], + { currentDirectory: "d:\\pretend\\" }), { newLine: NewLineKind.LineFeed }); it("handles no missing root files", () => { diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts index da4f4ae190c..7d31ae3df96 100644 --- a/src/harness/vfs.ts +++ b/src/harness/vfs.ts @@ -1,8 +1,9 @@ /// /// /// -/// /// +/// +/// // NOTE: The contents of this file are all exported from the namespace 'vfs'. This is to // support the eventual conversion of harness into a modular system. @@ -190,7 +191,7 @@ namespace vfs { } /** - * Gets a virtual file system with the following directories: + * Gets a read-only virtual file system with the following directories: * * | path | physical/virtual | * |:-------|:----------------------| @@ -198,7 +199,7 @@ namespace vfs { * | /.lib | physical: tests/lib | * | /.src | virtual | */ - public static getBuiltLocal(useCaseSensitiveFileNames: boolean = Harness.IO.useCaseSensitiveFileNames()): VirtualFileSystem { + public static getBuiltLocal(useCaseSensitiveFileNames: boolean): VirtualFileSystem { let vfs = useCaseSensitiveFileNames ? this._builtLocalCS : this._builtLocalCI; if (!vfs) { vfs = this._builtLocal; @@ -228,27 +229,24 @@ namespace vfs { return vfs; } - public static createFromOptions(options: { useCaseSensitiveFileNames?: boolean, currentDirectory?: string }) { - const vfs = this.getBuiltLocal(options.useCaseSensitiveFileNames).shadow(); - if (options.currentDirectory) { + public static createFromDocuments(useCaseSensitiveFileNames: boolean, documents: documents.TextDocument[], options?: { currentDirectory?: string, overwrite?: boolean }) { + const vfs = this.getBuiltLocal(useCaseSensitiveFileNames).shadow(); + if (options && options.currentDirectory) { vfs.addDirectory(options.currentDirectory); vfs.changeDirectory(options.currentDirectory); } - return vfs; - } - public static createFromTestFiles(options: { useCaseSensitiveFileNames?: boolean, currentDirectory?: string }, documents: Harness.Compiler.TestFile[], fileOptions?: { overwrite?: boolean }) { - const vfs = this.createFromOptions(options); + const fileOptions = options && options.overwrite ? { overwrite: true } : undefined; for (const document of documents) { - const file = vfs.addFile(document.unitName, document.content, fileOptions)!; - assert.isDefined(file, `Failed to add file: '${document.unitName}'`); + const file = vfs.addFile(document.file, document.text, fileOptions)!; + assert.isDefined(file, `Failed to add file: '${document.file}'`); file.metadata.set("document", document); // Add symlinks - const symlink = document.fileOptions && document.fileOptions.symlink; + const symlink = document.meta.get("symlink"); if (file && symlink) { for (const link of symlink.split(",")) { const symlink = vfs.addSymlink(vpath.resolve(vfs.currentDirectory, link.trim()), file)!; - assert.isDefined(symlink, `Failed to symlink: '${link}'`); + assert.isDefined(symlink, `Failed to create symlink: '${link}'`); symlink.metadata.set("document", document); } } diff --git a/src/harness/vpath.ts b/src/harness/vpath.ts index 9ad081ac1f5..c352b028c3f 100644 --- a/src/harness/vpath.ts +++ b/src/harness/vpath.ts @@ -59,7 +59,7 @@ namespace vpath { return hasTrailingSeparator(path) ? path.slice(0, -1) : path; } - function reduce(components: string[]) { + function reduce(components: ReadonlyArray) { const normalized = [components[0]]; for (let i = 1; i < components.length; i++) { const component = components[i]; @@ -242,7 +242,7 @@ namespace vpath { /** * Formats a parsed path consisting of a root component and zero or more path segments. */ - export function format(components: string[]) { + export function format(components: ReadonlyArray) { return components.length ? components[0] + components.slice(1).join(sep) : ""; } @@ -262,31 +262,33 @@ namespace vpath { * Gets the portion of a path following the last separator (`/`). * If the base name has any one of the provided extensions, it is removed. */ - export function basename(path: string, extensions: string | string[], ignoreCase: boolean): string; - export function basename(path: string, extensions?: string | string[], ignoreCase?: boolean) { + export function basename(path: string, extensions: string | ReadonlyArray, ignoreCase: boolean): string; + export function basename(path: string, extensions?: string | ReadonlyArray, ignoreCase?: boolean) { path = normalizeSeparators(path); const name = path.substr(Math.max(getRootLength(path), path.lastIndexOf(sep) + 1)); const extension = extensions ? extname(path, extensions, ignoreCase) : undefined; return extension ? name.slice(0, name.length - extension.length) : name; } - const extRegExp = /\.\w+$/; - - function extnameWorker(path: string, extensions: string | string[], stringEqualityComparer: core.EqualityComparer) { + function extnameWorker(path: string, extensions: string | ReadonlyArray, stringEqualityComparer: core.EqualityComparer) { const manyExtensions = Array.isArray(extensions) ? extensions : undefined; const singleExtension = Array.isArray(extensions) ? undefined : extensions; const length = manyExtensions ? manyExtensions.length : 1; for (let i = 0; i < length; i++) { let extension = manyExtensions ? manyExtensions[i] : singleExtension; if (!extension.startsWith(".")) extension = "." + extension; - if (path.length >= extension.length && - stringEqualityComparer(path.slice(path.length - extension.length), extension)) { - return extension; + if (path.length >= extension.length && path.charAt(path.length - extension.length) === ".") { + const pathExtension = path.slice(path.length - extension.length); + if (stringEqualityComparer(pathExtension, extension)) { + return pathExtension; + } } } return ""; } + const extRegExp = /\.\w+$/; + /** * Gets the file extension for a path. */ @@ -294,8 +296,8 @@ namespace vpath { /** * Gets the file extension for a path, provided it is one of the provided extensions. */ - export function extname(path: string, extensions: string | string[], ignoreCase: boolean): string; - export function extname(path: string, extensions?: string | string[], ignoreCase?: boolean) { + export function extname(path: string, extensions: string | ReadonlyArray, ignoreCase: boolean): string; + export function extname(path: string, extensions?: string | ReadonlyArray, ignoreCase?: boolean) { if (extensions) { return extnameWorker(path, extensions, ignoreCase ? core.equateStringsCaseInsensitive : core.equateStringsCaseSensitive); } @@ -305,32 +307,40 @@ namespace vpath { } export function changeExtension(path: string, ext: string): string; - export function changeExtension(path: string, ext: string, extensions: string | string[], ignoreCase: boolean): string; - export function changeExtension(path: string, ext: string, extensions?: string | string[], ignoreCase?: boolean) { + export function changeExtension(path: string, ext: string, extensions: string | ReadonlyArray, ignoreCase: boolean): string; + export function changeExtension(path: string, ext: string, extensions?: string | ReadonlyArray, ignoreCase?: boolean) { const pathext = extensions ? extname(path, extensions, ignoreCase) : extname(path); return pathext ? path.slice(0, path.length - pathext.length) + (ext.startsWith(".") ? ext : "." + ext) : path; } + const typeScriptExtensions: ReadonlyArray = [".ts", ".tsx"]; + export function isTypeScript(path: string) { - return path.endsWith(".ts") - || path.endsWith(".tsx"); + return extname(path, typeScriptExtensions, /*ignoreCase*/ false).length > 0; } + const javaScriptExtensions: ReadonlyArray = [".js", ".jsx"]; + export function isJavaScript(path: string) { - return path.endsWith(".js") - || path.endsWith(".jsx"); + return extname(path, javaScriptExtensions, /*ignoreCase*/ false).length > 0; } export function isDeclaration(path: string) { - return path.endsWith(".d.ts"); + return extname(path, ".d.ts", /*ignoreCase*/ false).length > 0; } export function isSourceMap(path: string) { - return path.endsWith(".map"); + return extname(path, ".map", /*ignoreCase*/ false).length > 0; + } + + const javaScriptSourceMapExtensions: ReadonlyArray = [".js.map", ".jsx.map"]; + + export function isJavaScriptSourceMap(path: string) { + return extname(path, javaScriptSourceMapExtensions, /*ignoreCase*/ false).length > 0; } export function isJson(path: string) { - return path.endsWith(".json"); + return extname(path, ".json", /*ignoreCase*/ false).length > 0; } export function isDefaultLibrary(path: string) {