mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Add support for variations in module/target in compiler tests
This commit is contained in:
@@ -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
@@ -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
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -83,9 +83,9 @@
|
||||
|
||||
"harness.ts",
|
||||
|
||||
"core.ts",
|
||||
"utils.ts",
|
||||
"events.ts",
|
||||
"collections.ts",
|
||||
"documents.ts",
|
||||
"vpath.ts",
|
||||
"vfs.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 });
|
||||
|
||||
|
||||
+5
-22
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user