mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 20:25:48 -06:00
Updated vfs
This commit is contained in:
parent
d23e5f1ee2
commit
5b4553d6a5
@ -93,7 +93,9 @@ var typesMapOutputPath = path.join(builtLocalDirectory, 'typesMap.json');
|
||||
|
||||
var harnessCoreSources = [
|
||||
"harness.ts",
|
||||
"virtualFileSystem.ts",
|
||||
"collections.ts",
|
||||
"vpath.ts",
|
||||
"vfs.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"sourceMapRecorder.ts",
|
||||
"harnessLanguageService.ts",
|
||||
|
||||
213
src/harness/collections.ts
Normal file
213
src/harness/collections.ts
Normal file
@ -0,0 +1,213 @@
|
||||
/// <reference path="./harness.ts" />
|
||||
namespace Collections {
|
||||
import compareValues = ts.compareValues;
|
||||
import binarySearch = ts.binarySearch;
|
||||
import removeAt = ts.orderedRemoveItemAt;
|
||||
|
||||
const caseInsensitiveComparisonCollator = typeof Intl === "object" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }) : undefined;
|
||||
const caseSensitiveComparisonCollator = typeof Intl === "object" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "variant" }) : undefined;
|
||||
|
||||
export function compareStrings(a: string | undefined, b: string | undefined, ignoreCase?: boolean) {
|
||||
if (a === b) return 0;
|
||||
if (a === undefined) return -1;
|
||||
if (b === undefined) return +1;
|
||||
const collator = ignoreCase ? caseInsensitiveComparisonCollator : caseSensitiveComparisonCollator;
|
||||
if (collator) {
|
||||
return collator.compare(a, b);
|
||||
}
|
||||
else if (ignoreCase) {
|
||||
a = a.toUpperCase();
|
||||
b = b.toUpperCase();
|
||||
}
|
||||
return a < b ? -1 : a > b ? +1 : 0;
|
||||
}
|
||||
|
||||
export namespace compareStrings {
|
||||
export function caseSensitive(a: string | undefined, b: string | undefined) {
|
||||
return compareStrings(a, b, /*ignoreCase*/ false);
|
||||
}
|
||||
|
||||
export function caseInsensitive(a: string | undefined, b: string | undefined) {
|
||||
return compareStrings(a, b, /*ignoreCase*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
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 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 = compareValues) {
|
||||
this._comparer = comparer;
|
||||
}
|
||||
|
||||
public get size() {
|
||||
return this._keys.length;
|
||||
}
|
||||
|
||||
public has(key: K) {
|
||||
return binarySearch(this._keys, key, this._comparer) >= 0;
|
||||
}
|
||||
|
||||
public get(key: K) {
|
||||
const index = binarySearch(this._keys, key, this._comparer);
|
||||
return index >= 0 ? this._values[index] : undefined;
|
||||
}
|
||||
|
||||
public set(key: K, value: V) {
|
||||
const index = binarySearch(this._keys, key, 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, 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 (let i = 0; i < order.length; i++) {
|
||||
callback(values[order[i]], keys[order[i]], 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) => compareValues(this._order[x], this._order[y]));
|
||||
}
|
||||
}
|
||||
|
||||
const undefinedSentinel = {};
|
||||
|
||||
/**
|
||||
* 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[key] !== undefined;
|
||||
}
|
||||
|
||||
public get(key: string): any {
|
||||
const value = this._map[key];
|
||||
return value === undefinedSentinel ? undefined : value;
|
||||
}
|
||||
|
||||
public set(key: string, value: any): this {
|
||||
this._map[key] = value === undefined ? undefinedSentinel : value;
|
||||
this._size = -1;
|
||||
this._version++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public delete(key: string): boolean {
|
||||
if (this._map[key] !== undefined) {
|
||||
delete this._map[key];
|
||||
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], key, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@
|
||||
/// <reference path="..\server\client.ts" />
|
||||
/// <reference path="sourceMapRecorder.ts"/>
|
||||
/// <reference path="runnerbase.ts"/>
|
||||
/// <reference path="virtualFileSystem.ts" />
|
||||
/// <reference path="vfs.ts" />
|
||||
/// <reference types="node" />
|
||||
/// <reference types="mocha" />
|
||||
/// <reference types="chai" />
|
||||
@ -743,8 +743,8 @@ namespace Harness {
|
||||
fs.addFile(file);
|
||||
}
|
||||
return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), getCurrentDirectory(), depth, path => {
|
||||
const entry = fs.traversePath(path);
|
||||
if (entry && entry.isDirectory()) {
|
||||
const entry = fs.getEntry(path);
|
||||
if (entry instanceof Utils.VirtualDirectory) {
|
||||
const directory = <Utils.VirtualDirectory>entry;
|
||||
return {
|
||||
files: ts.map(directory.getFiles(), f => f.name),
|
||||
|
||||
@ -123,7 +123,7 @@ namespace Harness.LanguageService {
|
||||
}
|
||||
|
||||
export class LanguageServiceAdapterHost {
|
||||
protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false);
|
||||
protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/ false);
|
||||
|
||||
constructor(protected cancellationToken = DefaultHostCancellationToken.Instance,
|
||||
protected settings = ts.getDefaultCompilerOptions()) {
|
||||
@ -135,9 +135,9 @@ namespace Harness.LanguageService {
|
||||
|
||||
public getFilenames(): string[] {
|
||||
const fileNames: string[] = [];
|
||||
for (const virtualEntry of this.virtualFileSystem.getAllFileEntries()) {
|
||||
const scriptInfo = virtualEntry.content;
|
||||
if (scriptInfo.isRootFile) {
|
||||
for (const virtualEntry of this.virtualFileSystem.getFiles({ recursive: true })) {
|
||||
const scriptInfo = virtualEntry.metadata.get("scriptInfo");
|
||||
if (scriptInfo && scriptInfo.isRootFile) {
|
||||
// only include root files here
|
||||
// usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir.
|
||||
fileNames.push(scriptInfo.fileName);
|
||||
@ -147,18 +147,20 @@ namespace Harness.LanguageService {
|
||||
}
|
||||
|
||||
public getScriptInfo(fileName: string): ScriptInfo {
|
||||
const fileEntry = this.virtualFileSystem.traversePath(fileName);
|
||||
return fileEntry && fileEntry.isFile() ? (<Utils.VirtualFile>fileEntry).content : undefined;
|
||||
const fileEntry = this.virtualFileSystem.getFile(fileName);
|
||||
return fileEntry ? fileEntry.metadata.get("scriptInfo") : undefined;
|
||||
}
|
||||
|
||||
public addScript(fileName: string, content: string, isRootFile: boolean): void {
|
||||
this.virtualFileSystem.addFile(fileName, new ScriptInfo(fileName, content, isRootFile));
|
||||
this.virtualFileSystem.addFile(fileName, content, { overwrite: true }).metadata.set("scriptInfo", new ScriptInfo(fileName, content, isRootFile));
|
||||
}
|
||||
|
||||
public editScript(fileName: string, start: number, end: number, newText: string) {
|
||||
const script = this.getScriptInfo(fileName);
|
||||
if (script !== undefined) {
|
||||
const file = this.virtualFileSystem.getFile(fileName);
|
||||
const script = file && file.metadata.get("scriptInfo") as ScriptInfo;
|
||||
if (script) {
|
||||
script.editContent(start, end, newText);
|
||||
file.setContent(script.content);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -185,9 +187,9 @@ namespace Harness.LanguageService {
|
||||
getCompilationSettings() { return this.settings; }
|
||||
getCancellationToken() { return this.cancellationToken; }
|
||||
getDirectories(path: string): string[] {
|
||||
const dir = this.virtualFileSystem.traversePath(path);
|
||||
if (dir && dir.isDirectory()) {
|
||||
return ts.map((<Utils.VirtualDirectory>dir).getDirectories(), (d) => ts.combinePaths(path, d.name));
|
||||
const dir = this.virtualFileSystem.getDirectory(path);
|
||||
if (dir) {
|
||||
return ts.map(dir.getDirectories(), (d) => ts.combinePaths(path, d.name));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -97,6 +97,9 @@
|
||||
"./parallel/host.ts",
|
||||
"./parallel/worker.ts",
|
||||
"runner.ts",
|
||||
"collections.ts",
|
||||
"vpath.ts",
|
||||
"vfs.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"../server/protocol.ts",
|
||||
"../server/session.ts",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="..\virtualFileSystem.ts" />
|
||||
/// <reference path="..\vfs.ts" />
|
||||
|
||||
namespace ts {
|
||||
const testContentsJson = createMapFromTemplate({
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="..\virtualFileSystem.ts" />
|
||||
/// <reference path="..\vfs.ts" />
|
||||
|
||||
namespace ts {
|
||||
const caseInsensitiveBasePath = "c:/dev/";
|
||||
|
||||
1073
src/harness/vfs.ts
Normal file
1073
src/harness/vfs.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,223 +0,0 @@
|
||||
/// <reference path="harness.ts" />
|
||||
/// <reference path="..\compiler\commandLineParser.ts"/>
|
||||
namespace Utils {
|
||||
export class VirtualFileSystemEntry {
|
||||
fileSystem: VirtualFileSystem;
|
||||
name: string;
|
||||
|
||||
constructor(fileSystem: VirtualFileSystem, name: string) {
|
||||
this.fileSystem = fileSystem;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
isDirectory(): this is VirtualDirectory { return false; }
|
||||
isFile(): this is VirtualFile { return false; }
|
||||
isFileSystem(): this is VirtualFileSystem { return false; }
|
||||
}
|
||||
|
||||
export class VirtualFile extends VirtualFileSystemEntry {
|
||||
content?: Harness.LanguageService.ScriptInfo;
|
||||
isFile() { return true; }
|
||||
}
|
||||
|
||||
export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry {
|
||||
abstract getFileSystemEntries(): VirtualFileSystemEntry[];
|
||||
|
||||
getFileSystemEntry(name: string): VirtualFileSystemEntry {
|
||||
for (const entry of this.getFileSystemEntries()) {
|
||||
if (this.fileSystem.sameName(entry.name, name)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getDirectories(): VirtualDirectory[] {
|
||||
return <VirtualDirectory[]>ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory());
|
||||
}
|
||||
|
||||
getFiles(): VirtualFile[] {
|
||||
return <VirtualFile[]>ts.filter(this.getFileSystemEntries(), entry => entry.isFile());
|
||||
}
|
||||
|
||||
getDirectory(name: string): VirtualDirectory {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
return entry.isDirectory() ? <VirtualDirectory>entry : undefined;
|
||||
}
|
||||
|
||||
getFile(name: string): VirtualFile {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
return entry.isFile() ? <VirtualFile>entry : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class VirtualDirectory extends VirtualFileSystemContainer {
|
||||
private entries: VirtualFileSystemEntry[] = [];
|
||||
|
||||
isDirectory() { return true; }
|
||||
|
||||
getFileSystemEntries() { return this.entries.slice(); }
|
||||
|
||||
addDirectory(name: string): VirtualDirectory {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
if (entry === undefined) {
|
||||
const directory = new VirtualDirectory(this.fileSystem, name);
|
||||
this.entries.push(directory);
|
||||
return directory;
|
||||
}
|
||||
else if (entry.isDirectory()) {
|
||||
return <VirtualDirectory>entry;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
addFile(name: string, content?: Harness.LanguageService.ScriptInfo): VirtualFile {
|
||||
const entry = this.getFileSystemEntry(name);
|
||||
if (entry === undefined) {
|
||||
const file = new VirtualFile(this.fileSystem, name);
|
||||
file.content = content;
|
||||
this.entries.push(file);
|
||||
return file;
|
||||
}
|
||||
else if (entry.isFile()) {
|
||||
entry.content = content;
|
||||
return entry;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VirtualFileSystem extends VirtualFileSystemContainer {
|
||||
private root: VirtualDirectory;
|
||||
|
||||
currentDirectory: string;
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
|
||||
constructor(currentDirectory: string, useCaseSensitiveFileNames: boolean) {
|
||||
super(/*fileSystem*/ undefined, "");
|
||||
this.fileSystem = this;
|
||||
this.root = new VirtualDirectory(this, "");
|
||||
this.currentDirectory = currentDirectory;
|
||||
this.useCaseSensitiveFileNames = useCaseSensitiveFileNames;
|
||||
}
|
||||
|
||||
isFileSystem() { return true; }
|
||||
|
||||
getFileSystemEntries() { return this.root.getFileSystemEntries(); }
|
||||
|
||||
addDirectory(path: string) {
|
||||
path = ts.normalizePath(path);
|
||||
const components = ts.getNormalizedPathComponents(path, this.currentDirectory);
|
||||
let directory: VirtualDirectory = this.root;
|
||||
for (const component of components) {
|
||||
directory = directory.addDirectory(component);
|
||||
if (directory === undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
addFile(path: string, content?: Harness.LanguageService.ScriptInfo) {
|
||||
const absolutePath = ts.normalizePath(ts.getNormalizedAbsolutePath(path, this.currentDirectory));
|
||||
const fileName = ts.getBaseFileName(path);
|
||||
const directoryPath = ts.getDirectoryPath(absolutePath);
|
||||
const directory = this.addDirectory(directoryPath);
|
||||
return directory ? directory.addFile(fileName, content) : undefined;
|
||||
}
|
||||
|
||||
fileExists(path: string) {
|
||||
const entry = this.traversePath(path);
|
||||
return entry !== undefined && entry.isFile();
|
||||
}
|
||||
|
||||
sameName(a: string, b: string) {
|
||||
return this.useCaseSensitiveFileNames ? a === b : a.toLowerCase() === b.toLowerCase();
|
||||
}
|
||||
|
||||
traversePath(path: string) {
|
||||
path = ts.normalizePath(path);
|
||||
let directory: VirtualDirectory = this.root;
|
||||
for (const component of ts.getNormalizedPathComponents(path, this.currentDirectory)) {
|
||||
const entry = directory.getFileSystemEntry(component);
|
||||
if (entry === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
else if (entry.isDirectory()) {
|
||||
directory = <VirtualDirectory>entry;
|
||||
}
|
||||
else {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the directory at the given path and retrieves a list of file names and a list
|
||||
* of directory names within it. Suitable for use with ts.matchFiles()
|
||||
* @param path The path to the directory to be read
|
||||
*/
|
||||
getAccessibleFileSystemEntries(path: string) {
|
||||
const entry = this.traversePath(path);
|
||||
if (entry && entry.isDirectory()) {
|
||||
const directory = <VirtualDirectory>entry;
|
||||
return {
|
||||
files: ts.map(directory.getFiles(), f => f.name),
|
||||
directories: ts.map(directory.getDirectories(), d => d.name)
|
||||
};
|
||||
}
|
||||
return { files: [], directories: [] };
|
||||
}
|
||||
|
||||
getAllFileEntries() {
|
||||
const fileEntries: VirtualFile[] = [];
|
||||
getFilesRecursive(this.root, fileEntries);
|
||||
return fileEntries;
|
||||
|
||||
function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) {
|
||||
const files = dir.getFiles();
|
||||
const dirs = dir.getDirectories();
|
||||
for (const file of files) {
|
||||
result.push(file);
|
||||
}
|
||||
for (const subDir of dirs) {
|
||||
getFilesRecursive(subDir, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost {
|
||||
constructor(currentDirectory: string, ignoreCase: boolean, files: ts.Map<string> | string[]) {
|
||||
super(currentDirectory, ignoreCase);
|
||||
if (files instanceof Array) {
|
||||
for (const file of files) {
|
||||
this.addFile(file, new Harness.LanguageService.ScriptInfo(file, undefined, /*isRootFile*/false));
|
||||
}
|
||||
}
|
||||
else {
|
||||
files.forEach((fileContent, fileName) => {
|
||||
this.addFile(fileName, new Harness.LanguageService.ScriptInfo(fileName, fileContent, /*isRootFile*/false));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
readFile(path: string): string | undefined {
|
||||
const value = this.traversePath(path);
|
||||
if (value && value.isFile()) {
|
||||
return value.content.content;
|
||||
}
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string>, includes: ReadonlyArray<string>, depth: number) {
|
||||
return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, (path: string) => this.getAccessibleFileSystemEntries(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
161
src/harness/vpath.ts
Normal file
161
src/harness/vpath.ts
Normal file
@ -0,0 +1,161 @@
|
||||
/// <reference path="./harness.ts" />
|
||||
|
||||
namespace vpath {
|
||||
import compareStrings = ts.compareStrings;
|
||||
|
||||
export function normalizeSlashes(path: string): string {
|
||||
return path.replace(/\s*[\\/]\s*/g, "/").trim();
|
||||
}
|
||||
|
||||
const rootRegExp = /^[\\/]([\\/](.*?[\\/](.*?[\\/])?)?)?|^[a-zA-Z]:[\\/]?|^\w+:\/{2}[^\\/]*\/?/;
|
||||
|
||||
function getRootLength(path: string) {
|
||||
const match = rootRegExp.exec(path);
|
||||
return match ? match[0].length : 0;
|
||||
}
|
||||
|
||||
export function isAbsolute(path: string) {
|
||||
return rootRegExp.test(path);
|
||||
}
|
||||
|
||||
const trailingSeperatorRegExp = /[\\/]$/;
|
||||
|
||||
export function hasTrailingSeperator(path: string) {
|
||||
return trailingSeperatorRegExp.test(path);
|
||||
}
|
||||
|
||||
function reduce(components: string[]) {
|
||||
const normalized = [components[0]];
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
const component = components[i];
|
||||
if (component === ".") continue;
|
||||
if (component === ".." && normalized.length > 0 && normalized[normalized.length - 1] !== "..") {
|
||||
normalized.pop();
|
||||
}
|
||||
else {
|
||||
normalized.push(component);
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function normalize(path: string): string {
|
||||
const components = reduce(parse(path));
|
||||
return components.length > 1 && hasTrailingSeperator(path) ? format(components) + "/" : format(components);
|
||||
}
|
||||
|
||||
export function combine(path: string, ...paths: string[]) {
|
||||
path = normalizeSlashes(path);
|
||||
for (let name of paths) {
|
||||
name = normalizeSlashes(name);
|
||||
if (name.length === 0) continue;
|
||||
if (path.length === 0 || isAbsolute(name)) {
|
||||
path = name;
|
||||
}
|
||||
else {
|
||||
path = hasTrailingSeperator(path) ? path + name : path + "/" + name;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function resolve(path: string, ...paths: string[]) {
|
||||
return normalize(combine(path, ...paths));
|
||||
}
|
||||
|
||||
export function relative(from: string, to: string, ignoreCase: boolean) {
|
||||
if (!isAbsolute(from)) throw new Error("Path not absolute");
|
||||
if (!isAbsolute(to)) throw new Error("Path not absolute");
|
||||
|
||||
const fromComponents = reduce(parse(from));
|
||||
const toComponents = reduce(parse(to));
|
||||
|
||||
let start: number;
|
||||
for (start = 0; start < fromComponents.length && start < toComponents.length; start++) {
|
||||
if (compareStrings(fromComponents[start], toComponents[start], ignoreCase)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (start === 0 || (start === 1 && fromComponents[0] === "/")) {
|
||||
return format(toComponents);
|
||||
}
|
||||
|
||||
const components = toComponents.slice(start);
|
||||
for (; start < fromComponents.length; start++) {
|
||||
components.unshift("..");
|
||||
}
|
||||
|
||||
return format(["", ...components]);
|
||||
}
|
||||
|
||||
export function beneath(ancestor: string, descendant: string, ignoreCase: boolean) {
|
||||
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));
|
||||
|
||||
let start: number;
|
||||
for (start = 0; start < ancestorComponents.length && start < descendantComponents.length; start++) {
|
||||
if (compareStrings(ancestorComponents[start], descendantComponents[start], ignoreCase)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return start === ancestorComponents.length;
|
||||
}
|
||||
|
||||
export function parse(path: string) {
|
||||
path = normalizeSlashes(path);
|
||||
const rootLength = getRootLength(path);
|
||||
const root = path.substring(0, rootLength);
|
||||
const rest = path.substring(rootLength).split(/\/+/g);
|
||||
if (rest.length && !rest[rest.length - 1]) rest.pop();
|
||||
return [root, ...rest.map(component => component.trim())];
|
||||
}
|
||||
|
||||
export function format(components: string[]) {
|
||||
return components.length ? components[0] + components.slice(1).join("/") : "";
|
||||
}
|
||||
|
||||
export function dirname(path: string) {
|
||||
path = normalizeSlashes(path);
|
||||
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf("/")));
|
||||
}
|
||||
|
||||
export function basename(path: string, ext?: string): string;
|
||||
export function basename(path: string, options?: { extensions?: string[], ignoreCase?: boolean }): string;
|
||||
export function basename(path: string, options?: { extensions?: string[], ignoreCase?: boolean } | string) {
|
||||
path = normalizeSlashes(path);
|
||||
const name = path.substr(Math.max(getRootLength(path), path.lastIndexOf("/") + 1));
|
||||
const extension = typeof options === "string" ? options.startsWith(".") ? options : "." + options :
|
||||
options && options.extensions ? extname(name, options) :
|
||||
undefined;
|
||||
return extension ? name.slice(0, name.length - extension.length) : name;
|
||||
}
|
||||
|
||||
const extRegExp = /\.\w+$/;
|
||||
export function extname(path: string, options?: { extensions?: string[], ignoreCase?: boolean }) {
|
||||
if (options && options.extensions) {
|
||||
for (let extension of options.extensions) {
|
||||
if (!extension.startsWith(".")) extension = "." + extension;
|
||||
if (path.length > extension.length) {
|
||||
const ext = path.slice(path.length - extension.length);
|
||||
if (compareStrings(ext, extension, options.ignoreCase) === 0) {
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const match = extRegExp.exec(path);
|
||||
return match ? match[0] : "";
|
||||
}
|
||||
|
||||
export function chext(path: string, ext: string, options?: { extensions?: string[], ignoreCase?: boolean }) {
|
||||
const pathext = extname(path, options);
|
||||
return pathext ? path.slice(0, path.length - pathext.length) + (ext.startsWith(".") ? ext : "." + ext) : path;
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,6 @@ verify.noErrors();
|
||||
|
||||
const ranges = test.ranges();
|
||||
const [r0, r1, r2] = ranges;
|
||||
verify.referenceGroups([r0, r1], [{ definition: 'module "/a"', ranges: [r0, r2, r1] }]);
|
||||
verify.referenceGroups([r0, r1], [{ definition: 'module "/a"', ranges: [r0, r1, r2] }]);
|
||||
// TODO:GH#15736
|
||||
verify.referenceGroups(r2, undefined);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user