diff --git a/src/harness/collections.ts b/src/harness/collections.ts
deleted file mode 100644
index d311216a915..00000000000
--- a/src/harness/collections.ts
+++ /dev/null
@@ -1,244 +0,0 @@
-///
-namespace collections {
- // NOTE: Some of the functions here duplicate functionality from compiler/core.ts. They have been added
- // to reduce the number of direct dependencies on compiler and services to eventually break away
- // from depending directly on the compiler to speed up compilation time.
-
- import binarySearch = ts.binarySearch;
-
- function compareValues(a: string | number, b: string | number): number {
- if (a === b) return 0;
- if (a === undefined) return -1;
- if (b === undefined) return +1;
- return a < b ? -1 : +1;
- }
-
- export function compareNumbers(a: number, b: number): number {
- return compareValues(a, b);
- }
-
- export function compareStrings(a: string, b: string, ignoreCase: boolean): number {
- return ignoreCase
- ? compareStringsCaseInsensitive(a, b)
- : compareStringsCaseSensitive(a, b);
- }
-
- export function compareStringsCaseSensitive(a: string, b: string): number {
- return compareValues(a, b);
- }
-
- export function compareStringsCaseInsensitive(a: string, b: string): number {
- if (a === b) return 0;
- if (a === undefined) return -1;
- if (b === undefined) return +1;
- a = a.toUpperCase();
- b = b.toUpperCase();
- return a < b ? -1 : a > b ? +1 : 0;
- }
-
- export function equateStringsCaseSensitive(a: string, b: string): boolean {
- return a === b;
- }
-
- export function equateStringsCaseInsensitive(a: string, b: string): boolean {
- return a === b
- || a !== undefined
- && b !== undefined
- && a.toUpperCase() === b.toUpperCase();
- }
-
- function removeAt(array: T[], index: number): void {
- for (let i = index; i < array.length - 1; i++) {
- array[i] = array[i + 1];
- }
- array.pop();
- }
-
- function insertAt(array: T[], index: number, value: T) {
- if (index === 0) {
- array.unshift(value);
- }
- else if (index === array.length) {
- array.push(value);
- }
- else {
- for (let i = array.length; i > index; i--) {
- array[i] = array[i - 1];
- }
- array[index] = value;
- }
- }
-
- /**
- * A collection of key/value pairs internally sorted by key.
- */
- export class KeyedCollection {
- private _comparer: (a: K, b: K) => number;
- private _keys: K[] = [];
- private _values: V[] = [];
- private _order: number[] = [];
- private _version = 0;
- private _copyOnWrite = false;
-
- constructor(comparer: (a: K, b: K) => number) {
- this._comparer = comparer;
- }
-
- public get size() {
- return this._keys.length;
- }
-
- public has(key: K) {
- return binarySearch(this._keys, key, ts.identity, this._comparer) >= 0;
- }
-
- public get(key: K) {
- const index = binarySearch(this._keys, key, ts.identity, this._comparer);
- return index >= 0 ? this._values[index] : undefined;
- }
-
- public set(key: K, value: V) {
- const index = binarySearch(this._keys, key, ts.identity, this._comparer);
- if (index >= 0) {
- this._values[index] = value;
- }
- else {
- this.writePreamble();
- insertAt(this._keys, ~index, key);
- insertAt(this._values, ~index, value);
- insertAt(this._order, ~index, this._version);
- this._version++;
- }
- return this;
- }
-
- public delete(key: K) {
- const index = binarySearch(this._keys, key, ts.identity, this._comparer);
- if (index >= 0) {
- this.writePreamble();
- removeAt(this._keys, index);
- removeAt(this._values, index);
- removeAt(this._order, index);
- this._version++;
- return true;
- }
- return false;
- }
-
- public clear() {
- if (this.size > 0) {
- this.writePreamble();
- this._keys.length = 0;
- this._values.length = 0;
- this._order.length = 0;
- this._version = 0;
- }
- }
-
- public forEach(callback: (value: V, key: K, collection: this) => void) {
- const keys = this._keys;
- const values = this._values;
- const order = this.getInsertionOrder();
- const version = this._version;
- this._copyOnWrite = true;
- for (const index of order) {
- callback(values[index], keys[index], this);
- }
- if (version === this._version) {
- this._copyOnWrite = false;
- }
- }
-
- private writePreamble() {
- if (this._copyOnWrite) {
- this._keys = this._keys.slice();
- this._values = this._values.slice();
- this._order = this._order.slice();
- this._copyOnWrite = false;
- }
- }
-
- private getInsertionOrder() {
- return this._order
- .map((_, i) => i)
- .sort((x, y) => compareNumbers(this._order[x], this._order[y]));
- }
- }
-
- const undefinedSentinel = {};
-
- function escapeKey(text: string) {
- return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text);
- }
-
- function unescapeKey(text: string) {
- return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text);
- }
-
- /**
- * A collection of metadata that supports inheritance.
- */
- export class Metadata {
- private _parent: Metadata | undefined;
- private _map: { [key: string]: any };
- private _version = 0;
- private _size = -1;
- private _parentVersion: number | undefined;
-
- constructor(parent?: Metadata) {
- this._parent = parent;
- this._map = Object.create(parent ? parent._map : null); // tslint:disable-line:no-null-keyword
- }
-
- public get size(): number {
- if (this._size === -1 || (this._parent && this._parent._version !== this._parentVersion)) {
- let size = 0;
- for (const _ in this._map) size++;
- this._size = size;
- if (this._parent) {
- this._parentVersion = this._parent._version;
- }
- }
- return this._size;
- }
-
- public has(key: string): boolean {
- return this._map[escapeKey(key)] !== undefined;
- }
-
- public get(key: string): any {
- const value = this._map[escapeKey(key)];
- return value === undefinedSentinel ? undefined : value;
- }
-
- public set(key: string, value: any): this {
- this._map[escapeKey(key)] = value === undefined ? undefinedSentinel : value;
- this._size = -1;
- this._version++;
- return this;
- }
-
- public delete(key: string): boolean {
- const escapedKey = escapeKey(key);
- if (this._map[escapedKey] !== undefined) {
- delete this._map[escapedKey];
- this._size = -1;
- this._version++;
- return true;
- }
- return false;
- }
-
- public clear(): void {
- this._map = Object.create(this._parent ? this._parent._map : null); // tslint:disable-line:no-null-keyword
- this._size = -1;
- this._version++;
- }
-
- public forEach(callback: (value: any, key: string, map: this) => void) {
- for (const key in this._map) {
- callback(this._map[key], unescapeKey(key), this);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/harness/compiler.ts b/src/harness/compiler.ts
index 6b4fcc62422..680f1f60978 100644
--- a/src/harness/compiler.ts
+++ b/src/harness/compiler.ts
@@ -1,32 +1,30 @@
///
///
-///
+///
///
///
///
-namespace compiler {
- import TextDocument = documents.TextDocument;
- import SourceMap = documents.SourceMap;
- import KeyedCollection = collections.KeyedCollection;
- import VirtualFileSystem = vfs.VirtualFileSystem;
+// 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 {
export class CompilerHost {
public readonly vfs: vfs.VirtualFileSystem;
public readonly defaultLibLocation: string;
- public readonly outputs: TextDocument[] = [];
+ public readonly outputs: documents.TextDocument[] = [];
public readonly traces: string[] = [];
public readonly shouldAssertInvariants = !Harness.lightMode;
private _setParentNodes: boolean;
- private _sourceFiles: KeyedCollection;
+ private _sourceFiles: core.KeyedCollection;
private _newLine: string;
private _parseConfigHost: ParseConfigHost;
- constructor(vfs: VirtualFileSystem, options: ts.CompilerOptions, setParentNodes = false) {
+ constructor(vfs: vfs.VirtualFileSystem, options: ts.CompilerOptions, setParentNodes = false) {
this.vfs = vfs;
this.defaultLibLocation = vfs.metadata.get("defaultLibLocation") || "";
- this._sourceFiles = new KeyedCollection(this.vfs.pathComparer);
+ this._sourceFiles = new core.KeyedCollection(this.vfs.pathComparer);
this._newLine = options.newLine === ts.NewLineKind.LineFeed ? "\n" : "\r\n";
this._setParentNodes = setParentNodes;
}
@@ -66,7 +64,7 @@ namespace compiler {
public readFile(path: string): string | undefined {
const entry = this.vfs.getFile(path);
- const content = entry && entry.content && utils.removeByteOrderMark(entry.content);
+ const content = entry && entry.content && core.removeByteOrderMark(entry.content);
return content === undefined ? undefined :
vpath.extname(path) === ".json" ? utils.removeComments(content, utils.CommentRemoval.leadingAndTrailing) :
content;
@@ -77,7 +75,7 @@ namespace compiler {
if (writeByteOrderMark) content = "\u00EF\u00BB\u00BF" + content;
const entry = this.vfs.addFile(fileName, content, { overwrite: true });
if (entry) {
- const document = new TextDocument(fileName, content);
+ const document = new documents.TextDocument(fileName, content);
document.meta.set("fileName", fileName);
entry.metadata.set("document", document);
const index = this.outputs.findIndex(output => this.vfs.pathComparer(document.file, output.file) === 0);
@@ -115,7 +113,7 @@ namespace compiler {
if (file) {
let content = file.content;
if (content !== undefined) {
- content = utils.removeByteOrderMark(content);
+ content = core.removeByteOrderMark(content);
// We cache and reuse source files for files we know shouldn't change.
const shouldCache = vpath.isDefaultLibrary(fileName) ||
@@ -153,9 +151,9 @@ namespace compiler {
}
export class ParseConfigHost {
- public readonly vfs: VirtualFileSystem;
+ public readonly vfs: vfs.VirtualFileSystem;
- constructor(vfs: VirtualFileSystem) {
+ constructor(vfs: vfs.VirtualFileSystem) {
this.vfs = vfs;
}
@@ -218,10 +216,10 @@ namespace compiler {
}
export interface CompilationOutput {
- readonly input: TextDocument;
- readonly js: TextDocument | undefined;
- readonly dts: TextDocument | undefined;
- readonly map: TextDocument | undefined;
+ readonly input: documents.TextDocument;
+ readonly js: documents.TextDocument | undefined;
+ readonly dts: documents.TextDocument | undefined;
+ readonly map: documents.TextDocument | undefined;
}
export class CompilationResult {
@@ -230,12 +228,12 @@ namespace compiler {
public readonly result: ts.EmitResult | undefined;
public readonly options: ts.CompilerOptions;
public readonly diagnostics: ts.Diagnostic[];
- public readonly js: KeyedCollection;
- public readonly dts: KeyedCollection;
- public readonly maps: KeyedCollection;
+ public readonly js: core.KeyedCollection;
+ public readonly dts: core.KeyedCollection;
+ public readonly maps: core.KeyedCollection;
- private _inputs: TextDocument[] = [];
- private _inputsAndOutputs: KeyedCollection;
+ private _inputs: documents.TextDocument[] = [];
+ private _inputsAndOutputs: core.KeyedCollection;
constructor(host: CompilerHost, options: ts.CompilerOptions, program: ts.Program | undefined, result: ts.EmitResult | undefined, diagnostics: ts.Diagnostic[]) {
this.host = host;
@@ -245,9 +243,9 @@ namespace compiler {
this.options = program ? program.getCompilerOptions() : options;
// collect outputs
- this.js = new KeyedCollection(this.vfs.pathComparer);
- this.dts = new KeyedCollection(this.vfs.pathComparer);
- this.maps = new KeyedCollection(this.vfs.pathComparer);
+ this.js = new core.KeyedCollection(this.vfs.pathComparer);
+ this.dts = new core.KeyedCollection(this.vfs.pathComparer);
+ 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);
@@ -261,11 +259,11 @@ namespace compiler {
}
// correlate inputs and outputs
- this._inputsAndOutputs = new KeyedCollection(this.vfs.pathComparer);
+ this._inputsAndOutputs = new core.KeyedCollection(this.vfs.pathComparer);
if (program) {
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile) {
- const input = new TextDocument(sourceFile.fileName, sourceFile.text);
+ const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text);
this._inputs.push(input);
if (!vpath.isDeclaration(sourceFile.fileName)) {
const outputs = {
@@ -289,11 +287,11 @@ namespace compiler {
return this.host.vfs;
}
- public get inputs(): ReadonlyArray {
+ public get inputs(): ReadonlyArray {
return this._inputs;
}
- public get outputs(): ReadonlyArray {
+ public get outputs(): ReadonlyArray {
return this.host.outputs;
}
@@ -318,25 +316,25 @@ namespace compiler {
return this._inputsAndOutputs.get(vpath.resolve(this.vfs.currentDirectory, path));
}
- public getInput(path: string): TextDocument | undefined {
+ public getInput(path: string): documents.TextDocument | undefined {
const outputs = this.getInputsAndOutputs(path);
return outputs && outputs.input;
}
- public getOutput(path: string, kind: "js" | "dts" | "map"): TextDocument | undefined {
+ public getOutput(path: string, kind: "js" | "dts" | "map"): documents.TextDocument | undefined {
const outputs = this.getInputsAndOutputs(path);
return outputs && outputs[kind];
}
- public getSourceMap(path: string): SourceMap | undefined {
+ public getSourceMap(path: string): documents.SourceMap | undefined {
if (this.options.noEmit || vpath.isDeclaration(path)) return undefined;
if (this.options.inlineSourceMap) {
const document = this.getOutput(path, "js");
- return document && SourceMap.fromSource(document.text);
+ return document && documents.SourceMap.fromSource(document.text);
}
if (this.options.sourceMap) {
const document = this.getOutput(path, "map");
- return document && new SourceMap(document.file, document.text);
+ return document && new documents.SourceMap(document.file, document.text);
}
}
diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts
index 7aa26b9c8cd..5eb78f746df 100644
--- a/src/harness/compilerRunner.ts
+++ b/src/harness/compilerRunner.ts
@@ -45,151 +45,6 @@ class CompilerBaselineRunner extends RunnerBase {
return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true });
}
- private makeUnitName(name: string, root: string) {
- const path = ts.toPath(name, root, utils.identity);
- const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", utils.identity);
- return pathStart ? path.replace(pathStart, "/") : path;
- }
-
- public checkTestCodeOutput(fileName: string) {
- describe(`${this.testSuiteName} tests for ${fileName}`, () => {
- // Mocha holds onto the closure environment of the describe callback even after the test is done.
- // Everything declared here should be cleared out in the "after" callback.
- let justName: string;
- let lastUnit: Harness.TestCaseParser.TestUnitData;
- let harnessSettings: Harness.TestCaseParser.CompilerSettings;
- let hasNonDtsFiles: boolean;
-
- let result: Harness.Compiler.CompilerResult;
- let options: ts.CompilerOptions;
- let tsConfigFiles: Harness.Compiler.TestFile[];
- // equivalent to the files that will be passed on the command line
- let toBeCompiled: Harness.Compiler.TestFile[];
- // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files)
- let otherFiles: Harness.Compiler.TestFile[];
-
- before(() => {
- justName = vpath.basename(fileName);
- const content = Harness.IO.readFile(fileName);
- const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
- const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName, rootDir);
- const units = testCaseContent.testUnitData;
- harnessSettings = testCaseContent.settings;
- let tsConfigOptions: ts.CompilerOptions;
- tsConfigFiles = [];
- if (testCaseContent.tsConfig) {
- assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
-
- tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options);
- tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath)));
- }
- else {
- const baseUrl = harnessSettings.baseUrl;
- if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
- harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
- }
- }
-
- lastUnit = units[units.length - 1];
- hasNonDtsFiles = ts.forEach(units, unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts));
- // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
- // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
- // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
- toBeCompiled = [];
- otherFiles = [];
-
- if (testCaseContent.settings.noImplicitReferences || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
- toBeCompiled.push(this.createHarnessTestFile(lastUnit, rootDir));
- units.forEach(unit => {
- if (unit.name !== lastUnit.name) {
- otherFiles.push(this.createHarnessTestFile(unit, rootDir));
- }
- });
- }
- else {
- toBeCompiled = units.map(unit => {
- return this.createHarnessTestFile(unit, rootDir);
- });
- }
-
- if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) {
- tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath);
- tsConfigOptions.configFile.fileName = tsConfigOptions.configFilePath;
- }
-
- const output = Harness.Compiler.compileFiles(
- toBeCompiled, otherFiles, harnessSettings, /*options*/ tsConfigOptions, /*currentDirectory*/ harnessSettings.currentDirectory);
-
- options = output.options;
- result = output.result;
- });
-
- after(() => {
- // Mocha holds onto the closure environment of the describe callback even after the test is done.
- // Therefore we have to clean out large objects after the test is done.
- justName = undefined;
- lastUnit = undefined;
- hasNonDtsFiles = undefined;
- result = undefined;
- options = undefined;
- toBeCompiled = undefined;
- otherFiles = undefined;
- tsConfigFiles = undefined;
- });
-
- // check errors
- it("Correct errors for " + fileName, () => {
- Harness.Compiler.doErrorBaseline(justName, tsConfigFiles.concat(toBeCompiled, otherFiles), result.errors, !!options.pretty);
- });
-
- it (`Correct module resolution tracing for ${fileName}`, () => {
- if (options.traceResolution) {
- Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".trace.json"), () => {
- return utils.removeTestPathPrefixes(JSON.stringify(result.traceResults || [], undefined, 4));
- });
- }
- });
-
- // Source maps?
- it("Correct sourcemap content for " + fileName, () => {
- if (options.sourceMap || options.inlineSourceMap) {
- Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => {
- const record = utils.removeTestPathPrefixes(result.getSourceMapRecord());
- if ((options.noEmitOnError && result.errors.length !== 0) || record === undefined) {
- // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
- /* tslint:disable:no-null-keyword */
- return null;
- /* tslint:enable:no-null-keyword */
- }
- return record;
- });
- }
- });
-
- it("Correct JS output for " + fileName, () => {
- if (hasNonDtsFiles && this.emit) {
- Harness.Compiler.doJsEmitBaseline(justName, fileName, options, result, tsConfigFiles, toBeCompiled, otherFiles, harnessSettings);
- }
- });
-
- it("Correct Sourcemap output for " + fileName, () => {
- Harness.Compiler.doSourcemapBaseline(justName, options, result, harnessSettings);
- });
-
- it("Correct type/symbol baselines for " + fileName, () => {
- if (fileName.indexOf("APISample") >= 0) {
- return;
- }
-
- Harness.Compiler.doTypeAndSymbolBaseline(justName, result.program, toBeCompiled.concat(otherFiles).filter(file => !!result.program.getSourceFile(file.unitName)));
- });
- });
- }
-
- private createHarnessTestFile(lastUnit: Harness.TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Harness.Compiler.TestFile {
- return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
- }
-
public initializeTests() {
describe(this.testSuiteName + " tests", () => {
describe("Setup compiler for compiler baselines", () => {
@@ -197,19 +52,33 @@ class CompilerBaselineRunner extends RunnerBase {
});
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
- if (this.tests.length === 0) {
- const testFiles = this.enumerateTestFiles();
- testFiles.forEach(fn => {
- fn = fn.replace(/\\/g, "/");
- this.checkTestCodeOutput(fn);
- });
- }
- else {
- this.tests.forEach(test => this.checkTestCodeOutput(test));
- }
+ const files = this.tests.length > 0 ? this.tests : this.enumerateTestFiles();
+ files.forEach(file => { this.checkTestCodeOutput(vpath.normalizeSeparators(file)); });
});
}
+ public checkTestCodeOutput(fileName: string) {
+ for (const { name, payload } of CompilerTest.getConfigurations(fileName)) {
+ describe(`${this.testSuiteName} tests for ${fileName}${name ? ` (${name})` : ``}`, () => {
+ this.runSuite(fileName, payload);
+ });
+ }
+ }
+
+ private runSuite(fileName: string, testCaseContent: Harness.TestCaseParser.TestCaseContent) {
+ // Mocha holds onto the closure environment of the describe callback even after the test is done.
+ // Everything declared here should be cleared out in the "after" callback.
+ let compilerTest: CompilerTest | undefined;
+ before(() => { compilerTest = new CompilerTest(fileName, testCaseContent); });
+ it(`Correct errors for ${fileName}`, () => { compilerTest.verifyDiagnostics(); });
+ it(`Correct module resolution tracing for ${fileName}`, () => { compilerTest.verifyModuleResolution(); });
+ it(`Correct sourcemap content for ${fileName}`, () => { compilerTest.verifySourceMapRecord(); });
+ it(`Correct JS output for ${fileName}`, () => { if (this.emit) compilerTest.verifyJavaScriptOutput(); });
+ it(`Correct Sourcemap output for ${fileName}`, () => { compilerTest.verifySourceMapOutput(); });
+ it(`Correct type/symbol baselines for ${fileName}`, () => { compilerTest.verifyTypesAndSymbols(); });
+ after(() => { compilerTest = undefined; });
+ }
+
private parseOptions() {
if (this.options && this.options.length > 0) {
this.emit = false;
@@ -226,4 +95,191 @@ class CompilerBaselineRunner extends RunnerBase {
}
}
}
+}
+
+interface CompilerTestConfiguration {
+ name: string;
+ payload: Harness.TestCaseParser.TestCaseContent;
+}
+
+class CompilerTest {
+ private fileName: string;
+ private justName: string;
+ private lastUnit: Harness.TestCaseParser.TestUnitData;
+ private harnessSettings: Harness.TestCaseParser.CompilerSettings;
+ private hasNonDtsFiles: boolean;
+ private result: Harness.Compiler.CompilerResult;
+ private options: ts.CompilerOptions;
+ private tsConfigFiles: Harness.Compiler.TestFile[];
+ // equivalent to the files that will be passed on the command line
+ private toBeCompiled: Harness.Compiler.TestFile[];
+ // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files)
+ private otherFiles: Harness.Compiler.TestFile[];
+
+ constructor(fileName: string, testCaseContent: Harness.TestCaseParser.TestCaseContent) {
+ this.fileName = fileName;
+ this.justName = vpath.basename(fileName);
+ const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
+ const units = testCaseContent.testUnitData;
+ this.harnessSettings = testCaseContent.settings;
+ let tsConfigOptions: ts.CompilerOptions;
+ this.tsConfigFiles = [];
+ if (testCaseContent.tsConfig) {
+ assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
+
+ tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options);
+ this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath)));
+ }
+ else {
+ const baseUrl = this.harnessSettings.baseUrl;
+ if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
+ this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
+ }
+ }
+
+ this.lastUnit = units[units.length - 1];
+ this.hasNonDtsFiles = ts.forEach(units, unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts));
+ // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
+ // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
+ // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
+ this.toBeCompiled = [];
+ this.otherFiles = [];
+
+ if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) {
+ this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir));
+ units.forEach(unit => {
+ if (unit.name !== this.lastUnit.name) {
+ this.otherFiles.push(this.createHarnessTestFile(unit, rootDir));
+ }
+ });
+ }
+ else {
+ this.toBeCompiled = units.map(unit => {
+ return this.createHarnessTestFile(unit, rootDir);
+ });
+ }
+
+ if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) {
+ tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath);
+ tsConfigOptions.configFile.fileName = tsConfigOptions.configFilePath;
+ }
+
+ const output = Harness.Compiler.compileFiles(
+ this.toBeCompiled,
+ this.otherFiles,
+ this.harnessSettings,
+ /*options*/ tsConfigOptions,
+ /*currentDirectory*/ this.harnessSettings.currentDirectory);
+
+ this.options = output.options;
+ this.result = output.result;
+ }
+
+ public static getConfigurations(fileName: string) {
+ const content = Harness.IO.readFile(fileName);
+ const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
+ const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName, rootDir);
+ const configurations: CompilerTestConfiguration[] = [];
+ const scriptTargets = this._split(testCaseContent.settings.target);
+ const moduleKinds = this._split(testCaseContent.settings.module);
+ for (const scriptTarget of scriptTargets) {
+ for (const moduleKind of moduleKinds) {
+ let name = "";
+ if (moduleKinds.length > 1) {
+ name += `@module: ${moduleKind || "none"}`;
+ }
+ if (scriptTargets.length > 1) {
+ if (name) name += ", ";
+ name += `@target: ${scriptTarget || "none"}`;
+ }
+
+ const settings = { ...testCaseContent.settings };
+ if (scriptTarget) settings.target = scriptTarget;
+ if (moduleKind) settings.module = moduleKind;
+ configurations.push({ name, payload: { ...testCaseContent, settings } });
+ }
+ }
+
+ return configurations;
+ }
+
+ public verifyDiagnostics() {
+ // check errors
+ Harness.Compiler.doErrorBaseline(
+ this.justName,
+ this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles),
+ this.result.errors,
+ !!this.options.pretty);
+ }
+
+ public verifyModuleResolution() {
+ if (this.options.traceResolution) {
+ Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".trace.json"), () => {
+ return utils.removeTestPathPrefixes(JSON.stringify(this.result.traceResults || [], undefined, 4));
+ });
+ }
+ }
+
+ public verifySourceMapRecord() {
+ if (this.options.sourceMap || this.options.inlineSourceMap) {
+ Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => {
+ const record = utils.removeTestPathPrefixes(this.result.getSourceMapRecord());
+ if ((this.options.noEmitOnError && this.result.errors.length !== 0) || record === undefined) {
+ // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
+ /* tslint:disable:no-null-keyword */
+ return null;
+ /* tslint:enable:no-null-keyword */
+ }
+ return record;
+ });
+ }
+ }
+
+ public verifyJavaScriptOutput() {
+ if (this.hasNonDtsFiles) {
+ Harness.Compiler.doJsEmitBaseline(
+ this.justName,
+ this.fileName,
+ this.options,
+ this.result,
+ this.tsConfigFiles,
+ this.toBeCompiled,
+ this.otherFiles,
+ this.harnessSettings);
+ }
+ }
+
+ public verifySourceMapOutput() {
+ Harness.Compiler.doSourcemapBaseline(
+ this.justName,
+ this.options,
+ this.result,
+ this.harnessSettings);
+ }
+
+ public verifyTypesAndSymbols() {
+ if (this.fileName.indexOf("APISample") >= 0) {
+ return;
+ }
+
+ Harness.Compiler.doTypeAndSymbolBaseline(
+ this.justName,
+ this.result.program,
+ this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program.getSourceFile(file.unitName)));
+ }
+
+ private static _split(text: string) {
+ const entries = text && text.split(",").map(s => s.toLowerCase().trim()).filter(s => s.length > 0);
+ return entries && entries.length > 0 ? entries : [""];
+ }
+
+ private makeUnitName(name: string, root: string) {
+ const path = ts.toPath(name, root, core.identity);
+ const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", core.identity);
+ return pathStart ? path.replace(pathStart, "/") : path;
+ }
+
+ private createHarnessTestFile(lastUnit: Harness.TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Harness.Compiler.TestFile {
+ return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
+ }
}
\ No newline at end of file
diff --git a/src/harness/core.ts b/src/harness/core.ts
new file mode 100644
index 00000000000..9efe53c4d8a
--- /dev/null
+++ b/src/harness/core.ts
@@ -0,0 +1,421 @@
+///
+
+// 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.
+
+// NOTE: Some of the functions here duplicate functionality from compiler/core.ts. They have been added
+// to reduce the number of direct dependencies on compiler and services to eventually break away
+// from depending directly on the compiler to speed up compilation time.
+
+namespace core {
+ export function identity(v: T): T { return v; }
+
+ //
+ // Comparers
+ //
+
+ export type Comparer = (x: T, y: T) => number;
+ export type EqualityComparer = (x: T, y: T) => boolean;
+
+ export function compareNumbers(a: number, b: number): number {
+ if (a === b) return 0;
+ if (a === undefined) return -1;
+ if (b === undefined) return +1;
+ return a < b ? -1 : +1;
+ }
+
+ export function compareStrings(a: string, b: string, ignoreCase: boolean): number {
+ return ignoreCase
+ ? compareStringsCaseInsensitive(a, b)
+ : compareStringsCaseSensitive(a, b);
+ }
+
+ // NOTE: This is a duplicate of `compareNumbers` above, but is intended to be used only with
+ // strings to reduce polymorphism.
+ export function compareStringsCaseSensitive(a: string, b: string): number {
+ if (a === b) return 0;
+ if (a === undefined) return -1;
+ if (b === undefined) return +1;
+ return a < b ? -1 : +1;
+ }
+
+ export function compareStringsCaseInsensitive(a: string, b: string): number {
+ if (a === b) return 0;
+ if (a === undefined) return -1;
+ if (b === undefined) return +1;
+ a = a.toUpperCase();
+ b = b.toUpperCase();
+ return a < b ? -1 : a > b ? +1 : 0;
+ }
+
+ export function equateStringsCaseSensitive(a: string, b: string): boolean {
+ return a === b;
+ }
+
+ export function equateStringsCaseInsensitive(a: string, b: string): boolean {
+ return a === b
+ || a !== undefined
+ && b !== undefined
+ && a.toUpperCase() === b.toUpperCase();
+ }
+
+ //
+ // Collections
+ //
+
+ /**
+ * A collection of key/value pairs internally sorted by key.
+ */
+ export class KeyedCollection {
+ private _comparer: (a: K, b: K) => number;
+ private _keys: K[] = [];
+ private _values: V[] = [];
+ private _order: number[] = [];
+ private _version = 0;
+ private _copyOnWrite = false;
+
+ constructor(comparer: (a: K, b: K) => number) {
+ this._comparer = comparer;
+ }
+
+ public get size() {
+ return this._keys.length;
+ }
+
+ public has(key: K) {
+ return binarySearch(this._keys, key, identity, this._comparer) >= 0;
+ }
+
+ public get(key: K) {
+ const index = binarySearch(this._keys, key, identity, this._comparer);
+ return index >= 0 ? this._values[index] : undefined;
+ }
+
+ public set(key: K, value: V) {
+ const index = binarySearch(this._keys, key, identity, this._comparer);
+ if (index >= 0) {
+ this._values[index] = value;
+ }
+ else {
+ this.writePreamble();
+ insertAt(this._keys, ~index, key);
+ insertAt(this._values, ~index, value);
+ insertAt(this._order, ~index, this._version);
+ this._version++;
+ }
+ return this;
+ }
+
+ public delete(key: K) {
+ const index = binarySearch(this._keys, key, identity, this._comparer);
+ if (index >= 0) {
+ this.writePreamble();
+ removeAt(this._keys, index);
+ removeAt(this._values, index);
+ removeAt(this._order, index);
+ this._version++;
+ return true;
+ }
+ return false;
+ }
+
+ public clear() {
+ if (this.size > 0) {
+ this.writePreamble();
+ this._keys.length = 0;
+ this._values.length = 0;
+ this._order.length = 0;
+ this._version = 0;
+ }
+ }
+
+ public forEach(callback: (value: V, key: K, collection: this) => void) {
+ const keys = this._keys;
+ const values = this._values;
+ const order = this.getInsertionOrder();
+ const version = this._version;
+ this._copyOnWrite = true;
+ for (const index of order) {
+ callback(values[index], keys[index], this);
+ }
+ if (version === this._version) {
+ this._copyOnWrite = false;
+ }
+ }
+
+ private writePreamble() {
+ if (this._copyOnWrite) {
+ this._keys = this._keys.slice();
+ this._values = this._values.slice();
+ this._order = this._order.slice();
+ this._copyOnWrite = false;
+ }
+ }
+
+ private getInsertionOrder() {
+ return this._order
+ .map((_, i) => i)
+ .sort((x, y) => compareNumbers(this._order[x], this._order[y]));
+ }
+ }
+
+ /**
+ * A collection of metadata that supports inheritance.
+ */
+ export class Metadata {
+ private static readonly _undefinedValue = {};
+ private _parent: Metadata | undefined;
+ private _map: { [key: string]: any };
+ private _version = 0;
+ private _size = -1;
+ private _parentVersion: number | undefined;
+
+ constructor(parent?: Metadata) {
+ this._parent = parent;
+ this._map = Object.create(parent ? parent._map : null); // tslint:disable-line:no-null-keyword
+ }
+
+ public get size(): number {
+ if (this._size === -1 || (this._parent && this._parent._version !== this._parentVersion)) {
+ let size = 0;
+ for (const _ in this._map) size++;
+ this._size = size;
+ if (this._parent) {
+ this._parentVersion = this._parent._version;
+ }
+ }
+ return this._size;
+ }
+
+ public has(key: string): boolean {
+ return this._map[Metadata._escapeKey(key)] !== undefined;
+ }
+
+ public get(key: string): any {
+ const value = this._map[Metadata._escapeKey(key)];
+ return value === Metadata._undefinedValue ? undefined : value;
+ }
+
+ public set(key: string, value: any): this {
+ this._map[Metadata._escapeKey(key)] = value === undefined ? Metadata._undefinedValue : value;
+ this._size = -1;
+ this._version++;
+ return this;
+ }
+
+ public delete(key: string): boolean {
+ const escapedKey = Metadata._escapeKey(key);
+ if (this._map[escapedKey] !== undefined) {
+ delete this._map[escapedKey];
+ this._size = -1;
+ this._version++;
+ return true;
+ }
+ return false;
+ }
+
+ public clear(): void {
+ this._map = Object.create(this._parent ? this._parent._map : null); // tslint:disable-line:no-null-keyword
+ this._size = -1;
+ this._version++;
+ }
+
+ public forEach(callback: (value: any, key: string, map: this) => void) {
+ for (const key in this._map) {
+ callback(this._map[key], Metadata._unescapeKey(key), this);
+ }
+ }
+
+ private static _escapeKey(text: string) {
+ return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text);
+ }
+
+ private static _unescapeKey(text: string) {
+ return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text);
+ }
+ }
+
+ export function binarySearch(array: ReadonlyArray, value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number {
+ if (!array || array.length === 0) {
+ return -1;
+ }
+
+ let low = offset || 0;
+ let high = array.length - 1;
+ const key = keySelector(value);
+ while (low <= high) {
+ const middle = low + ((high - low) >> 1);
+ const midKey = keySelector(array[middle]);
+ const result = keyComparer(midKey, key);
+ if (result < 0) {
+ low = middle + 1;
+ }
+ else if (result > 0) {
+ high = middle - 1;
+ }
+ else {
+ return middle;
+ }
+ }
+
+ return ~low;
+ }
+
+ export function removeAt(array: T[], index: number): void {
+ for (let i = index; i < array.length - 1; i++) {
+ array[i] = array[i + 1];
+ }
+
+ array.length--;
+ }
+
+ export function insertAt(array: T[], index: number, value: T): void {
+ if (index === 0) {
+ array.unshift(value);
+ }
+ else if (index === array.length) {
+ array.push(value);
+ }
+ else {
+ for (let i = array.length; i > index; i--) {
+ array[i] = array[i - 1];
+ }
+ array[index] = value;
+ }
+ }
+
+ export function stableSort(array: T[], comparer: (x: T, y: T) => number): T[] {
+ return array
+ .map((_, i) => i) // create array of indices
+ .sort((x, y) => comparer(array[x], array[y]) || x - y) // sort indices by value then position
+ .map(i => array[i]); // get sorted array
+ }
+
+ //
+ // Strings
+ //
+
+ export function padLeft(text: string, size: number, ch = " "): string {
+ while (text.length < size) text = ch + text;
+ return text;
+ }
+
+ export function padRight(text: string, size: number, ch = " "): string {
+ while (text.length < size) text += ch;
+ return text;
+ }
+
+ export function getByteOrderMarkLength(text: string): number {
+ if (text.length >= 2) {
+ 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;
+ }
+ }
+ return 0;
+ }
+
+ export function removeByteOrderMark(text: string): string {
+ const length = getByteOrderMarkLength(text);
+ return length ? text.slice(length) : text;
+ }
+
+ function splitLinesWorker(text: string, lineStarts: number[] | undefined, lines: string[] | undefined, removeEmptyElements: boolean) {
+ let pos = 0;
+ let end = 0;
+ let lineStart = 0;
+ let nonWhiteSpace = false;
+ while (pos < text.length) {
+ const ch = text.charCodeAt(pos);
+ end = pos;
+ pos++;
+ switch (ch) {
+ // LineTerminator
+ case 0x000d: // carriage return
+ if (pos < text.length && text.charCodeAt(pos) === 0x000a) {
+ pos++;
+ }
+ // falls through
+
+ case 0x000a: // line feed
+ case 0x2028: // line separator
+ case 0x2029: // paragraph separator
+ if (lineStarts) {
+ lineStarts.push(lineStart);
+ }
+ if (lines && (!removeEmptyElements || nonWhiteSpace)) {
+ lines.push(text.slice(lineStart, end));
+ }
+ lineStart = pos;
+ nonWhiteSpace = false;
+ break;
+
+ // WhiteSpace
+ case 0x0009: // tab
+ case 0x000b: // vertical tab
+ case 0x000c: // form feed
+ case 0x0020: // space
+ case 0x00a0: // no-break space
+ case 0xfeff: // zero width no-break space
+ case 0x1680: // ogham space mark
+ case 0x2000: // en quad
+ case 0x2001: // em quad
+ case 0x2002: // en space
+ case 0x2003: // em space
+ case 0x2004: // three-per-em space
+ case 0x2005: // four-per-em space
+ case 0x2006: // six-per-em space
+ case 0x2007: // figure space
+ case 0x2008: // punctuation space
+ case 0x2009: // thin space
+ case 0x200a: // hair space
+ case 0x202f: // narrow no-break space
+ case 0x205f: // medium mathematical space
+ case 0x3000: // ideographic space
+ case 0x0085: // next-line (not strictly per spec, but used by the compiler)
+ break;
+
+ default:
+ nonWhiteSpace = true;
+ break;
+ }
+ }
+ if (lineStarts) {
+ lineStarts.push(lineStart);
+ }
+ if (lines && (!removeEmptyElements || nonWhiteSpace)) {
+ lines.push(text.slice(lineStart, text.length));
+ }
+ }
+
+ export type LineStarts = ReadonlyArray;
+
+ export interface LinesAndLineStarts {
+ readonly lines: ReadonlyArray;
+ readonly lineStarts: LineStarts;
+ }
+
+ export function getLinesAndLineStarts(text: string): LinesAndLineStarts {
+ const lines: string[] = [];
+ const lineStarts: number[] = [];
+ splitLinesWorker(text, lineStarts, lines, /*removeEmptyElements*/ false);
+ return { lines, lineStarts };
+ }
+
+ export function splitLines(text: string, removeEmptyElements = false): string[] {
+ const lines: string[] = [];
+ splitLinesWorker(text, /*lineStarts*/ undefined, lines, removeEmptyElements);
+ return lines;
+ }
+
+ export function computeLineStarts(text: string): LineStarts {
+ const lineStarts: number[] = [];
+ splitLinesWorker(text, lineStarts, /*lines*/ undefined, /*removeEmptyElements*/ false);
+ return lineStarts;
+ }
+}
\ No newline at end of file
diff --git a/src/harness/documents.ts b/src/harness/documents.ts
index b460cc8393f..a5df3a6af52 100644
--- a/src/harness/documents.ts
+++ b/src/harness/documents.ts
@@ -1,4 +1,7 @@
-///
+///
+
+// NOTE: The contents of this file are all exported from the namespace 'documents'. This is to
+// support the eventual conversion of harness into a modular system.
namespace documents {
export class TextDocument {
@@ -6,16 +9,16 @@ namespace documents {
public readonly file: string;
public readonly text: string;
- private _lineStarts: number[] | undefined;
+ private _lineStarts: core.LineStarts | undefined;
- constructor(file: string, content: string, meta?: Map) {
+ constructor(file: string, text: string, meta?: Map) {
this.file = file;
- this.text = content;
+ this.text = text;
this.meta = meta || new Map();
}
- public get lineStarts(): number[] {
- return this._lineStarts || (this._lineStarts = ts.computeLineStarts(this.text));
+ public get lineStarts(): core.LineStarts {
+ return this._lineStarts || (this._lineStarts = core.computeLineStarts(this.text));
}
}
@@ -39,10 +42,6 @@ namespace documents {
nameIndex?: number;
}
- const mappingRegExp = /([A-Za-z0-9+/]+),?|(;)|./g;
- const sourceMappingURLRegExp = /^\/\/[#@]\s*sourceMappingURL\s*=\s*(.*?)\s*$/mig;
- const dataURLRegExp = /^data:application\/json;base64,([a-z0-9+/=]+)$/i;
-
export class SourceMap {
public readonly raw: RawSourceMap;
public readonly mapFile: string | undefined;
@@ -54,6 +53,11 @@ namespace documents {
public readonly mappings: ReadonlyArray = [];
public readonly names: ReadonlyArray | undefined;
+ private static readonly _mappingRegExp = /([A-Za-z0-9+/]+),?|(;)|./g;
+ private static readonly _sourceMappingURLRegExp = /^\/\/[#@]\s*sourceMappingURL\s*=\s*(.*?)\s*$/mig;
+ private static readonly _dataURLRegExp = /^data:application\/json;base64,([a-z0-9+/=]+)$/i;
+ private static readonly _base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
private _emittedLineMappings: Mapping[][] = [];
private _sourceLineMappings: Mapping[][][] = [];
@@ -76,9 +80,9 @@ namespace documents {
let sourceColumn = 0;
let nameIndex = 0;
let match: RegExpExecArray | null;
- while (match = mappingRegExp.exec(this.raw.mappings)) {
+ while (match = SourceMap._mappingRegExp.exec(this.raw.mappings)) {
if (match[1]) {
- const segment = decodeVLQ(match[1]);
+ const segment = SourceMap._decodeVLQ(match[1]);
if (segment.length !== 1 && segment.length !== 4 && segment.length !== 5) {
throw new Error("Invalid VLQ");
}
@@ -120,14 +124,14 @@ namespace documents {
public static getUrl(text: string) {
let match: RegExpExecArray | null;
let lastMatch: RegExpExecArray | undefined;
- while (match = sourceMappingURLRegExp.exec(text)) {
+ while (match = SourceMap._sourceMappingURLRegExp.exec(text)) {
lastMatch = match;
}
return lastMatch ? lastMatch[1] : undefined;
}
public static fromUrl(url: string) {
- const match = dataURLRegExp.exec(url);
+ const match = SourceMap._dataURLRegExp.exec(url);
return match ? new SourceMap(/*mapFile*/ undefined, new Buffer(match[1], "base64").toString("utf8")) : undefined;
}
@@ -144,26 +148,24 @@ namespace documents {
const mappingsForSource = this._sourceLineMappings[sourceIndex];
return mappingsForSource && mappingsForSource[sourceLine];
}
- }
- const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
- export function decodeVLQ(text: string) {
- const vlq: number[] = [];
- let shift = 0;
- let value = 0;
- for (let i = 0; i < text.length; i++) {
- const currentByte = base64Chars.indexOf(text.charAt(i));
- value += (currentByte & 31) << shift;
- if ((currentByte & 32) === 0) {
- vlq.push(value & 1 ? -(value >>> 1) : value >>> 1);
- shift = 0;
- value = 0;
- }
- else {
- shift += 5;
+ private static _decodeVLQ(text: string): number[] {
+ const vlq: number[] = [];
+ let shift = 0;
+ let value = 0;
+ for (let i = 0; i < text.length; i++) {
+ const currentByte = SourceMap._base64Chars.indexOf(text.charAt(i));
+ value += (currentByte & 31) << shift;
+ if ((currentByte & 32) === 0) {
+ vlq.push(value & 1 ? -(value >>> 1) : value >>> 1);
+ shift = 0;
+ value = 0;
+ }
+ else {
+ shift += 5;
+ }
}
+ return vlq;
}
- return vlq;
}
}
\ No newline at end of file
diff --git a/src/harness/events.ts b/src/harness/events.ts
index 684995d4250..f882f461729 100644
--- a/src/harness/events.ts
+++ b/src/harness/events.ts
@@ -1,4 +1,8 @@
///
+
+// NOTE: The contents of this file are all exported from the namespace 'events'. This is to
+// support the eventual conversion of harness into a modular system.
+
namespace events {
const _events = require("events");
diff --git a/src/harness/harness.ts b/src/harness/harness.ts
index 2ab0b8c7a35..1d17c784783 100644
--- a/src/harness/harness.ts
+++ b/src/harness/harness.ts
@@ -1222,8 +1222,8 @@ namespace Harness {
const fileOutputs = compilation.outputs ? compilation.outputs.map(output => ({
fileName: output.file,
- code: utils.removeByteOrderMark(output.text),
- writeByteOrderMark: utils.getByteOrderMarkLength(output.text) > 0
+ code: core.removeByteOrderMark(output.text),
+ writeByteOrderMark: core.getByteOrderMarkLength(output.text) > 0
})) : [];
const traceResults = compilation.traces && compilation.traces.slice();
@@ -1856,13 +1856,15 @@ namespace Harness {
return opts;
}
- /** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */
- export function makeUnitsFromTest(code: string, fileName: string, rootDir?: string): {
+ export interface TestCaseContent {
settings: CompilerSettings;
testUnitData: TestUnitData[];
tsConfig: ts.ParsedCommandLine;
tsConfigFileUnitData: TestUnitData;
- } {
+ }
+
+ /** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */
+ export function makeUnitsFromTest(code: string, fileName: string, rootDir?: string): TestCaseContent {
const settings = extractCompilerSettings(code);
// List of all the subfiles we've parsed out
diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json
index 26f60116009..8e853bbf3b9 100644
--- a/src/harness/tsconfig.json
+++ b/src/harness/tsconfig.json
@@ -83,9 +83,9 @@
"harness.ts",
+ "core.ts",
"utils.ts",
"events.ts",
- "collections.ts",
"documents.ts",
"vpath.ts",
"vfs.ts",
diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts
index 6e0602998f0..8c042f2afbc 100644
--- a/src/harness/unittests/programMissingFiles.ts
+++ b/src/harness/unittests/programMissingFiles.ts
@@ -42,7 +42,7 @@ namespace ts {
const testCompilerHost = new compiler.CompilerHost(
vfs.VirtualFileSystem.createFromTestFiles(
- { useCaseSensitiveFileNames: false, currentDirectory: "d:\\pretend\\" },
+ { useCaseSensitiveFileNames: false, currentDirectory: "d:\\pretend\\" },
[emptyFile, referenceFile]),
{ newLine: NewLineKind.LineFeed });
diff --git a/src/harness/utils.ts b/src/harness/utils.ts
index 518a1be993f..4d0d43d7fba 100644
--- a/src/harness/utils.ts
+++ b/src/harness/utils.ts
@@ -1,26 +1,9 @@
+///
+
+// 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.
+
namespace utils {
- export function identity(v: T) { return v; }
-
- export function getByteOrderMarkLength(text: string) {
- if (text.length >= 2) {
- 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;
- }
- }
- return 0;
- }
-
- export function removeByteOrderMark(text: string) {
- const length = getByteOrderMarkLength(text);
- return length ? text.slice(length) : text;
- }
-
const leadingCommentRegExp = /^(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+/;
const trailingCommentRegExp = /(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+$/;
const leadingAndTrailingCommentRegExp = /^(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+|(\s*\/\*[^]*?\*\/\s*|\s*\/\/[^\r\n\u2028\u2029]*[\r\n\u2028\u2029]*)+$/g;
diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts
index 643b805a926..da4f4ae190c 100644
--- a/src/harness/vfs.ts
+++ b/src/harness/vfs.ts
@@ -1,13 +1,13 @@
+///
///
-///
+///
///
///
-///
-namespace vfs {
- import KeyedCollection = collections.KeyedCollection;
- import Metadata = collections.Metadata;
- import EventEmitter = events.EventEmitter;
+// 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.
+
+namespace vfs {
export interface PathMappings {
[path: string]: string;
}
@@ -89,7 +89,7 @@ namespace vfs {
recursive: boolean;
}
- export abstract class VirtualFileSystemObject extends EventEmitter {
+ export abstract class VirtualFileSystemObject extends events.EventEmitter {
private _readonly = false;
/**
@@ -121,11 +121,11 @@ namespace vfs {
private _currentDirectory: string;
private _currentDirectoryStack: string[] | undefined;
private _shadowRoot: VirtualFileSystem | undefined;
- private _watchedFiles: KeyedCollection | undefined;
- private _watchedDirectories: KeyedCollection | undefined;
+ private _watchedFiles: core.KeyedCollection | undefined;
+ private _watchedDirectories: core.KeyedCollection | undefined;
private _stringComparer: ts.Comparer | undefined;
private _pathComparer: ts.Comparer | undefined;
- private _metadata: Metadata;
+ private _metadata: core.Metadata;
private _onRootFileSystemChange: (path: string, change: FileSystemChange) => void;
constructor(currentDirectory: string, useCaseSensitiveFileNames: boolean) {
@@ -137,8 +137,8 @@ namespace vfs {
public get stringComparer() {
return this._stringComparer || (this._stringComparer = this.useCaseSensitiveFileNames
- ? collections.compareStringsCaseSensitive
- : collections.compareStringsCaseInsensitive);
+ ? core.compareStringsCaseSensitive
+ : core.compareStringsCaseInsensitive);
}
public get pathComparer() {
@@ -157,8 +157,8 @@ namespace vfs {
/**
* Gets metadata about this file system.
*/
- public get metadata(): Metadata {
- return this._metadata || (this._metadata = new Metadata(this.shadowRoot ? this.shadowRoot.metadata : undefined));
+ public get metadata(): core.Metadata {
+ return this._metadata || (this._metadata = new core.Metadata(this.shadowRoot ? this.shadowRoot.metadata : undefined));
}
/**
@@ -447,7 +447,7 @@ namespace vfs {
public watchFile(path: string, watcher: (path: string, change: FileSystemChange) => void): ts.FileWatcher {
if (!this._watchedFiles) {
const pathComparer = this.useCaseSensitiveFileNames ? vpath.compareCaseSensitive : vpath.compareCaseInsensitive;
- this._watchedFiles = new KeyedCollection(pathComparer);
+ this._watchedFiles = new core.KeyedCollection(pathComparer);
}
path = vpath.resolve(this.currentDirectory, path);
@@ -476,7 +476,7 @@ namespace vfs {
public watchDirectory(path: string, watcher: (path: string) => void, recursive?: boolean) {
if (!this._watchedDirectories) {
const pathComparer = this.useCaseSensitiveFileNames ? vpath.compareCaseSensitive : vpath.compareCaseInsensitive;
- this._watchedDirectories = new KeyedCollection(pathComparer);
+ this._watchedDirectories = new core.KeyedCollection(pathComparer);
}
path = vpath.resolve(this.currentDirectory, path);
@@ -590,7 +590,7 @@ namespace vfs {
export abstract class VirtualFileSystemEntry extends VirtualFileSystemObject {
private _path: string;
- private _metadata: Metadata;
+ private _metadata: core.Metadata;
/**
* Gets the name of this entry.
@@ -623,8 +623,8 @@ namespace vfs {
/**
* Gets metadata about this entry.
*/
- public get metadata(): Metadata {
- return this._metadata || (this._metadata = new Metadata(this.shadowRoot ? this.shadowRoot.metadata : undefined));
+ public get metadata(): core.Metadata {
+ return this._metadata || (this._metadata = new core.Metadata(this.shadowRoot ? this.shadowRoot.metadata : undefined));
}
/**
@@ -724,7 +724,7 @@ namespace vfs {
export class VirtualDirectory extends VirtualFileSystemEntry {
protected _shadowRoot: VirtualDirectory | undefined;
private _parent: VirtualDirectory;
- private _entries: KeyedCollection | undefined;
+ private _entries: core.KeyedCollection | undefined;
private _resolver: FileSystemResolver | undefined;
private _onChildFileSystemChange: (path: string, change: FileSystemChange) => void;
@@ -990,7 +990,7 @@ namespace vfs {
protected getOwnEntries() {
if (!this._entries) {
- const entries = new KeyedCollection(this.fileSystem.stringComparer);
+ const entries = new core.KeyedCollection(this.fileSystem.stringComparer);
const resolver = this._resolver;
const shadowRoot = this._shadowRoot;
if (resolver) {
@@ -1136,8 +1136,8 @@ namespace vfs {
export class VirtualDirectorySymlink extends VirtualDirectory {
private _targetPath: string;
private _target: VirtualDirectory | undefined;
- private _views: KeyedCollection | undefined;
- private _allViews: KeyedCollection | undefined;
+ private _views: core.KeyedCollection | undefined;
+ private _allViews: core.KeyedCollection | undefined;
private _onTargetParentChildRemoved: (entry: VirtualEntry) => void;
private _onTargetChildRemoved: (entry: VirtualEntry) => void;
private _onTargetChildAdded: (entry: VirtualEntry) => void;
@@ -1145,7 +1145,7 @@ namespace vfs {
constructor(parent: VirtualDirectory, name: string, target: string) {
super(parent, name);
- this._views = new KeyedCollection(this.fileSystem.stringComparer);
+ this._views = new core.KeyedCollection(this.fileSystem.stringComparer);
this._targetPath = target;
this._onTargetParentChildRemoved = entry => this.onTargetParentChildRemoved(entry);
this._onTargetChildAdded = entry => this.onTargetChildAdded(entry);
@@ -1225,9 +1225,9 @@ namespace vfs {
return target && target.removeFile(name) || false;
}
- protected getOwnEntries(): KeyedCollection {
+ protected getOwnEntries(): core.KeyedCollection {
if (!this._allViews) {
- this._allViews = new KeyedCollection(this.fileSystem.stringComparer);
+ this._allViews = new core.KeyedCollection(this.fileSystem.stringComparer);
const target = this.target;
if (target) {
for (const entry of target.getEntries()) {
diff --git a/src/harness/vpath.ts b/src/harness/vpath.ts
index 7a7669a4e10..9ad081ac1f5 100644
--- a/src/harness/vpath.ts
+++ b/src/harness/vpath.ts
@@ -1,11 +1,13 @@
///
-namespace vpath {
- // NOTE: Some of the functions here duplicate functionality from compiler/core.ts. They have been added
- // to reduce the number of direct dependencies on compiler and services to eventually break away
- // from depending directly on the compiler to speed up compilation time.
- import compareValues = collections.compareNumbers;
- import compareStrings = collections.compareStrings;
+// NOTE: The contents of this file are all exported from the namespace 'vpath'. This is to
+// support the eventual conversion of harness into a modular system.
+
+// NOTE: Some of the functions here duplicate functionality from compiler/core.ts. They have been
+// added to reduce the number of direct dependencies on compiler and services to eventually
+// break away from depending directly on the compiler to speed up compilation time.
+
+namespace vpath {
/**
* Virtual path separator.
@@ -101,10 +103,7 @@ namespace vpath {
return normalize(combine(path, ...paths));
}
- /**
- * Gets a relative path that can be used to traverse between `from` and `to`.
- */
- export function relative(from: string, to: string, ignoreCase: boolean) {
+ function relativeWorker(from: string, to: string, stringEqualityComparer: core.EqualityComparer) {
if (!isAbsolute(from)) throw new Error("Path not absolute");
if (!isAbsolute(to)) throw new Error("Path not absolute");
@@ -113,7 +112,7 @@ namespace vpath {
let start: number;
for (start = 0; start < fromComponents.length && start < toComponents.length; start++) {
- if (compareStrings(fromComponents[start], toComponents[start], ignoreCase)) {
+ if (stringEqualityComparer(fromComponents[start], toComponents[start])) {
break;
}
}
@@ -130,10 +129,22 @@ namespace vpath {
return format(["", ...components]);
}
+ function relativeCaseSensitive(from: string, to: string) {
+ return relativeWorker(from, to, core.equateStringsCaseSensitive);
+ }
+
+ function relativeCaseInsensitive(from: string, to: string) {
+ return relativeWorker(from, to, core.equateStringsCaseInsensitive);
+ }
+
/**
- * Compare two paths.
+ * Gets a relative path that can be used to traverse between `from` and `to`.
*/
- export function compare(a: string, b: string, ignoreCase: boolean) {
+ export function relative(from: string, to: string, ignoreCase: boolean) {
+ return ignoreCase ? relativeCaseInsensitive(from, to) : relativeCaseSensitive(from, to);
+ }
+
+ function compareWorker(a: string, b: string, stringComparer: core.Comparer) {
if (a === b) return 0;
a = removeTrailingSeparator(a);
b = removeTrailingSeparator(b);
@@ -142,21 +153,32 @@ namespace vpath {
const bComponents = reduce(parse(b));
const len = Math.min(aComponents.length, bComponents.length);
for (let i = 0; i < len; i++) {
- const result = compareStrings(aComponents[i], bComponents[i], ignoreCase);
+ const result = stringComparer(aComponents[i], bComponents[i]);
if (result !== 0) return result;
}
- return compareValues(aComponents.length, bComponents.length);
+ return core.compareNumbers(aComponents.length, bComponents.length);
}
/**
* Performs a case-sensitive comparison of two paths.
*/
- export function compareCaseSensitive(a: string, b: string) { return compare(a, b, /*ignoreCase*/ false); }
+ export function compareCaseSensitive(a: string, b: string) {
+ return compareWorker(a, b, core.compareStringsCaseSensitive);
+ }
/**
* Performs a case-insensitive comparison of two paths.
*/
- export function compareCaseInsensitive(a: string, b: string) { return compare(a, b, /*ignoreCase*/ true); }
+ export function compareCaseInsensitive(a: string, b: string) {
+ return compareWorker(a, b, core.compareStringsCaseInsensitive);
+ }
+
+ /**
+ * Compare two paths.
+ */
+ export function compare(a: string, b: string, ignoreCase: boolean) {
+ return ignoreCase ? compareCaseInsensitive(a, b) : compareCaseSensitive(a, b);
+ }
/**
* Determines whether two strings are equal.
@@ -174,24 +196,35 @@ namespace vpath {
return ignoreCase && a.toUpperCase() === b.toUpperCase();
}
- /**
- * Determines whether the path `descendant` is beneath the path `ancestor`.
- */
- export function beneath(ancestor: string, descendant: string, ignoreCase: boolean) {
+ function beneathWorker(ancestor: string, descendant: string, stringEqualityComparer: ts.EqualityComparer) {
if (!isAbsolute(ancestor)) throw new Error("Path not absolute");
if (!isAbsolute(descendant)) throw new Error("Path not absolute");
const ancestorComponents = reduce(parse(ancestor));
const descendantComponents = reduce(parse(descendant));
if (descendantComponents.length < ancestorComponents.length) return false;
- const equalityComparer = ignoreCase ? collections.equateStringsCaseInsensitive : collections.equateStringsCaseSensitive;
for (let i = 0; i < ancestorComponents.length; i++) {
- if (!equalityComparer(ancestorComponents[i], descendantComponents[i])) {
+ if (!stringEqualityComparer(ancestorComponents[i], descendantComponents[i])) {
return false;
}
}
return true;
}
+ function beneathCaseSensitive(ancestor: string, descendant: string) {
+ return beneathWorker(ancestor, descendant, core.equateStringsCaseSensitive);
+ }
+
+ function beneathCaseInsensitive(ancestor: string, descendant: string) {
+ return beneathWorker(ancestor, descendant, core.equateStringsCaseInsensitive);
+ }
+
+ /**
+ * Determines whether the path `descendant` is beneath the path `ancestor`.
+ */
+ export function beneath(ancestor: string, descendant: string, ignoreCase: boolean) {
+ return ignoreCase ? beneathCaseInsensitive(ancestor, descendant) : beneathCaseSensitive(ancestor, descendant);
+ }
+
/**
* Parse a path into a root component and zero or more path segments.
* If the path is relative, the root component is `""`.
@@ -239,6 +272,21 @@ namespace vpath {
const extRegExp = /\.\w+$/;
+ function extnameWorker(path: string, extensions: string | string[], 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;
+ }
+ }
+ return "";
+ }
+
/**
* Gets the file extension for a path.
*/
@@ -249,19 +297,7 @@ namespace vpath {
export function extname(path: string, extensions: string | string[], ignoreCase: boolean): string;
export function extname(path: string, extensions?: string | string[], ignoreCase?: boolean) {
if (extensions) {
- const manyExtensions = Array.isArray(extensions) ? extensions : undefined;
- const singleExtension = Array.isArray(extensions) ? undefined : extensions;
- const length = manyExtensions ? manyExtensions.length : 1;
- const comparer = ignoreCase ? collections.compareStringsCaseInsensitive : collections.compareStringsCaseSensitive;
- for (let i = 0; i < length; i++) {
- let extension = manyExtensions ? manyExtensions[i] : singleExtension;
- if (!extension.startsWith(".")) extension = "." + extension;
- if (path.length >= extension.length &&
- comparer(path.slice(path.length - extension.length), extension) === 0) {
- return extension;
- }
- }
- return "";
+ return extnameWorker(path, extensions, ignoreCase ? core.equateStringsCaseInsensitive : core.equateStringsCaseSensitive);
}
const match = extRegExp.exec(path);