Add support for variations in module/target in compiler tests

This commit is contained in:
Ron Buckton
2017-11-09 18:35:24 -08:00
parent cf261c34aa
commit 4697ba3e50
12 changed files with 816 additions and 558 deletions
-244
View File
@@ -1,244 +0,0 @@
/// <reference path="./harness.ts" />
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<T>(array: T[], index: number): void {
for (let i = index; i < array.length - 1; i++) {
array[i] = array[i + 1];
}
array.pop();
}
function insertAt<T>(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<K, V> {
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);
}
}
}
}
+34 -36
View File
@@ -1,32 +1,30 @@
/// <reference path="./harness.ts" />
/// <reference path="./documents.ts" />
/// <reference path="./collections.ts" />
/// <reference path="./core.ts" />
/// <reference path="./vpath.ts" />
/// <reference path="./vfs.ts" />
/// <reference path="./utils.ts" />
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<string, ts.SourceFile>;
private _sourceFiles: core.KeyedCollection<string, ts.SourceFile>;
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<string, ts.SourceFile>(this.vfs.pathComparer);
this._sourceFiles = new core.KeyedCollection<string, ts.SourceFile>(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<string, TextDocument>;
public readonly dts: KeyedCollection<string, TextDocument>;
public readonly maps: KeyedCollection<string, TextDocument>;
public readonly js: core.KeyedCollection<string, documents.TextDocument>;
public readonly dts: core.KeyedCollection<string, documents.TextDocument>;
public readonly maps: core.KeyedCollection<string, documents.TextDocument>;
private _inputs: TextDocument[] = [];
private _inputsAndOutputs: KeyedCollection<string, CompilationOutput>;
private _inputs: documents.TextDocument[] = [];
private _inputsAndOutputs: core.KeyedCollection<string, CompilationOutput>;
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<string, TextDocument>(this.vfs.pathComparer);
this.dts = new KeyedCollection<string, TextDocument>(this.vfs.pathComparer);
this.maps = new KeyedCollection<string, TextDocument>(this.vfs.pathComparer);
this.js = new core.KeyedCollection<string, documents.TextDocument>(this.vfs.pathComparer);
this.dts = new core.KeyedCollection<string, documents.TextDocument>(this.vfs.pathComparer);
this.maps = new core.KeyedCollection<string, documents.TextDocument>(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<string, CompilationOutput>(this.vfs.pathComparer);
this._inputsAndOutputs = new core.KeyedCollection<string, CompilationOutput>(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<TextDocument> {
public get inputs(): ReadonlyArray<documents.TextDocument> {
return this._inputs;
}
public get outputs(): ReadonlyArray<TextDocument> {
public get outputs(): ReadonlyArray<documents.TextDocument> {
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);
}
}
+211 -155
View File
@@ -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 };
}
}
+421
View File
@@ -0,0 +1,421 @@
/// <reference path="./harness.ts" />
// 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<T>(v: T): T { return v; }
//
// Comparers
//
export type Comparer<T> = (x: T, y: T) => number;
export type EqualityComparer<T> = (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<K, V> {
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<T, U>(array: ReadonlyArray<T>, value: T, keySelector: (v: T) => U, keyComparer: Comparer<U>, 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<T>(array: T[], index: number): void {
for (let i = index; i < array.length - 1; i++) {
array[i] = array[i + 1];
}
array.length--;
}
export function insertAt<T>(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<T>(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: // <CR> carriage return
if (pos < text.length && text.charCodeAt(pos) === 0x000a) {
pos++;
}
// falls through
case 0x000a: // <LF> line feed
case 0x2028: // <LS> line separator
case 0x2029: // <PS> 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> tab
case 0x000b: // <VT> vertical tab
case 0x000c: // <FF> form feed
case 0x0020: // <SP> space
case 0x00a0: // <NBSP> no-break space
case 0xfeff: // <ZWNBSP> zero width no-break space
case 0x1680: // <USP> ogham space mark
case 0x2000: // <USP> en quad
case 0x2001: // <USP> em quad
case 0x2002: // <USP> en space
case 0x2003: // <USP> em space
case 0x2004: // <USP> three-per-em space
case 0x2005: // <USP> four-per-em space
case 0x2006: // <USP> six-per-em space
case 0x2007: // <USP> figure space
case 0x2008: // <USP> punctuation space
case 0x2009: // <USP> thin space
case 0x200a: // <USP> hair space
case 0x202f: // <USP> narrow no-break space
case 0x205f: // <USP> medium mathematical space
case 0x3000: // <USP> 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<number>;
export interface LinesAndLineStarts {
readonly lines: ReadonlyArray<string>;
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;
}
}
+34 -32
View File
@@ -1,4 +1,7 @@
/// <reference path="harness.ts" />
/// <reference path="./core.ts" />
// 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<string, string>) {
constructor(file: string, text: string, meta?: Map<string, string>) {
this.file = file;
this.text = content;
this.text = text;
this.meta = meta || new Map<string, string>();
}
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<Mapping> = [];
public readonly names: ReadonlyArray<string> | 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;
}
}
+4
View File
@@ -1,4 +1,8 @@
/// <reference path="./harness.ts" />
// 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");
+7 -5
View File
@@ -1222,8 +1222,8 @@ namespace Harness {
const fileOutputs = compilation.outputs ? compilation.outputs.map(output => (<GeneratedFile>{
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
+1 -1
View File
@@ -83,9 +83,9 @@
"harness.ts",
"core.ts",
"utils.ts",
"events.ts",
"collections.ts",
"documents.ts",
"vpath.ts",
"vfs.ts",
+1 -1
View File
@@ -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 });
+5 -22
View File
@@ -1,26 +1,9 @@
/// <reference path="./core.ts" />
// 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<T>(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;
+26 -26
View File
@@ -1,13 +1,13 @@
/// <reference path="../compiler/commandLineParser.ts"/>
/// <reference path="./harness.ts" />
/// <reference path="./collections.ts" />
/// <reference path="./core.ts" />
/// <reference path="./vpath.ts" />
/// <reference path="./events.ts" />
/// <reference path="../compiler/commandLineParser.ts"/>
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<string, FileWatcherEntry[]> | undefined;
private _watchedDirectories: KeyedCollection<string, DirectoryWatcherEntryArray> | undefined;
private _watchedFiles: core.KeyedCollection<string, FileWatcherEntry[]> | undefined;
private _watchedDirectories: core.KeyedCollection<string, DirectoryWatcherEntryArray> | undefined;
private _stringComparer: ts.Comparer<string> | undefined;
private _pathComparer: ts.Comparer<string> | 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<string, FileWatcherEntry[]>(pathComparer);
this._watchedFiles = new core.KeyedCollection<string, FileWatcherEntry[]>(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<string, DirectoryWatcherEntryArray>(pathComparer);
this._watchedDirectories = new core.KeyedCollection<string, DirectoryWatcherEntryArray>(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<string, VirtualEntry> | undefined;
private _entries: core.KeyedCollection<string, VirtualEntry> | 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<string, VirtualEntry>(this.fileSystem.stringComparer);
const entries = new core.KeyedCollection<string, VirtualEntry>(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<string, VirtualEntryView> | undefined;
private _allViews: KeyedCollection<string, VirtualEntryView> | undefined;
private _views: core.KeyedCollection<string, VirtualEntryView> | undefined;
private _allViews: core.KeyedCollection<string, VirtualEntryView> | 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<string, VirtualEntryView>(this.fileSystem.stringComparer);
this._views = new core.KeyedCollection<string, VirtualEntryView>(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<string, VirtualEntryView> {
protected getOwnEntries(): core.KeyedCollection<string, VirtualEntryView> {
if (!this._allViews) {
this._allViews = new KeyedCollection<string, VirtualEntryView>(this.fileSystem.stringComparer);
this._allViews = new core.KeyedCollection<string, VirtualEntryView>(this.fileSystem.stringComparer);
const target = this.target;
if (target) {
for (const entry of target.getEntries()) {
+72 -36
View File
@@ -1,11 +1,13 @@
/// <reference path="./harness.ts" />
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<string>) {
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<string>) {
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<string>) {
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<string>) {
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);