mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Reorganize and remove duplication
This commit is contained in:
parent
bb26ab5556
commit
c9c562afac
@ -1,5 +1,4 @@
|
||||
/// <reference path="./functions.ts" />
|
||||
namespace core {
|
||||
namespace collections {
|
||||
export interface SortOptions<T> {
|
||||
comparer: (a: T, b: T) => number;
|
||||
sort: "insertion" | "comparison";
|
||||
@ -43,16 +42,16 @@ namespace core {
|
||||
}
|
||||
|
||||
public has(key: K) {
|
||||
return binarySearch(this._keys, key, identity, this._comparer) >= 0;
|
||||
return ts.binarySearch(this._keys, key, ts.identity, this._comparer) >= 0;
|
||||
}
|
||||
|
||||
public get(key: K) {
|
||||
const index = binarySearch(this._keys, key, identity, this._comparer);
|
||||
const index = ts.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, identity, this._comparer);
|
||||
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
|
||||
if (index >= 0) {
|
||||
this._values[index] = value;
|
||||
}
|
||||
@ -67,12 +66,12 @@ namespace core {
|
||||
}
|
||||
|
||||
public delete(key: K) {
|
||||
const index = binarySearch(this._keys, key, identity, this._comparer);
|
||||
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
|
||||
if (index >= 0) {
|
||||
this.writePreamble();
|
||||
removeAt(this._keys, index);
|
||||
removeAt(this._values, index);
|
||||
if (this._order) removeAt(this._order, index);
|
||||
ts.orderedRemoveItemAt(this._keys, index);
|
||||
ts.orderedRemoveItemAt(this._values, index);
|
||||
if (this._order) ts.orderedRemoveItemAt(this._order, index);
|
||||
this.writePostScript();
|
||||
return true;
|
||||
}
|
||||
@ -211,226 +210,6 @@ namespace core {
|
||||
}
|
||||
}
|
||||
|
||||
export class SortedSet<T> {
|
||||
private _comparer: (a: T, b: T) => number;
|
||||
private _values: T[] = [];
|
||||
private _order: number[] | undefined;
|
||||
private _version = 0;
|
||||
private _copyOnWrite = false;
|
||||
|
||||
constructor(comparer: ((a: T, b: T) => number) | SortOptions<T>, iterable?: Iterable<T>) {
|
||||
this._comparer = typeof comparer === "object" ? comparer.comparer : comparer;
|
||||
this._order = typeof comparer === "object" && comparer.sort === "insertion" ? [] : undefined;
|
||||
|
||||
if (iterable) {
|
||||
const iterator = getIterator(iterable);
|
||||
try {
|
||||
for (let i = nextResult(iterator); i; i = nextResult(iterator)) {
|
||||
const value = i.value;
|
||||
this.add(value);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
closeIterator(iterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get size() {
|
||||
return this._values.length;
|
||||
}
|
||||
|
||||
public get comparer() {
|
||||
return this._comparer;
|
||||
}
|
||||
|
||||
public get [Symbol.toStringTag]() {
|
||||
return "SortedSet";
|
||||
}
|
||||
|
||||
public has(value: T) {
|
||||
return binarySearch(this._values, value, identity, this._comparer) >= 0;
|
||||
}
|
||||
|
||||
public add(value: T) {
|
||||
const index = binarySearch(this._values, value, identity, this._comparer);
|
||||
if (index < 0) {
|
||||
this.writePreamble();
|
||||
insertAt(this._values, ~index, value);
|
||||
if (this._order) insertAt(this._order, ~index, this._version);
|
||||
this.writePostScript();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public delete(value: T) {
|
||||
const index = binarySearch(this._values, value, identity, this._comparer);
|
||||
if (index >= 0) {
|
||||
this.writePreamble();
|
||||
removeAt(this._values, index);
|
||||
if (this._order) removeAt(this._order, index);
|
||||
this.writePostScript();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
if (this.size > 0) {
|
||||
this.writePreamble();
|
||||
this._values.length = 0;
|
||||
if (this._order) this._order.length = 0;
|
||||
this.writePostScript();
|
||||
}
|
||||
}
|
||||
|
||||
public forEach(callback: (value: T, key: T, collection: this) => void, thisArg?: any) {
|
||||
const values = this._values;
|
||||
const indices = this.getIterationOrder();
|
||||
const version = this._version;
|
||||
this._copyOnWrite = true;
|
||||
try {
|
||||
if (indices) {
|
||||
for (const i of indices) {
|
||||
callback.call(thisArg, values[i], values[i], this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const value of values) {
|
||||
callback.call(thisArg, value, value, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (version === this._version) {
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public keys() {
|
||||
return this.values();
|
||||
}
|
||||
|
||||
public * values() {
|
||||
const values = this._values;
|
||||
const indices = this.getIterationOrder();
|
||||
const version = this._version;
|
||||
this._copyOnWrite = true;
|
||||
try {
|
||||
if (indices) {
|
||||
for (const i of indices) {
|
||||
yield values[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const value of values) {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (version === this._version) {
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public * entries() {
|
||||
const values = this._values;
|
||||
const indices = this.getIterationOrder();
|
||||
const version = this._version;
|
||||
this._copyOnWrite = true;
|
||||
try {
|
||||
if (indices) {
|
||||
for (const i of indices) {
|
||||
yield [values[i], values[i]] as [T, T];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const value of values) {
|
||||
yield [value, value] as [T, T];
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (version === this._version) {
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public [Symbol.iterator]() {
|
||||
return this.values();
|
||||
}
|
||||
|
||||
private writePreamble() {
|
||||
if (this._copyOnWrite) {
|
||||
this._values = this._values.slice();
|
||||
if (this._order) this._order = this._order.slice();
|
||||
this._copyOnWrite = false;
|
||||
}
|
||||
}
|
||||
|
||||
private writePostScript() {
|
||||
this._version++;
|
||||
}
|
||||
|
||||
private getIterationOrder() {
|
||||
if (this._order) {
|
||||
const order = this._order;
|
||||
return this._order
|
||||
.map((_, i) => i)
|
||||
.sort((x, y) => order[x] - order[y]);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function binarySearch<T, U>(array: ReadonlyArray<T>, value: T, keySelector: (v: T) => U, keyComparer: (a: U, b: U) => number, 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 {
|
||||
if (index < 0 || index >= array.length) {
|
||||
return;
|
||||
}
|
||||
else if (index === 0) {
|
||||
array.shift();
|
||||
}
|
||||
else if (index === array.length - 1) {
|
||||
array.pop();
|
||||
}
|
||||
else {
|
||||
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);
|
||||
@ -1,14 +1,6 @@
|
||||
/// <reference path="./harness.ts" />
|
||||
/// <reference path="./documents.ts" />
|
||||
/// <reference path="./core.ts" />
|
||||
/// <reference path="./vpath.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
/// <reference path="./utils.ts" />
|
||||
/// <reference path="./fakes.ts" />
|
||||
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* Test harness compiler functionality.
|
||||
*/
|
||||
namespace compiler {
|
||||
export interface Project {
|
||||
file: string;
|
||||
@ -64,7 +56,7 @@ namespace compiler {
|
||||
public readonly maps: ReadonlyMap<string, documents.TextDocument>;
|
||||
|
||||
private _inputs: documents.TextDocument[] = [];
|
||||
private _inputsAndOutputs: core.SortedMap<string, CompilationOutput>;
|
||||
private _inputsAndOutputs: collections.SortedMap<string, CompilationOutput>;
|
||||
|
||||
constructor(host: fakes.CompilerHost, options: ts.CompilerOptions, program: ts.Program | undefined, result: ts.EmitResult | undefined, diagnostics: ts.Diagnostic[]) {
|
||||
this.host = host;
|
||||
@ -74,9 +66,9 @@ namespace compiler {
|
||||
this.options = program ? program.getCompilerOptions() : options;
|
||||
|
||||
// collect outputs
|
||||
const js = this.js = new core.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const dts = this.dts = new core.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const maps = this.maps = new core.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const js = this.js = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const dts = this.dts = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
const maps = this.maps = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
for (const document of this.host.outputs) {
|
||||
if (vpath.isJavaScript(document.file)) {
|
||||
js.set(document.file, document);
|
||||
@ -90,7 +82,7 @@ namespace compiler {
|
||||
}
|
||||
|
||||
// correlate inputs and outputs
|
||||
this._inputsAndOutputs = new core.SortedMap<string, CompilationOutput>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
this._inputsAndOutputs = new collections.SortedMap<string, CompilationOutput>({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
if (program) {
|
||||
if (this.options.out || this.options.outFile) {
|
||||
const outFile = vpath.resolve(this.vfs.cwd(), this.options.outFile || this.options.out);
|
||||
|
||||
@ -273,8 +273,8 @@ class CompilerTest {
|
||||
}
|
||||
|
||||
private makeUnitName(name: string, root: string) {
|
||||
const path = ts.toPath(name, root, core.identity);
|
||||
const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", core.identity);
|
||||
const path = ts.toPath(name, root, ts.identity);
|
||||
const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", ts.identity);
|
||||
return pathStart ? path.replace(pathStart, "/") : path;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
/// <reference path="./core/functions.ts" />
|
||||
/// <reference path="./core/comparers.ts" />
|
||||
/// <reference path="./core/collections.ts" />
|
||||
/// <reference path="./core/strings.ts" />
|
||||
@ -1,43 +0,0 @@
|
||||
namespace core {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
namespace core {
|
||||
export function identity<T>(v: T): T { return v; }
|
||||
}
|
||||
@ -1,130 +0,0 @@
|
||||
namespace core {
|
||||
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 getByteOrderMark(text: string): string {
|
||||
const length = getByteOrderMarkLength(text);
|
||||
return length > 0 ? text.slice(0, length) : "";
|
||||
}
|
||||
|
||||
export function getByteOrderMarkLength(text: string): number {
|
||||
if (text.length >= 1) {
|
||||
const ch0 = text.charCodeAt(0);
|
||||
if (ch0 === 0xfeff) return 1;
|
||||
if (ch0 === 0xfe) return text.length >= 2 && text.charCodeAt(1) === 0xff ? 2 : 0;
|
||||
if (ch0 === 0xff) return text.length >= 2 && text.charCodeAt(1) === 0xfe ? 2 : 0;
|
||||
if (ch0 === 0xef) return text.length >= 3 && text.charCodeAt(1) === 0xbb && text.charCodeAt(2) === 0xbf ? 3 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function removeByteOrderMark(text: string): string {
|
||||
const length = getByteOrderMarkLength(text);
|
||||
return length ? text.slice(length) : text;
|
||||
}
|
||||
|
||||
export function addUTF8ByteOrderMark(text: string) {
|
||||
return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text;
|
||||
}
|
||||
|
||||
function splitLinesWorker(text: string, lineStarts: number[] | undefined, lines: string[] | undefined, removeEmptyElements: boolean) {
|
||||
let pos = 0;
|
||||
let end = 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
/// <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.
|
||||
|
||||
@ -9,7 +7,7 @@ namespace documents {
|
||||
public readonly file: string;
|
||||
public readonly text: string;
|
||||
|
||||
private _lineStarts: core.LineStarts | undefined;
|
||||
private _lineStarts: ReadonlyArray<number> | undefined;
|
||||
private _testFile: Harness.Compiler.TestFile | undefined;
|
||||
|
||||
constructor(file: string, text: string, meta?: Map<string, string>) {
|
||||
@ -18,8 +16,8 @@ namespace documents {
|
||||
this.meta = meta || new Map<string, string>();
|
||||
}
|
||||
|
||||
public get lineStarts(): core.LineStarts {
|
||||
return this._lineStarts || (this._lineStarts = core.computeLineStarts(this.text));
|
||||
public get lineStarts(): ReadonlyArray<number> {
|
||||
return this._lineStarts || (this._lineStarts = ts.computeLineStarts(this.text));
|
||||
}
|
||||
|
||||
public static fromTestFile(file: Harness.Compiler.TestFile) {
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
/// <reference path="./core.ts" />
|
||||
/// <reference path="./utils.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
|
||||
// NOTE: The contents of this file are all exported from the namespace 'fakes'. This is to
|
||||
// support the eventual conversion of harness into a modular system.
|
||||
|
||||
// harness fakes
|
||||
/**
|
||||
* Fake implementations of various compiler dependencies.
|
||||
*/
|
||||
namespace fakes {
|
||||
const processExitSentinel = new Error("System exit");
|
||||
|
||||
@ -45,8 +40,8 @@ namespace fakes {
|
||||
try {
|
||||
const content = this.vfs.readFileSync(path, "utf8");
|
||||
return content === undefined ? undefined :
|
||||
vpath.extname(path) === ".json" ? utils.removeComments(core.removeByteOrderMark(content), utils.CommentRemoval.leadingAndTrailing) :
|
||||
core.removeByteOrderMark(content);
|
||||
vpath.extname(path) === ".json" ? utils.removeComments(utils.removeByteOrderMark(content), utils.CommentRemoval.leadingAndTrailing) :
|
||||
utils.removeByteOrderMark(content);
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
@ -55,7 +50,7 @@ namespace fakes {
|
||||
|
||||
public writeFile(path: string, data: string, writeByteOrderMark?: boolean): void {
|
||||
this.vfs.mkdirpSync(vpath.dirname(path));
|
||||
this.vfs.writeFileSync(path, writeByteOrderMark ? core.addUTF8ByteOrderMark(data) : data);
|
||||
this.vfs.writeFileSync(path, writeByteOrderMark ? utils.addUTF8ByteOrderMark(data) : data);
|
||||
}
|
||||
|
||||
public fileExists(path: string) {
|
||||
@ -212,7 +207,7 @@ namespace fakes {
|
||||
public readonly shouldAssertInvariants = !Harness.lightMode;
|
||||
|
||||
private _setParentNodes: boolean;
|
||||
private _sourceFiles: core.SortedMap<string, ts.SourceFile>;
|
||||
private _sourceFiles: collections.SortedMap<string, ts.SourceFile>;
|
||||
private _parseConfigHost: ParseConfigHost;
|
||||
private _newLine: string;
|
||||
|
||||
@ -221,7 +216,7 @@ namespace fakes {
|
||||
this.sys = sys;
|
||||
this.defaultLibLocation = sys.vfs.meta.get("defaultLibLocation") || "";
|
||||
this._newLine = ts.getNewLineCharacter(options, () => this.sys.newLine);
|
||||
this._sourceFiles = new core.SortedMap<string, ts.SourceFile>({ comparer: sys.vfs.stringComparer, sort: "insertion" });
|
||||
this._sourceFiles = new collections.SortedMap<string, ts.SourceFile>({ comparer: sys.vfs.stringComparer, sort: "insertion" });
|
||||
this._setParentNodes = setParentNodes;
|
||||
}
|
||||
|
||||
@ -266,7 +261,7 @@ namespace fakes {
|
||||
}
|
||||
|
||||
public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) {
|
||||
if (writeByteOrderMark) content = core.addUTF8ByteOrderMark(content);
|
||||
if (writeByteOrderMark) content = utils.addUTF8ByteOrderMark(content);
|
||||
this.sys.writeFile(fileName, content);
|
||||
|
||||
const document = new documents.TextDocument(fileName, content);
|
||||
|
||||
@ -678,9 +678,9 @@ namespace Harness {
|
||||
function createBrowserIO(): IO {
|
||||
const serverRoot = new URL("http://localhost:8888/");
|
||||
|
||||
class HttpHeaders extends core.SortedMap<string, string | string[]> {
|
||||
class HttpHeaders extends collections.SortedMap<string, string | string[]> {
|
||||
constructor(template?: Record<string, string | string[]>) {
|
||||
super(core.compareStringsCaseInsensitive);
|
||||
super(ts.compareStringsCaseInsensitive);
|
||||
if (template) {
|
||||
for (const key in template) {
|
||||
if (ts.hasProperty(template, key)) {
|
||||
@ -1237,7 +1237,7 @@ namespace Harness {
|
||||
}
|
||||
|
||||
const docs = inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile);
|
||||
const fs = vfs.FileSystem.createFromFileSystem(IO, !useCaseSensitiveFileNames, { documents: docs, cwd: currentDirectory });
|
||||
const fs = vfs.createFromFileSystem(IO, !useCaseSensitiveFileNames, { documents: docs, cwd: currentDirectory });
|
||||
const host = new fakes.CompilerHost(fs, options);
|
||||
return compiler.compileFiles(host, programFileNames, options);
|
||||
}
|
||||
@ -1286,7 +1286,7 @@ namespace Harness {
|
||||
else if (vpath.isTypeScript(file.unitName)) {
|
||||
const declFile = findResultCodeFile(file.unitName);
|
||||
if (declFile && !findUnit(declFile.file, declInputFiles) && !findUnit(declFile.file, declOtherFiles)) {
|
||||
dtsFiles.push({ unitName: declFile.file, content: core.removeByteOrderMark(declFile.text) });
|
||||
dtsFiles.push({ unitName: declFile.file, content: utils.removeByteOrderMark(declFile.text) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1733,7 +1733,7 @@ namespace Harness {
|
||||
const dupeCase = ts.createMap<number>();
|
||||
// Yield them
|
||||
for (const outputFile of files) {
|
||||
yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + core.removeByteOrderMark(outputFile.text)];
|
||||
yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + utils.removeByteOrderMark(outputFile.text)];
|
||||
}
|
||||
|
||||
function cleanName(fn: string) {
|
||||
|
||||
@ -119,11 +119,11 @@ namespace Harness.LanguageService {
|
||||
export abstract class LanguageServiceAdapterHost {
|
||||
public readonly sys = new fakes.System(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot }));
|
||||
public typesRegistry: ts.Map<void> | undefined;
|
||||
private scriptInfos: core.SortedMap<string, ScriptInfo>;
|
||||
private scriptInfos: collections.SortedMap<string, ScriptInfo>;
|
||||
|
||||
constructor(protected cancellationToken = DefaultHostCancellationToken.instance,
|
||||
protected settings = ts.getDefaultCompilerOptions()) {
|
||||
this.scriptInfos = new core.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
this.scriptInfos = new collections.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" });
|
||||
}
|
||||
|
||||
public get vfs() {
|
||||
|
||||
@ -193,7 +193,7 @@ namespace project {
|
||||
assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message);
|
||||
}
|
||||
|
||||
const fs = vfs.FileSystem.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
|
||||
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
|
||||
fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO));
|
||||
fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot));
|
||||
fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot));
|
||||
@ -393,7 +393,7 @@ namespace project {
|
||||
}
|
||||
});
|
||||
|
||||
const _vfs = vfs.FileSystem.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, {
|
||||
const _vfs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, {
|
||||
documents: allInputFiles,
|
||||
cwd: vpath.combine(vfs.srcFolder, this.testCase.projectRoot)
|
||||
});
|
||||
|
||||
@ -317,7 +317,7 @@ namespace Harness.SourceMapRecorder {
|
||||
const startPos = lineMap[line];
|
||||
const endPos = lineMap[line + 1];
|
||||
const text = code.substring(startPos, endPos);
|
||||
return line === 0 ? core.removeByteOrderMark(text) : text;
|
||||
return line === 0 ? utils.removeByteOrderMark(text) : text;
|
||||
}
|
||||
|
||||
function writeJsFileLines(endJsLine: number) {
|
||||
|
||||
@ -134,12 +134,7 @@
|
||||
"../server/session.ts",
|
||||
"../server/scriptVersionCache.ts",
|
||||
|
||||
"./core/functions.ts",
|
||||
"./core/comparers.ts",
|
||||
"./core/collections.ts",
|
||||
"./core/strings.ts",
|
||||
"core.ts",
|
||||
|
||||
"collections.ts",
|
||||
"utils.ts",
|
||||
"documents.ts",
|
||||
"vpath.ts",
|
||||
|
||||
@ -37,7 +37,7 @@ namespace ts {
|
||||
);
|
||||
|
||||
const testCompilerHost = new fakes.CompilerHost(
|
||||
vfs.FileSystem.createFromFileSystem(
|
||||
vfs.createFromFileSystem(
|
||||
Harness.IO,
|
||||
/*ignoreCase*/ true,
|
||||
{ documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }),
|
||||
|
||||
@ -14,7 +14,7 @@ describe("Public APIs", () => {
|
||||
});
|
||||
|
||||
it("should compile", () => {
|
||||
const fs = vfs.FileSystem.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
|
||||
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
|
||||
fs.linkSync(`${vfs.builtFolder}/${fileName}`, `${vfs.srcFolder}/${fileName}`);
|
||||
const sys = new fakes.System(fs);
|
||||
const host = new fakes.CompilerHost(sys);
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
/// <reference path="./core.ts" />
|
||||
|
||||
// NOTE: The contents of this file are all exported from the namespace 'utils'. This is to
|
||||
// support the eventual conversion of harness into a modular system.
|
||||
|
||||
/**
|
||||
* Common utilities
|
||||
*/
|
||||
namespace utils {
|
||||
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]*)+$/;
|
||||
@ -34,28 +32,6 @@ namespace utils {
|
||||
return text !== undefined ? text.replace(testPathPrefixRegExp, "") : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* SameValueZero (from ECMAScript spec), which has stricter equality sematics than "==" or "===".
|
||||
*/
|
||||
export function is(x: any, y: any) {
|
||||
return (x === y) ? (x !== 0 || 1 / x === 1 / y) : (x !== x && y !== y);
|
||||
}
|
||||
|
||||
export interface Theory {
|
||||
title: string;
|
||||
args: any[];
|
||||
}
|
||||
|
||||
export function theory(name: string, data: (Theory | any[])[], callback: (...args: any[]) => any) {
|
||||
describe(name, () => {
|
||||
for (const theory of data) {
|
||||
const title = Array.isArray(theory) ? theory.toString() : theory.title;
|
||||
const args = Array.isArray(theory) ? theory : theory.args;
|
||||
it(title, () => callback(...args));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes leading indentation from a template literal string.
|
||||
*/
|
||||
@ -112,14 +88,27 @@ namespace utils {
|
||||
return indentation;
|
||||
}
|
||||
|
||||
export function checkFileNames(caption: string, actualFileNames: ReadonlyArray<string>, expectedFileNames: string[]) {
|
||||
assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${expectedFileNames}, got ${actualFileNames}`);
|
||||
for (const f of expectedFileNames) {
|
||||
assert.isTrue(actualFileNames.indexOf(f) >= 0, `${caption}: expected to find ${f} in ${actualFileNames}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function toUtf8(text: string): string {
|
||||
return new Buffer(text).toString("utf8");
|
||||
}
|
||||
|
||||
export function getByteOrderMarkLength(text: string): number {
|
||||
if (text.length >= 1) {
|
||||
const ch0 = text.charCodeAt(0);
|
||||
if (ch0 === 0xfeff) return 1;
|
||||
if (ch0 === 0xfe) return text.length >= 2 && text.charCodeAt(1) === 0xff ? 2 : 0;
|
||||
if (ch0 === 0xff) return text.length >= 2 && text.charCodeAt(1) === 0xfe ? 2 : 0;
|
||||
if (ch0 === 0xef) return text.length >= 3 && text.charCodeAt(1) === 0xbb && text.charCodeAt(2) === 0xbf ? 3 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function removeByteOrderMark(text: string): string {
|
||||
const length = getByteOrderMarkLength(text);
|
||||
return length ? text.slice(length) : text;
|
||||
}
|
||||
|
||||
export function addUTF8ByteOrderMark(text: string) {
|
||||
return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text;
|
||||
}
|
||||
}
|
||||
@ -40,9 +40,9 @@ namespace vfs {
|
||||
|
||||
// lazy-initialized state that should be mutable even if the FileSystem is frozen.
|
||||
private _lazy: {
|
||||
links?: core.SortedMap<string, Inode>;
|
||||
links?: collections.SortedMap<string, Inode>;
|
||||
shadows?: Map<number, Inode>;
|
||||
meta?: core.Metadata;
|
||||
meta?: collections.Metadata;
|
||||
} = {};
|
||||
|
||||
private _cwd: string; // current working directory
|
||||
@ -68,16 +68,16 @@ namespace vfs {
|
||||
|
||||
let cwd = options.cwd;
|
||||
if ((!cwd || !vpath.isRoot(cwd)) && this._lazy.links) {
|
||||
const iterator = core.getIterator(this._lazy.links.keys());
|
||||
const iterator = collections.getIterator(this._lazy.links.keys());
|
||||
try {
|
||||
for (let i = core.nextResult(iterator); i; i = core.nextResult(iterator)) {
|
||||
for (let i = collections.nextResult(iterator); i; i = collections.nextResult(iterator)) {
|
||||
const name = i.value;
|
||||
cwd = cwd ? vpath.resolve(name, cwd) : name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
core.closeIterator(iterator);
|
||||
collections.closeIterator(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,9 +92,9 @@ namespace vfs {
|
||||
/**
|
||||
* Gets metadata for this `FileSystem`.
|
||||
*/
|
||||
public get meta(): core.Metadata {
|
||||
public get meta(): collections.Metadata {
|
||||
if (!this._lazy.meta) {
|
||||
this._lazy.meta = new core.Metadata(this._shadowRoot ? this._shadowRoot.meta : undefined);
|
||||
this._lazy.meta = new collections.Metadata(this._shadowRoot ? this._shadowRoot.meta : undefined);
|
||||
}
|
||||
return this._lazy.meta;
|
||||
}
|
||||
@ -121,40 +121,6 @@ namespace vfs {
|
||||
return this._shadowRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a virtual file system from a physical file system using the following path mappings:
|
||||
*
|
||||
* - `/.ts` is a directory mapped to `${workspaceRoot}/built/local`
|
||||
* - `/.lib` is a directory mapped to `${workspaceRoot}/tests/lib`
|
||||
* - `/.src` is a virtual directory to be used for tests.
|
||||
*
|
||||
* Unless overridden, `/.src` will be the current working directory for the virtual file system.
|
||||
*/
|
||||
public static createFromFileSystem(host: FileSystemResolverHost, ignoreCase: boolean, { documents, cwd }: FileSystemCreateOptions = {}) {
|
||||
const fs = getBuiltLocal(host, ignoreCase).shadow();
|
||||
if (cwd) {
|
||||
fs.mkdirpSync(cwd);
|
||||
fs.chdir(cwd);
|
||||
}
|
||||
if (documents) {
|
||||
for (const document of documents) {
|
||||
fs.mkdirpSync(vpath.dirname(document.file));
|
||||
fs.writeFileSync(document.file, document.text, "utf8");
|
||||
fs.filemeta(document.file).set("document", document);
|
||||
// Add symlinks
|
||||
const symlink = document.meta.get("symlink");
|
||||
if (symlink) {
|
||||
for (const link of symlink.split(",").map(link => link.trim())) {
|
||||
fs.mkdirpSync(vpath.dirname(link));
|
||||
fs.symlinkSync(document.file, link);
|
||||
fs.filemeta(link).set("document", document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a shadow copy of this file system. Changes to the shadow copy do not affect the
|
||||
* original, allowing multiple copies of the same core file system without multiple copies
|
||||
@ -190,16 +156,16 @@ namespace vfs {
|
||||
* Gets the metadata object for a path.
|
||||
* @param path
|
||||
*/
|
||||
public filemeta(path: string): core.Metadata {
|
||||
public filemeta(path: string): collections.Metadata {
|
||||
const { node } = this._walk(this._resolve(path));
|
||||
if (!node) throw createIOError("ENOENT");
|
||||
return this._filemeta(node);
|
||||
}
|
||||
|
||||
private _filemeta(node: Inode): core.Metadata {
|
||||
private _filemeta(node: Inode): collections.Metadata {
|
||||
if (!node.meta) {
|
||||
const parentMeta = node.shadowRoot && this._shadowRoot && this._shadowRoot._filemeta(node.shadowRoot);
|
||||
node.meta = new core.Metadata(parentMeta);
|
||||
node.meta = new collections.Metadata(parentMeta);
|
||||
}
|
||||
return node.meta;
|
||||
}
|
||||
@ -390,10 +356,10 @@ namespace vfs {
|
||||
*/
|
||||
public debugPrint(): void {
|
||||
let result = "";
|
||||
const printLinks = (dirname: string | undefined, links: core.SortedMap<string, Inode>) => {
|
||||
const iterator = core.getIterator(links);
|
||||
const printLinks = (dirname: string | undefined, links: collections.SortedMap<string, Inode>) => {
|
||||
const iterator = collections.getIterator(links);
|
||||
try {
|
||||
for (let i = core.nextResult(iterator); i; i = core.nextResult(iterator)) {
|
||||
for (let i = collections.nextResult(iterator); i; i = collections.nextResult(iterator)) {
|
||||
const [name, node] = i.value;
|
||||
const path = dirname ? vpath.combine(dirname, name) : name;
|
||||
const marker = vpath.compare(this._cwd, path, this.ignoreCase) === 0 ? "*" : " ";
|
||||
@ -412,7 +378,7 @@ namespace vfs {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
core.closeIterator(iterator);
|
||||
collections.closeIterator(iterator);
|
||||
}
|
||||
};
|
||||
printLinks(/*dirname*/ undefined, this._getRootLinks());
|
||||
@ -686,7 +652,7 @@ namespace vfs {
|
||||
};
|
||||
}
|
||||
|
||||
private _addLink(parent: DirectoryInode | undefined, links: core.SortedMap<string, Inode>, name: string, node: Inode, time = this.time()) {
|
||||
private _addLink(parent: DirectoryInode | undefined, links: collections.SortedMap<string, Inode>, name: string, node: Inode, time = this.time()) {
|
||||
links.set(name, node);
|
||||
node.nlink++;
|
||||
node.ctimeMs = time;
|
||||
@ -694,14 +660,14 @@ namespace vfs {
|
||||
if (!parent && !this._cwd) this._cwd = name;
|
||||
}
|
||||
|
||||
private _removeLink(parent: DirectoryInode | undefined, links: core.SortedMap<string, Inode>, name: string, node: Inode, time = this.time()) {
|
||||
private _removeLink(parent: DirectoryInode | undefined, links: collections.SortedMap<string, Inode>, name: string, node: Inode, time = this.time()) {
|
||||
links.delete(name);
|
||||
node.nlink--;
|
||||
node.ctimeMs = time;
|
||||
if (parent) parent.mtimeMs = time;
|
||||
}
|
||||
|
||||
private _replaceLink(oldParent: DirectoryInode, oldLinks: core.SortedMap<string, Inode>, oldName: string, newParent: DirectoryInode, newLinks: core.SortedMap<string, Inode>, newName: string, node: Inode, time: number) {
|
||||
private _replaceLink(oldParent: DirectoryInode, oldLinks: collections.SortedMap<string, Inode>, oldName: string, newParent: DirectoryInode, newLinks: collections.SortedMap<string, Inode>, newName: string, node: Inode, time: number) {
|
||||
if (oldParent !== newParent) {
|
||||
this._removeLink(oldParent, oldLinks, oldName, node, time);
|
||||
this._addLink(newParent, newLinks, newName, node, time);
|
||||
@ -716,7 +682,7 @@ namespace vfs {
|
||||
|
||||
private _getRootLinks() {
|
||||
if (!this._lazy.links) {
|
||||
this._lazy.links = new core.SortedMap<string, Inode>(this.stringComparer);
|
||||
this._lazy.links = new collections.SortedMap<string, Inode>(this.stringComparer);
|
||||
if (this._shadowRoot) {
|
||||
this._copyShadowLinks(this._shadowRoot._getRootLinks(), this._lazy.links);
|
||||
}
|
||||
@ -727,7 +693,7 @@ namespace vfs {
|
||||
|
||||
private _getLinks(node: DirectoryInode) {
|
||||
if (!node.links) {
|
||||
const links = new core.SortedMap<string, Inode>(this.stringComparer);
|
||||
const links = new collections.SortedMap<string, Inode>(this.stringComparer);
|
||||
const { source, resolver } = node;
|
||||
if (source && resolver) {
|
||||
node.source = undefined;
|
||||
@ -786,16 +752,16 @@ namespace vfs {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
private _copyShadowLinks(source: ReadonlyMap<string, Inode>, target: core.SortedMap<string, Inode>) {
|
||||
const iterator = core.getIterator(source);
|
||||
private _copyShadowLinks(source: ReadonlyMap<string, Inode>, target: collections.SortedMap<string, Inode>) {
|
||||
const iterator = collections.getIterator(source);
|
||||
try {
|
||||
for (let i = core.nextResult(iterator); i; i = core.nextResult(iterator)) {
|
||||
for (let i = collections.nextResult(iterator); i; i = collections.nextResult(iterator)) {
|
||||
const [name, root] = i.value;
|
||||
target.set(name, this._getShadow(root));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
core.closeIterator(iterator);
|
||||
collections.closeIterator(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1035,6 +1001,40 @@ namespace vfs {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a virtual file system from a physical file system using the following path mappings:
|
||||
*
|
||||
* - `/.ts` is a directory mapped to `${workspaceRoot}/built/local`
|
||||
* - `/.lib` is a directory mapped to `${workspaceRoot}/tests/lib`
|
||||
* - `/.src` is a virtual directory to be used for tests.
|
||||
*
|
||||
* Unless overridden, `/.src` will be the current working directory for the virtual file system.
|
||||
*/
|
||||
export function createFromFileSystem(host: FileSystemResolverHost, ignoreCase: boolean, { documents, cwd }: FileSystemCreateOptions = {}) {
|
||||
const fs = getBuiltLocal(host, ignoreCase).shadow();
|
||||
if (cwd) {
|
||||
fs.mkdirpSync(cwd);
|
||||
fs.chdir(cwd);
|
||||
}
|
||||
if (documents) {
|
||||
for (const document of documents) {
|
||||
fs.mkdirpSync(vpath.dirname(document.file));
|
||||
fs.writeFileSync(document.file, document.text, "utf8");
|
||||
fs.filemeta(document.file).set("document", document);
|
||||
// Add symlinks
|
||||
const symlink = document.meta.get("symlink");
|
||||
if (symlink) {
|
||||
for (const link of symlink.split(",").map(link => link.trim())) {
|
||||
fs.mkdirpSync(vpath.dirname(link));
|
||||
fs.symlinkSync(document.file, link);
|
||||
fs.filemeta(link).set("document", document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
export class Stats {
|
||||
public dev: number;
|
||||
public ino: number;
|
||||
@ -1188,7 +1188,7 @@ namespace vfs {
|
||||
source?: string;
|
||||
resolver?: FileSystemResolver;
|
||||
shadowRoot?: FileInode;
|
||||
meta?: core.Metadata;
|
||||
meta?: collections.Metadata;
|
||||
}
|
||||
|
||||
interface DirectoryInode {
|
||||
@ -1200,11 +1200,11 @@ namespace vfs {
|
||||
ctimeMs: number; // status change time
|
||||
birthtimeMs: number; // creation time
|
||||
nlink: number; // number of hard links
|
||||
links?: core.SortedMap<string, Inode>;
|
||||
links?: collections.SortedMap<string, Inode>;
|
||||
source?: string;
|
||||
resolver?: FileSystemResolver;
|
||||
shadowRoot?: DirectoryInode;
|
||||
meta?: core.Metadata;
|
||||
meta?: collections.Metadata;
|
||||
}
|
||||
|
||||
interface SymlinkInode {
|
||||
@ -1218,7 +1218,7 @@ namespace vfs {
|
||||
nlink: number; // number of hard links
|
||||
symlink?: string;
|
||||
shadowRoot?: SymlinkInode;
|
||||
meta?: core.Metadata;
|
||||
meta?: collections.Metadata;
|
||||
}
|
||||
|
||||
function isFile(node: Inode | undefined): node is FileInode {
|
||||
@ -1237,7 +1237,7 @@ namespace vfs {
|
||||
realpath: string;
|
||||
basename: string;
|
||||
parent: DirectoryInode | undefined;
|
||||
links: core.SortedMap<string, Inode> | undefined;
|
||||
links: collections.SortedMap<string, Inode> | undefined;
|
||||
node: Inode | undefined;
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
/// <reference path="./core/comparers.ts" />
|
||||
/// <reference path="./vfs.ts" />
|
||||
namespace vpath {
|
||||
/**
|
||||
* Virtual path separator.
|
||||
*/
|
||||
export const sep = "/";
|
||||
export import sep = ts.directorySeparator;
|
||||
|
||||
/**
|
||||
* Normalize path separators.
|
||||
*/
|
||||
export function normalizeSeparators(path: string): string {
|
||||
return path.replace(/\s*[\\/]\s*/g, sep).trim();
|
||||
}
|
||||
export import normalizeSeparators = ts.normalizeSlashes;
|
||||
// export function normalizeSeparators(path: string): string {
|
||||
// return ts.normalizeSlashes(path);
|
||||
// }
|
||||
|
||||
const invalidRootComponentRegExp = /^(?!(\/|\/\/\w+\/|[a-zA-Z]:\/?|)$)/;
|
||||
const invalidNavigableComponentRegExp = /[:*?"<>|]/;
|
||||
@ -96,6 +95,7 @@ namespace vpath {
|
||||
|
||||
const absolutePathRegExp = /^[\\/]([\\/](.*?[\\/](.*?[\\/])?)?)?|^[a-zA-Z]:[\\/]?|^\w+:\/{2}[^\\/]*\/?/;
|
||||
|
||||
// NOTE: this differs from `ts.getRootLength` in that it doesn't support URIs.
|
||||
function getRootLength(path: string) {
|
||||
const match = absolutePathRegExp.exec(path);
|
||||
return match ? match[0].length : 0;
|
||||
@ -105,9 +105,10 @@ namespace vpath {
|
||||
* Determines whether a path is an absolute path (e.g. starts with `/`, `\\`, or a dos path
|
||||
* like `c:`).
|
||||
*/
|
||||
export function isAbsolute(path: string) {
|
||||
return absolutePathRegExp.test(path);
|
||||
}
|
||||
export import isAbsolute = ts.isRootedDiskPath;
|
||||
// export function isAbsolute(path: string) {
|
||||
// return absolutePathRegExp.test(path);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Determines whether a path consists only of a path root.
|
||||
@ -129,16 +130,18 @@ namespace vpath {
|
||||
/**
|
||||
* Adds a trailing separator (`/`) to a path if it doesn't have one.
|
||||
*/
|
||||
export function addTrailingSeparator(path: string) {
|
||||
return !trailingSeperatorRegExp.test(path) && path ? path + "/" : path;
|
||||
}
|
||||
export import addTrailingSeparator = ts.ensureTrailingDirectorySeparator;
|
||||
// export function addTrailingSeparator(path: string) {
|
||||
// return !trailingSeperatorRegExp.test(path) && path ? path + "/" : path;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Removes a trailing separator (`/`) from a path if it has one.
|
||||
*/
|
||||
export function removeTrailingSeparator(path: string) {
|
||||
return trailingSeperatorRegExp.test(path) && !isRoot(path) ? path.slice(0, -1) : path;
|
||||
}
|
||||
export import removeTrailingSeparator = ts.removeTrailingDirectorySeparator;
|
||||
// export function removeTrailingSeparator(path: string) {
|
||||
// return trailingSeperatorRegExp.test(path) && !isRoot(path) ? path.slice(0, -1) : path;
|
||||
// }
|
||||
|
||||
function reduce(components: ReadonlyArray<string>) {
|
||||
const normalized = [components[0]];
|
||||
@ -172,11 +175,12 @@ namespace vpath {
|
||||
*/
|
||||
export function combine(path: string, ...paths: string[]) {
|
||||
path = normalizeSeparators(path);
|
||||
for (let name of paths) {
|
||||
name = normalizeSeparators(name);
|
||||
if (name.length === 0) continue;
|
||||
path = path.length === 0 || isAbsolute(name) ? name :
|
||||
addTrailingSeparator(path) + name;
|
||||
for (const name of paths) {
|
||||
path = ts.combinePaths(path, normalizeSeparators(name));
|
||||
// name = normalizeSeparators(name);
|
||||
// if (name.length === 0) continue;
|
||||
// path = path.length === 0 || isAbsolute(name) ? name :
|
||||
// addTrailingSeparator(path) + name;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@ -188,6 +192,8 @@ namespace vpath {
|
||||
return normalize(combine(path, ...paths));
|
||||
}
|
||||
|
||||
// NOTE: this differs from `ts.getRelativePathToDirectoryOrUrl` in that it requires both paths
|
||||
// are already absolute and does not perform "canonicalization".
|
||||
function relativeWorker(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean) {
|
||||
if (!isAbsolute(from)) throw new Error("Path not absolute");
|
||||
if (!isAbsolute(to)) throw new Error("Path not absolute");
|
||||
@ -215,20 +221,23 @@ namespace vpath {
|
||||
}
|
||||
|
||||
function relativeCaseSensitive(from: string, to: string) {
|
||||
return relativeWorker(from, to, core.equateStringsCaseSensitive);
|
||||
return relativeWorker(from, to, ts.equateStringsCaseSensitive);
|
||||
}
|
||||
|
||||
function relativeCaseInsensitive(from: string, to: string) {
|
||||
return relativeWorker(from, to, core.equateStringsCaseInsensitive);
|
||||
return relativeWorker(from, to, ts.equateStringsCaseInsensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a relative path that can be used to traverse between `from` and `to`.
|
||||
*/
|
||||
// NOTE: this differs from `ts.getRelativePathToDirectoryOrUrl` in that it requires both paths
|
||||
// are already absolute and does not perform "canonicalization".
|
||||
export function relative(from: string, to: string, ignoreCase: boolean) {
|
||||
return ignoreCase ? relativeCaseInsensitive(from, to) : relativeCaseSensitive(from, to);
|
||||
}
|
||||
|
||||
// NOTE: this differs from `ts.comparePaths` due to the behavior of `parse`.
|
||||
function compareWorker(a: string, b: string, stringComparer: (a: string, b: string) => number) {
|
||||
if (a === b) return 0;
|
||||
a = removeTrailingSeparator(a);
|
||||
@ -241,32 +250,33 @@ namespace vpath {
|
||||
const result = stringComparer(aComponents[i], bComponents[i]);
|
||||
if (result !== 0) return result;
|
||||
}
|
||||
return core.compareNumbers(aComponents.length, bComponents.length);
|
||||
return ts.compareValues(aComponents.length, bComponents.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-sensitive comparison of two paths.
|
||||
*/
|
||||
export function compareCaseSensitive(a: string, b: string) {
|
||||
return compareWorker(a, b, core.compareStringsCaseSensitive);
|
||||
return compareWorker(a, b, ts.compareStringsCaseSensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-insensitive comparison of two paths.
|
||||
*/
|
||||
export function compareCaseInsensitive(a: string, b: string) {
|
||||
return compareWorker(a, b, core.compareStringsCaseInsensitive);
|
||||
return compareWorker(a, b, ts.compareStringsCaseInsensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two paths.
|
||||
*/
|
||||
// NOTE: this differs from `ts.comparePaths` due to the behavior of `parse`.
|
||||
export function compare(a: string, b: string, ignoreCase: boolean) {
|
||||
return ignoreCase ? compareCaseInsensitive(a, b) : compareCaseSensitive(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two strings are equal.
|
||||
* Determines whether two paths are equal.
|
||||
*/
|
||||
export function equals(a: string, b: string, ignoreCase: boolean) {
|
||||
if (!isAbsolute(a)) throw new Error("Path not absolute");
|
||||
@ -281,6 +291,7 @@ namespace vpath {
|
||||
return ignoreCase && a.toUpperCase() === b.toUpperCase();
|
||||
}
|
||||
|
||||
// NOTE: this differs from `ts.containsPath` due to the behavior of `parse`.
|
||||
function beneathWorker(ancestor: string, descendant: string, stringEqualityComparer: (a: string, b: string) => boolean) {
|
||||
if (!isAbsolute(ancestor)) throw new Error("Path not absolute");
|
||||
if (!isAbsolute(descendant)) throw new Error("Path not absolute");
|
||||
@ -296,16 +307,17 @@ namespace vpath {
|
||||
}
|
||||
|
||||
function beneathCaseSensitive(ancestor: string, descendant: string) {
|
||||
return beneathWorker(ancestor, descendant, core.equateStringsCaseSensitive);
|
||||
return beneathWorker(ancestor, descendant, ts.equateStringsCaseSensitive);
|
||||
}
|
||||
|
||||
function beneathCaseInsensitive(ancestor: string, descendant: string) {
|
||||
return beneathWorker(ancestor, descendant, core.equateStringsCaseInsensitive);
|
||||
return beneathWorker(ancestor, descendant, ts.equateStringsCaseInsensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the path `descendant` is beneath the path `ancestor`.
|
||||
*/
|
||||
// NOTE: this differs from `containsPath` in compiler/core.ts due to the behavior of `parse`.
|
||||
export function beneath(ancestor: string, descendant: string, ignoreCase: boolean) {
|
||||
return ignoreCase ? beneathCaseInsensitive(ancestor, descendant) : beneathCaseSensitive(ancestor, descendant);
|
||||
}
|
||||
@ -315,6 +327,9 @@ namespace vpath {
|
||||
* If the path is relative, the root component is `""`.
|
||||
* If the path is absolute, the root component includes the first path separator (`/`).
|
||||
*/
|
||||
// NOTE: this differs from `ts.getNormalizedPathComponents` due to the fact that `parse` does
|
||||
// not automatically normalize relative paths and does not perform path normalization. This is
|
||||
// necessary to support proper path navigation in `vfs`.
|
||||
export function parse(path: string) {
|
||||
path = normalizeSeparators(path);
|
||||
const rootLength = getRootLength(path);
|
||||
@ -327,6 +342,8 @@ namespace vpath {
|
||||
/**
|
||||
* Formats a parsed path consisting of a root component and zero or more path segments.
|
||||
*/
|
||||
// NOTE: this differs from `ts.getNormalizedPathFromPathComponents` in that this function
|
||||
// always returns a string.
|
||||
export function format(components: ReadonlyArray<string>) {
|
||||
return components.length ? components[0] + components.slice(1).join(sep) : "";
|
||||
}
|
||||
@ -334,6 +351,7 @@ namespace vpath {
|
||||
/**
|
||||
* Gets the parent directory name of a path.
|
||||
*/
|
||||
// NOTE: this differs from `ts.getDirectoryPath` due to the behavior of `getRootLength`.
|
||||
export function dirname(path: string) {
|
||||
path = normalizeSeparators(path);
|
||||
path = removeTrailingSeparator(path);
|
||||
@ -349,6 +367,8 @@ namespace vpath {
|
||||
* If the base name has any one of the provided extensions, it is removed.
|
||||
*/
|
||||
export function basename(path: string, extensions: string | ReadonlyArray<string>, ignoreCase: boolean): string;
|
||||
// NOTE: this differs from `ts.getBaseFileName` in that this function handles extensions in a
|
||||
// fashion similar to the NodeJS `path.basename` function as well as handles case sensitivity.
|
||||
export function basename(path: string, extensions?: string | ReadonlyArray<string>, ignoreCase?: boolean) {
|
||||
path = normalizeSeparators(path);
|
||||
path = removeTrailingSeparator(path);
|
||||
@ -384,9 +404,11 @@ namespace vpath {
|
||||
* Gets the file extension for a path, provided it is one of the provided extensions.
|
||||
*/
|
||||
export function extname(path: string, extensions: string | ReadonlyArray<string>, ignoreCase: boolean): string;
|
||||
// NOTE: this differs from `ts.getAnyExtensionFromPath` in that this function allows you to
|
||||
// restrict extensions and handle case sensitivity
|
||||
export function extname(path: string, extensions?: string | ReadonlyArray<string>, ignoreCase?: boolean) {
|
||||
if (extensions) {
|
||||
return extnameWorker(path, extensions, ignoreCase ? core.equateStringsCaseInsensitive : core.equateStringsCaseSensitive);
|
||||
return extnameWorker(path, extensions, ignoreCase ? ts.equateStringsCaseInsensitive : ts.equateStringsCaseSensitive);
|
||||
}
|
||||
|
||||
const match = extRegExp.exec(path);
|
||||
@ -395,6 +417,8 @@ namespace vpath {
|
||||
|
||||
export function changeExtension(path: string, ext: string): string;
|
||||
export function changeExtension(path: string, ext: string, extensions: string | ReadonlyArray<string>, ignoreCase: boolean): string;
|
||||
// NOTE: this differs from `ts.changeExtension` in that this function allows you to
|
||||
// specify extensions and handle case sensitivity
|
||||
export function changeExtension(path: string, ext: string, extensions?: string | ReadonlyArray<string>, ignoreCase?: boolean) {
|
||||
const pathext = extensions !== undefined && ignoreCase !== undefined ? extname(path, extensions, ignoreCase) : extname(path);
|
||||
return pathext ? path.slice(0, path.length - pathext.length) + (ext.startsWith(".") ? ext : "." + ext) : path;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user