Add external mapShim/debug modules (#33712)
* Add external mapShim/debug modules * rename test file
This commit is contained in:
parent
154793aa58
commit
01b3d41124
14
Gulpfile.js
14
Gulpfile.js
|
@ -90,8 +90,18 @@ const localize = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const buildShims = () => buildProject("src/shims");
|
||||
const cleanShims = () => cleanProject("src/shims");
|
||||
cleanTasks.push(cleanShims);
|
||||
|
||||
const buildDebugTools = () => buildProject("src/debug");
|
||||
const cleanDebugTools = () => cleanProject("src/debug");
|
||||
cleanTasks.push(cleanDebugTools);
|
||||
|
||||
const buildShimsAndTools = parallel(buildShims, buildDebugTools);
|
||||
|
||||
// Pre-build steps when targeting the LKG compiler
|
||||
const lkgPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics));
|
||||
const lkgPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics, buildShimsAndTools));
|
||||
|
||||
const buildTsc = () => buildProject("src/tsc");
|
||||
task("tsc", series(lkgPreBuild, buildTsc));
|
||||
|
@ -107,7 +117,7 @@ task("watch-tsc", series(lkgPreBuild, parallel(watchLib, watchDiagnostics, watch
|
|||
task("watch-tsc").description = "Watch for changes and rebuild the command-line compiler only.";
|
||||
|
||||
// Pre-build steps when targeting the built/local compiler.
|
||||
const localPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics, buildTsc));
|
||||
const localPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics, buildShimsAndTools, buildTsc));
|
||||
|
||||
// Pre-build steps to use based on supplied options.
|
||||
const preBuild = cmdLineOptions.lkg ? lkgPreBuild : localPreBuild;
|
||||
|
|
|
@ -161,7 +161,12 @@ namespace ts {
|
|||
IsObjectLiteralOrClassExpressionMethod = 1 << 7,
|
||||
}
|
||||
|
||||
let flowNodeCreated: <T extends FlowNode>(node: T) => T = identity;
|
||||
function initFlowNode<T extends FlowNode>(node: T) {
|
||||
Debug.attachFlowNodeDebugInfo(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
let flowNodeCreated: <T extends FlowNode>(node: T) => T = initFlowNode;
|
||||
|
||||
const binder = createBinder();
|
||||
|
||||
|
@ -238,6 +243,10 @@ namespace ts {
|
|||
|
||||
Symbol = objectAllocator.getSymbolConstructor();
|
||||
|
||||
// Attach debugging information if necessary
|
||||
Debug.attachFlowNodeDebugInfo(unreachableFlow);
|
||||
Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow);
|
||||
|
||||
if (!file.locals) {
|
||||
bind(file);
|
||||
file.symbolCount = symbolCount;
|
||||
|
@ -626,7 +635,7 @@ namespace ts {
|
|||
// A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave
|
||||
// similarly to break statements that exit to a label just past the statement body.
|
||||
if (!isIIFE) {
|
||||
currentFlow = { flags: FlowFlags.Start };
|
||||
currentFlow = initFlowNode({ flags: FlowFlags.Start });
|
||||
if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) {
|
||||
currentFlow.node = <FunctionExpression | ArrowFunction | MethodDeclaration>node;
|
||||
}
|
||||
|
@ -638,7 +647,7 @@ namespace ts {
|
|||
currentContinueTarget = undefined;
|
||||
activeLabels = undefined;
|
||||
hasExplicitReturn = false;
|
||||
flowNodeCreated = identity;
|
||||
flowNodeCreated = initFlowNode;
|
||||
bindChildren(node);
|
||||
// Reset all reachability check related flags on node (for incremental scenarios)
|
||||
node.flags &= ~NodeFlags.ReachabilityAndEmitFlags;
|
||||
|
@ -919,11 +928,11 @@ namespace ts {
|
|||
}
|
||||
|
||||
function createBranchLabel(): FlowLabel {
|
||||
return { flags: FlowFlags.BranchLabel, antecedents: undefined };
|
||||
return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined });
|
||||
}
|
||||
|
||||
function createLoopLabel(): FlowLabel {
|
||||
return { flags: FlowFlags.LoopLabel, antecedents: undefined };
|
||||
return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined });
|
||||
}
|
||||
|
||||
function setFlowNodeReferenced(flow: FlowNode) {
|
||||
|
@ -1193,7 +1202,7 @@ namespace ts {
|
|||
// as possible antecedents of the start of the `catch` or `finally` blocks.
|
||||
// Don't bother intercepting the call if there's no finally or catch block that needs the information
|
||||
if (node.catchClause || node.finallyBlock) {
|
||||
flowNodeCreated = node => (tryPriors.push(node), node);
|
||||
flowNodeCreated = node => (tryPriors.push(node), initFlowNode(node));
|
||||
}
|
||||
bind(node.tryBlock);
|
||||
flowNodeCreated = oldFlowNodeCreated;
|
||||
|
@ -1262,7 +1271,7 @@ namespace ts {
|
|||
//
|
||||
// extra edges that we inject allows to control this behavior
|
||||
// if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped.
|
||||
const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preFinallyPrior, lock: {} };
|
||||
const preFinallyFlow: PreFinallyFlow = initFlowNode({ flags: FlowFlags.PreFinally, antecedent: preFinallyPrior, lock: {} });
|
||||
addAntecedent(preFinallyLabel, preFinallyFlow);
|
||||
|
||||
currentFlow = finishFlowLabel(preFinallyLabel);
|
||||
|
@ -1983,7 +1992,7 @@ namespace ts {
|
|||
const host = getJSDocHost(typeAlias);
|
||||
container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file;
|
||||
blockScopeContainer = getEnclosingBlockScopeContainer(host) || file;
|
||||
currentFlow = { flags: FlowFlags.Start };
|
||||
currentFlow = initFlowNode({ flags: FlowFlags.Start });
|
||||
parent = typeAlias;
|
||||
bind(typeAlias.typeExpression);
|
||||
const declName = getNameOfDeclaration(typeAlias);
|
||||
|
|
|
@ -42,6 +42,12 @@ namespace ts {
|
|||
clear(): void;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface MapConstructor {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
||||
new <T>(): Map<T>;
|
||||
}
|
||||
|
||||
/** ES6 Iterator type. */
|
||||
export interface Iterator<T> {
|
||||
next(): { value: T, done?: false } | { value: never, done: true };
|
||||
|
@ -66,28 +72,40 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
// Natives
|
||||
// NOTE: This must be declared in a separate block from the one below so that we don't collide with the exported definition of `Map`.
|
||||
declare const Map: (new <T>() => Map<T>) | undefined;
|
||||
|
||||
/**
|
||||
* Returns the native Map implementation if it is available and compatible (i.e. supports iteration).
|
||||
*/
|
||||
export function tryGetNativeMap(): MapConstructor | undefined {
|
||||
// Internet Explorer's Map doesn't support iteration, so don't use it.
|
||||
// eslint-disable-next-line no-in-operator
|
||||
return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export const emptyArray: never[] = [] as never[];
|
||||
|
||||
/** Create a MapLike with good performance. */
|
||||
function createDictionaryObject<T>(): MapLike<T> {
|
||||
const map = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null
|
||||
export const Map: MapConstructor = tryGetNativeMap() || (() => {
|
||||
// NOTE: createMapShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it.
|
||||
if (typeof createMapShim === "function") {
|
||||
return createMapShim();
|
||||
}
|
||||
throw new Error("TypeScript requires an environment that provides a compatible native Map implementation.");
|
||||
})();
|
||||
|
||||
// Using 'delete' on an object causes V8 to put the object in dictionary mode.
|
||||
// This disables creation of hidden classes, which are expensive when an object is
|
||||
// constantly changing shape.
|
||||
map.__ = undefined;
|
||||
delete map.__;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Create a new map. If a template object is provided, the map will copy entries from it. */
|
||||
/** Create a new map. */
|
||||
export function createMap<T>(): Map<T> {
|
||||
return new MapCtr<T>();
|
||||
return new Map<T>();
|
||||
}
|
||||
|
||||
/** Create a new map from an array of entries. */
|
||||
export function createMapFromEntries<T>(entries: [string, T][]): Map<T> {
|
||||
const map = createMap<T>();
|
||||
for (const [key, value] of entries) {
|
||||
|
@ -96,8 +114,9 @@ namespace ts {
|
|||
return map;
|
||||
}
|
||||
|
||||
/** Create a new map from a template object is provided, the map will copy entries from it. */
|
||||
export function createMapFromTemplate<T>(template: MapLike<T>): Map<T> {
|
||||
const map: Map<T> = new MapCtr<T>();
|
||||
const map: Map<T> = new Map<T>();
|
||||
|
||||
// Copies keys/values from template. Note that for..in will not throw if
|
||||
// template is undefined, and instead will just exit the loop.
|
||||
|
@ -110,204 +129,6 @@ namespace ts {
|
|||
return map;
|
||||
}
|
||||
|
||||
// The global Map object. This may not be available, so we must test for it.
|
||||
declare const Map: (new <T>() => Map<T>) | undefined;
|
||||
// Internet Explorer's Map doesn't support iteration, so don't use it.
|
||||
// eslint-disable-next-line no-in-operator
|
||||
export const MapCtr = typeof Map !== "undefined" && "entries" in Map.prototype ? Map : shimMap();
|
||||
|
||||
// Keep the class inside a function so it doesn't get compiled if it's not used.
|
||||
export function shimMap(): new <T>() => Map<T> {
|
||||
|
||||
interface MapEntry<T> {
|
||||
readonly key?: string;
|
||||
value?: T;
|
||||
|
||||
// Linked list references for iterators.
|
||||
nextEntry?: MapEntry<T>;
|
||||
previousEntry?: MapEntry<T>;
|
||||
|
||||
/**
|
||||
* Specifies if iterators should skip the next entry.
|
||||
* This will be set when an entry is deleted.
|
||||
* See https://github.com/Microsoft/TypeScript/pull/27292 for more information.
|
||||
*/
|
||||
skipNext?: boolean;
|
||||
}
|
||||
|
||||
class MapIterator<T, U extends (string | T | [string, T])> {
|
||||
private currentEntry?: MapEntry<T>;
|
||||
private selector: (key: string, value: T) => U;
|
||||
|
||||
constructor(currentEntry: MapEntry<T>, selector: (key: string, value: T) => U) {
|
||||
this.currentEntry = currentEntry;
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
public next(): { value: U, done: false } | { value: never, done: true } {
|
||||
// Navigate to the next entry.
|
||||
while (this.currentEntry) {
|
||||
const skipNext = !!this.currentEntry.skipNext;
|
||||
this.currentEntry = this.currentEntry.nextEntry;
|
||||
|
||||
if (!skipNext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentEntry) {
|
||||
return { value: this.selector(this.currentEntry.key!, this.currentEntry.value!), done: false };
|
||||
}
|
||||
else {
|
||||
return { value: undefined as never, done: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return class <T> implements Map<T> {
|
||||
private data = createDictionaryObject<MapEntry<T>>();
|
||||
public size = 0;
|
||||
|
||||
// Linked list references for iterators.
|
||||
// See https://github.com/Microsoft/TypeScript/pull/27292
|
||||
// for more information.
|
||||
|
||||
/**
|
||||
* The first entry in the linked list.
|
||||
* Note that this is only a stub that serves as starting point
|
||||
* for iterators and doesn't contain a key and a value.
|
||||
*/
|
||||
private readonly firstEntry: MapEntry<T>;
|
||||
private lastEntry: MapEntry<T>;
|
||||
|
||||
constructor() {
|
||||
// Create a first (stub) map entry that will not contain a key
|
||||
// and value but serves as starting point for iterators.
|
||||
this.firstEntry = {};
|
||||
// When the map is empty, the last entry is the same as the
|
||||
// first one.
|
||||
this.lastEntry = this.firstEntry;
|
||||
}
|
||||
|
||||
get(key: string): T | undefined {
|
||||
const entry = this.data[key] as MapEntry<T> | undefined;
|
||||
return entry && entry.value!;
|
||||
}
|
||||
|
||||
set(key: string, value: T): this {
|
||||
if (!this.has(key)) {
|
||||
this.size++;
|
||||
|
||||
// Create a new entry that will be appended at the
|
||||
// end of the linked list.
|
||||
const newEntry: MapEntry<T> = {
|
||||
key,
|
||||
value
|
||||
};
|
||||
this.data[key] = newEntry;
|
||||
|
||||
// Adjust the references.
|
||||
const previousLastEntry = this.lastEntry;
|
||||
previousLastEntry.nextEntry = newEntry;
|
||||
newEntry.previousEntry = previousLastEntry;
|
||||
this.lastEntry = newEntry;
|
||||
}
|
||||
else {
|
||||
this.data[key].value = value;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
has(key: string): boolean {
|
||||
// eslint-disable-next-line no-in-operator
|
||||
return key in this.data;
|
||||
}
|
||||
|
||||
delete(key: string): boolean {
|
||||
if (this.has(key)) {
|
||||
this.size--;
|
||||
const entry = this.data[key];
|
||||
delete this.data[key];
|
||||
|
||||
// Adjust the linked list references of the neighbor entries.
|
||||
const previousEntry = entry.previousEntry!;
|
||||
previousEntry.nextEntry = entry.nextEntry;
|
||||
if (entry.nextEntry) {
|
||||
entry.nextEntry.previousEntry = previousEntry;
|
||||
}
|
||||
|
||||
// When the deleted entry was the last one, we need to
|
||||
// adjust the lastEntry reference.
|
||||
if (this.lastEntry === entry) {
|
||||
this.lastEntry = previousEntry;
|
||||
}
|
||||
|
||||
// Adjust the forward reference of the deleted entry
|
||||
// in case an iterator still references it. This allows us
|
||||
// to throw away the entry, but when an active iterator
|
||||
// (which points to the current entry) continues, it will
|
||||
// navigate to the entry that originally came before the
|
||||
// current one and skip it.
|
||||
entry.previousEntry = undefined;
|
||||
entry.nextEntry = previousEntry;
|
||||
entry.skipNext = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.data = createDictionaryObject<MapEntry<T>>();
|
||||
this.size = 0;
|
||||
|
||||
// Reset the linked list. Note that we must adjust the forward
|
||||
// references of the deleted entries to ensure iterators stuck
|
||||
// in the middle of the list don't continue with deleted entries,
|
||||
// but can continue with new entries added after the clear()
|
||||
// operation.
|
||||
const firstEntry = this.firstEntry;
|
||||
let currentEntry = firstEntry.nextEntry;
|
||||
while (currentEntry) {
|
||||
const nextEntry = currentEntry.nextEntry;
|
||||
currentEntry.previousEntry = undefined;
|
||||
currentEntry.nextEntry = firstEntry;
|
||||
currentEntry.skipNext = true;
|
||||
|
||||
currentEntry = nextEntry;
|
||||
}
|
||||
firstEntry.nextEntry = undefined;
|
||||
this.lastEntry = firstEntry;
|
||||
}
|
||||
|
||||
keys(): Iterator<string> {
|
||||
return new MapIterator(this.firstEntry, key => key);
|
||||
}
|
||||
|
||||
values(): Iterator<T> {
|
||||
return new MapIterator(this.firstEntry, (_key, value) => value);
|
||||
}
|
||||
|
||||
entries(): Iterator<[string, T]> {
|
||||
return new MapIterator(this.firstEntry, (key, value) => [key, value] as [string, T]);
|
||||
}
|
||||
|
||||
forEach(action: (value: T, key: string) => void): void {
|
||||
const iterator = this.entries();
|
||||
while (true) {
|
||||
const iterResult = iterator.next();
|
||||
if (iterResult.done) {
|
||||
break;
|
||||
}
|
||||
|
||||
const [key, value] = iterResult.value;
|
||||
action(value, key);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function length(array: readonly any[] | undefined): number {
|
||||
return array ? array.length : 0;
|
||||
}
|
||||
|
@ -2052,20 +1873,6 @@ namespace ts {
|
|||
return str.indexOf(substring) !== -1;
|
||||
}
|
||||
|
||||
export function fileExtensionIs(path: string, extension: string): boolean {
|
||||
return path.length > extension.length && endsWith(path, extension);
|
||||
}
|
||||
|
||||
export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean {
|
||||
for (const extension of extensions) {
|
||||
if (fileExtensionIs(path, extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a string like "jquery-min.4.2.3" and returns "jquery"
|
||||
*/
|
||||
|
|
|
@ -221,6 +221,40 @@ namespace ts {
|
|||
|
||||
let isDebugInfoEnabled = false;
|
||||
|
||||
interface ExtendedDebugModule {
|
||||
init(_ts: typeof ts): void;
|
||||
formatControlFlowGraph(flowNode: FlowNode): string;
|
||||
}
|
||||
|
||||
let extendedDebugModule: ExtendedDebugModule | undefined;
|
||||
|
||||
function extendedDebug() {
|
||||
enableDebugInfo();
|
||||
if (!extendedDebugModule) {
|
||||
throw new Error("Debugging helpers could not be loaded.");
|
||||
}
|
||||
return extendedDebugModule;
|
||||
}
|
||||
|
||||
export function printControlFlowGraph(flowNode: FlowNode) {
|
||||
return console.log(formatControlFlowGraph(flowNode));
|
||||
}
|
||||
|
||||
export function formatControlFlowGraph(flowNode: FlowNode) {
|
||||
return extendedDebug().formatControlFlowGraph(flowNode);
|
||||
}
|
||||
|
||||
export function attachFlowNodeDebugInfo(flowNode: FlowNode) {
|
||||
if (isDebugInfoEnabled) {
|
||||
if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator
|
||||
Object.defineProperties(flowNode, {
|
||||
__debugFlowFlags: { get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } },
|
||||
__debugToString: { value(this: FlowNode) { return formatControlFlowGraph(this); } }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects debug information into frequently used types.
|
||||
*/
|
||||
|
@ -266,6 +300,21 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
// attempt to load extended debugging information
|
||||
try {
|
||||
if (sys && sys.require) {
|
||||
const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath()));
|
||||
const result = sys.require(basePath, "./compiler-debug") as RequireResult<ExtendedDebugModule>;
|
||||
if (!result.error) {
|
||||
result.module.init(ts);
|
||||
extendedDebugModule = result.module;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
isDebugInfoEnabled = true;
|
||||
}
|
||||
|
||||
|
|
855
src/compiler/path.ts
Normal file
855
src/compiler/path.ts
Normal file
|
@ -0,0 +1,855 @@
|
|||
/* @internal */
|
||||
namespace ts {
|
||||
/**
|
||||
* Internally, we represent paths as strings with '/' as the directory separator.
|
||||
* When we make system calls (eg: LanguageServiceHost.getDirectory()),
|
||||
* we expect the host to correctly handle paths in our specified format.
|
||||
*/
|
||||
export const directorySeparator = "/";
|
||||
const altDirectorySeparator = "\\";
|
||||
const urlSchemeSeparator = "://";
|
||||
const backslashRegExp = /\\/g;
|
||||
|
||||
//// Path Tests
|
||||
|
||||
/**
|
||||
* Determines whether a charCode corresponds to `/` or `\`.
|
||||
*/
|
||||
export function isAnyDirectorySeparator(charCode: number): boolean {
|
||||
return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path starts with a URL scheme (e.g. starts with `http://`, `ftp://`, `file://`, etc.).
|
||||
*/
|
||||
export function isUrl(path: string) {
|
||||
return getEncodedRootLength(path) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path is an absolute disk path (e.g. starts with `/`, or a dos path
|
||||
* like `c:`, `c:\` or `c:/`).
|
||||
*/
|
||||
export function isRootedDiskPath(path: string) {
|
||||
return getEncodedRootLength(path) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path consists only of a path root.
|
||||
*/
|
||||
export function isDiskPathRoot(path: string) {
|
||||
const rootLength = getEncodedRootLength(path);
|
||||
return rootLength > 0 && rootLength === path.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path starts with an absolute path component (i.e. `/`, `c:/`, `file://`, etc.).
|
||||
*
|
||||
* ```ts
|
||||
* // POSIX
|
||||
* pathIsAbsolute("/path/to/file.ext") === true
|
||||
* // DOS
|
||||
* pathIsAbsolute("c:/path/to/file.ext") === true
|
||||
* // URL
|
||||
* pathIsAbsolute("file:///path/to/file.ext") === true
|
||||
* // Non-absolute
|
||||
* pathIsAbsolute("path/to/file.ext") === false
|
||||
* pathIsAbsolute("./path/to/file.ext") === false
|
||||
* ```
|
||||
*/
|
||||
export function pathIsAbsolute(path: string): boolean {
|
||||
return getEncodedRootLength(path) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path starts with a relative path component (i.e. `.` or `..`).
|
||||
*/
|
||||
export function pathIsRelative(path: string): boolean {
|
||||
return /^\.\.?($|[\\/])/.test(path);
|
||||
}
|
||||
|
||||
export function hasExtension(fileName: string): boolean {
|
||||
return stringContains(getBaseFileName(fileName), ".");
|
||||
}
|
||||
|
||||
export function fileExtensionIs(path: string, extension: string): boolean {
|
||||
return path.length > extension.length && endsWith(path, extension);
|
||||
}
|
||||
|
||||
export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean {
|
||||
for (const extension of extensions) {
|
||||
if (fileExtensionIs(path, extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path has a trailing separator (`/` or `\\`).
|
||||
*/
|
||||
export function hasTrailingDirectorySeparator(path: string) {
|
||||
return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1));
|
||||
}
|
||||
|
||||
//// Path Parsing
|
||||
|
||||
function isVolumeCharacter(charCode: number) {
|
||||
return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) ||
|
||||
(charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z);
|
||||
}
|
||||
|
||||
function getFileUrlVolumeSeparatorEnd(url: string, start: number) {
|
||||
const ch0 = url.charCodeAt(start);
|
||||
if (ch0 === CharacterCodes.colon) return start + 1;
|
||||
if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) {
|
||||
const ch2 = url.charCodeAt(start + 2);
|
||||
if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
|
||||
* If the root is part of a URL, the twos-complement of the root length is returned.
|
||||
*/
|
||||
function getEncodedRootLength(path: string): number {
|
||||
if (!path) return 0;
|
||||
const ch0 = path.charCodeAt(0);
|
||||
|
||||
// POSIX or UNC
|
||||
if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) {
|
||||
if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\")
|
||||
|
||||
const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2);
|
||||
if (p1 < 0) return path.length; // UNC: "//server" or "\\server"
|
||||
|
||||
return p1 + 1; // UNC: "//server/" or "\\server\"
|
||||
}
|
||||
|
||||
// DOS
|
||||
if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) {
|
||||
const ch2 = path.charCodeAt(2);
|
||||
if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\"
|
||||
if (path.length === 2) return 2; // DOS: "c:" (but not "c:d")
|
||||
}
|
||||
|
||||
// URL
|
||||
const schemeEnd = path.indexOf(urlSchemeSeparator);
|
||||
if (schemeEnd !== -1) {
|
||||
const authorityStart = schemeEnd + urlSchemeSeparator.length;
|
||||
const authorityEnd = path.indexOf(directorySeparator, authorityStart);
|
||||
if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path"
|
||||
// For local "file" URLs, include the leading DOS volume (if present).
|
||||
// Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a
|
||||
// special case interpreted as "the machine from which the URL is being interpreted".
|
||||
const scheme = path.slice(0, schemeEnd);
|
||||
const authority = path.slice(authorityStart, authorityEnd);
|
||||
if (scheme === "file" && (authority === "" || authority === "localhost") &&
|
||||
isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) {
|
||||
const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2);
|
||||
if (volumeSeparatorEnd !== -1) {
|
||||
if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) {
|
||||
// URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/"
|
||||
return ~(volumeSeparatorEnd + 1);
|
||||
}
|
||||
if (volumeSeparatorEnd === path.length) {
|
||||
// URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a"
|
||||
// but not "file:///c:d" or "file:///c%3ad"
|
||||
return ~volumeSeparatorEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ~(authorityEnd + 1); // URL: "file://server/", "http://server/"
|
||||
}
|
||||
return ~path.length; // URL: "file://server", "http://server"
|
||||
}
|
||||
|
||||
// relative
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
|
||||
*
|
||||
* For example:
|
||||
* ```ts
|
||||
* getRootLength("a") === 0 // ""
|
||||
* getRootLength("/") === 1 // "/"
|
||||
* getRootLength("c:") === 2 // "c:"
|
||||
* getRootLength("c:d") === 0 // ""
|
||||
* getRootLength("c:/") === 3 // "c:/"
|
||||
* getRootLength("c:\\") === 3 // "c:\\"
|
||||
* getRootLength("//server") === 7 // "//server"
|
||||
* getRootLength("//server/share") === 8 // "//server/"
|
||||
* getRootLength("\\\\server") === 7 // "\\\\server"
|
||||
* getRootLength("\\\\server\\share") === 8 // "\\\\server\\"
|
||||
* getRootLength("file:///path") === 8 // "file:///"
|
||||
* getRootLength("file:///c:") === 10 // "file:///c:"
|
||||
* getRootLength("file:///c:d") === 8 // "file:///"
|
||||
* getRootLength("file:///c:/path") === 11 // "file:///c:/"
|
||||
* getRootLength("file://server") === 13 // "file://server"
|
||||
* getRootLength("file://server/path") === 14 // "file://server/"
|
||||
* getRootLength("http://server") === 13 // "http://server"
|
||||
* getRootLength("http://server/path") === 14 // "http://server/"
|
||||
* ```
|
||||
*/
|
||||
export function getRootLength(path: string) {
|
||||
const rootLength = getEncodedRootLength(path);
|
||||
return rootLength < 0 ? ~rootLength : rootLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path except for its basename. Semantics align with NodeJS's `path.dirname`
|
||||
* except that we support URLs as well.
|
||||
*
|
||||
* ```ts
|
||||
* // POSIX
|
||||
* getDirectoryPath("/path/to/file.ext") === "/path/to"
|
||||
* getDirectoryPath("/path/to/") === "/path"
|
||||
* getDirectoryPath("/") === "/"
|
||||
* // DOS
|
||||
* getDirectoryPath("c:/path/to/file.ext") === "c:/path/to"
|
||||
* getDirectoryPath("c:/path/to/") === "c:/path"
|
||||
* getDirectoryPath("c:/") === "c:/"
|
||||
* getDirectoryPath("c:") === "c:"
|
||||
* // URL
|
||||
* getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to"
|
||||
* getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path"
|
||||
* getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/"
|
||||
* getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org"
|
||||
* ```
|
||||
*/
|
||||
export function getDirectoryPath(path: Path): Path;
|
||||
/**
|
||||
* Returns the path except for its basename. Semantics align with NodeJS's `path.dirname`
|
||||
* except that we support URLs as well.
|
||||
*
|
||||
* ```ts
|
||||
* // POSIX
|
||||
* getDirectoryPath("/path/to/file.ext") === "/path/to"
|
||||
* getDirectoryPath("/path/to/") === "/path"
|
||||
* getDirectoryPath("/") === "/"
|
||||
* // DOS
|
||||
* getDirectoryPath("c:/path/to/file.ext") === "c:/path/to"
|
||||
* getDirectoryPath("c:/path/to/") === "c:/path"
|
||||
* getDirectoryPath("c:/") === "c:/"
|
||||
* getDirectoryPath("c:") === "c:"
|
||||
* // URL
|
||||
* getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to"
|
||||
* getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path"
|
||||
* getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/"
|
||||
* getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org"
|
||||
* getDirectoryPath("file://server/path/to/file.ext") === "file://server/path/to"
|
||||
* getDirectoryPath("file://server/path/to") === "file://server/path"
|
||||
* getDirectoryPath("file://server/") === "file://server/"
|
||||
* getDirectoryPath("file://server") === "file://server"
|
||||
* getDirectoryPath("file:///path/to/file.ext") === "file:///path/to"
|
||||
* getDirectoryPath("file:///path/to") === "file:///path"
|
||||
* getDirectoryPath("file:///") === "file:///"
|
||||
* getDirectoryPath("file://") === "file://"
|
||||
* ```
|
||||
*/
|
||||
export function getDirectoryPath(path: string): string;
|
||||
export function getDirectoryPath(path: string): string {
|
||||
path = normalizeSlashes(path);
|
||||
|
||||
// If the path provided is itself the root, then return it.
|
||||
const rootLength = getRootLength(path);
|
||||
if (rootLength === path.length) return path;
|
||||
|
||||
// return the leading portion of the path up to the last (non-terminal) directory separator
|
||||
// but not including any trailing directory separator.
|
||||
path = removeTrailingDirectorySeparator(path);
|
||||
return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path except for its containing directory name.
|
||||
* Semantics align with NodeJS's `path.basename` except that we support URL's as well.
|
||||
*
|
||||
* ```ts
|
||||
* // POSIX
|
||||
* getBaseFileName("/path/to/file.ext") === "file.ext"
|
||||
* getBaseFileName("/path/to/") === "to"
|
||||
* getBaseFileName("/") === ""
|
||||
* // DOS
|
||||
* getBaseFileName("c:/path/to/file.ext") === "file.ext"
|
||||
* getBaseFileName("c:/path/to/") === "to"
|
||||
* getBaseFileName("c:/") === ""
|
||||
* getBaseFileName("c:") === ""
|
||||
* // URL
|
||||
* getBaseFileName("http://typescriptlang.org/path/to/file.ext") === "file.ext"
|
||||
* getBaseFileName("http://typescriptlang.org/path/to/") === "to"
|
||||
* getBaseFileName("http://typescriptlang.org/") === ""
|
||||
* getBaseFileName("http://typescriptlang.org") === ""
|
||||
* getBaseFileName("file://server/path/to/file.ext") === "file.ext"
|
||||
* getBaseFileName("file://server/path/to/") === "to"
|
||||
* getBaseFileName("file://server/") === ""
|
||||
* getBaseFileName("file://server") === ""
|
||||
* getBaseFileName("file:///path/to/file.ext") === "file.ext"
|
||||
* getBaseFileName("file:///path/to/") === "to"
|
||||
* getBaseFileName("file:///") === ""
|
||||
* getBaseFileName("file://") === ""
|
||||
* ```
|
||||
*/
|
||||
export function getBaseFileName(path: string): string;
|
||||
/**
|
||||
* Gets the portion of a path following the last (non-terminal) separator (`/`).
|
||||
* Semantics align with NodeJS's `path.basename` except that we support URL's as well.
|
||||
* If the base name has any one of the provided extensions, it is removed.
|
||||
*
|
||||
* ```ts
|
||||
* getBaseFileName("/path/to/file.ext", ".ext", true) === "file"
|
||||
* getBaseFileName("/path/to/file.js", ".ext", true) === "file.js"
|
||||
* getBaseFileName("/path/to/file.js", [".ext", ".js"], true) === "file"
|
||||
* getBaseFileName("/path/to/file.ext", ".EXT", false) === "file.ext"
|
||||
* ```
|
||||
*/
|
||||
export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string;
|
||||
export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) {
|
||||
path = normalizeSlashes(path);
|
||||
|
||||
// if the path provided is itself the root, then it has not file name.
|
||||
const rootLength = getRootLength(path);
|
||||
if (rootLength === path.length) return "";
|
||||
|
||||
// return the trailing portion of the path starting after the last (non-terminal) directory
|
||||
// separator but not including any trailing directory separator.
|
||||
path = removeTrailingDirectorySeparator(path);
|
||||
const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1));
|
||||
const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined;
|
||||
return extension ? name.slice(0, name.length - extension.length) : name;
|
||||
}
|
||||
|
||||
function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) {
|
||||
if (!startsWith(extension, ".")) extension = "." + extension;
|
||||
if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === CharacterCodes.dot) {
|
||||
const pathExtension = path.slice(path.length - extension.length);
|
||||
if (stringEqualityComparer(pathExtension, extension)) {
|
||||
return pathExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) {
|
||||
if (typeof extensions === "string") {
|
||||
return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || "";
|
||||
}
|
||||
for (const extension of extensions) {
|
||||
const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer);
|
||||
if (result) return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file extension for a path.
|
||||
*
|
||||
* ```ts
|
||||
* getAnyExtensionFromPath("/path/to/file.ext") === ".ext"
|
||||
* getAnyExtensionFromPath("/path/to/file.ext/") === ".ext"
|
||||
* getAnyExtensionFromPath("/path/to/file") === ""
|
||||
* getAnyExtensionFromPath("/path/to.ext/file") === ""
|
||||
* ```
|
||||
*/
|
||||
export function getAnyExtensionFromPath(path: string): string;
|
||||
/**
|
||||
* Gets the file extension for a path, provided it is one of the provided extensions.
|
||||
*
|
||||
* ```ts
|
||||
* getAnyExtensionFromPath("/path/to/file.ext", ".ext", true) === ".ext"
|
||||
* getAnyExtensionFromPath("/path/to/file.js", ".ext", true) === ""
|
||||
* getAnyExtensionFromPath("/path/to/file.js", [".ext", ".js"], true) === ".js"
|
||||
* getAnyExtensionFromPath("/path/to/file.ext", ".EXT", false) === ""
|
||||
*/
|
||||
export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string;
|
||||
export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string {
|
||||
// Retrieves any string from the final "." onwards from a base file name.
|
||||
// Unlike extensionFromPath, which throws an exception on unrecognized extensions.
|
||||
if (extensions) {
|
||||
return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive);
|
||||
}
|
||||
const baseFileName = getBaseFileName(path);
|
||||
const extensionIndex = baseFileName.lastIndexOf(".");
|
||||
if (extensionIndex >= 0) {
|
||||
return baseFileName.substring(extensionIndex);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function pathComponents(path: string, rootLength: number) {
|
||||
const root = path.substring(0, rootLength);
|
||||
const rest = path.substring(rootLength).split(directorySeparator);
|
||||
if (rest.length && !lastOrUndefined(rest)) rest.pop();
|
||||
return [root, ...rest];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a path into an array containing a root component (at index 0) and zero or more path
|
||||
* components (at indices > 0). The result is not normalized.
|
||||
* If the path is relative, the root component is `""`.
|
||||
* If the path is absolute, the root component includes the first path separator (`/`).
|
||||
*
|
||||
* ```ts
|
||||
* // POSIX
|
||||
* getPathComponents("/path/to/file.ext") === ["/", "path", "to", "file.ext"]
|
||||
* getPathComponents("/path/to/") === ["/", "path", "to"]
|
||||
* getPathComponents("/") === ["/"]
|
||||
* // DOS
|
||||
* getPathComponents("c:/path/to/file.ext") === ["c:/", "path", "to", "file.ext"]
|
||||
* getPathComponents("c:/path/to/") === ["c:/", "path", "to"]
|
||||
* getPathComponents("c:/") === ["c:/"]
|
||||
* getPathComponents("c:") === ["c:"]
|
||||
* // URL
|
||||
* getPathComponents("http://typescriptlang.org/path/to/file.ext") === ["http://typescriptlang.org/", "path", "to", "file.ext"]
|
||||
* getPathComponents("http://typescriptlang.org/path/to/") === ["http://typescriptlang.org/", "path", "to"]
|
||||
* getPathComponents("http://typescriptlang.org/") === ["http://typescriptlang.org/"]
|
||||
* getPathComponents("http://typescriptlang.org") === ["http://typescriptlang.org"]
|
||||
* getPathComponents("file://server/path/to/file.ext") === ["file://server/", "path", "to", "file.ext"]
|
||||
* getPathComponents("file://server/path/to/") === ["file://server/", "path", "to"]
|
||||
* getPathComponents("file://server/") === ["file://server/"]
|
||||
* getPathComponents("file://server") === ["file://server"]
|
||||
* getPathComponents("file:///path/to/file.ext") === ["file:///", "path", "to", "file.ext"]
|
||||
* getPathComponents("file:///path/to/") === ["file:///", "path", "to"]
|
||||
* getPathComponents("file:///") === ["file:///"]
|
||||
* getPathComponents("file://") === ["file://"]
|
||||
*/
|
||||
export function getPathComponents(path: string, currentDirectory = "") {
|
||||
path = combinePaths(currentDirectory, path);
|
||||
return pathComponents(path, getRootLength(path));
|
||||
}
|
||||
|
||||
//// Path Formatting
|
||||
|
||||
/**
|
||||
* Formats a parsed path consisting of a root component (at index 0) and zero or more path
|
||||
* segments (at indices > 0).
|
||||
*
|
||||
* ```ts
|
||||
* getPathFromPathComponents(["/", "path", "to", "file.ext"]) === "/path/to/file.ext"
|
||||
* ```
|
||||
*/
|
||||
export function getPathFromPathComponents(pathComponents: readonly string[]) {
|
||||
if (pathComponents.length === 0) return "";
|
||||
|
||||
const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]);
|
||||
return root + pathComponents.slice(1).join(directorySeparator);
|
||||
}
|
||||
|
||||
//// Path Normalization
|
||||
|
||||
/**
|
||||
* Normalize path separators, converting `\` into `/`.
|
||||
*/
|
||||
export function normalizeSlashes(path: string): string {
|
||||
return path.replace(backslashRegExp, directorySeparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce an array of path components to a more simplified path by navigating any
|
||||
* `"."` or `".."` entries in the path.
|
||||
*/
|
||||
export function reducePathComponents(components: readonly string[]) {
|
||||
if (!some(components)) return [];
|
||||
const reduced = [components[0]];
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
const component = components[i];
|
||||
if (!component) continue;
|
||||
if (component === ".") continue;
|
||||
if (component === "..") {
|
||||
if (reduced.length > 1) {
|
||||
if (reduced[reduced.length - 1] !== "..") {
|
||||
reduced.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (reduced[0]) continue;
|
||||
}
|
||||
reduced.push(component);
|
||||
}
|
||||
return reduced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified.
|
||||
*
|
||||
* ```ts
|
||||
* // Non-rooted
|
||||
* combinePaths("path", "to", "file.ext") === "path/to/file.ext"
|
||||
* combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext"
|
||||
* // POSIX
|
||||
* combinePaths("/path", "to", "file.ext") === "/path/to/file.ext"
|
||||
* combinePaths("/path", "/to", "file.ext") === "/to/file.ext"
|
||||
* // DOS
|
||||
* combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext"
|
||||
* combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext"
|
||||
* // URL
|
||||
* combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext"
|
||||
* combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext"
|
||||
* ```
|
||||
*/
|
||||
export function combinePaths(path: string, ...paths: (string | undefined)[]): string {
|
||||
if (path) path = normalizeSlashes(path);
|
||||
for (let relativePath of paths) {
|
||||
if (!relativePath) continue;
|
||||
relativePath = normalizeSlashes(relativePath);
|
||||
if (!path || getRootLength(relativePath) !== 0) {
|
||||
path = relativePath;
|
||||
}
|
||||
else {
|
||||
path = ensureTrailingDirectorySeparator(path) + relativePath;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines and resolves paths. If a path is absolute, it replaces any previous path. Any
|
||||
* `.` and `..` path components are resolved. Trailing directory separators are preserved.
|
||||
*
|
||||
* ```ts
|
||||
* resolvePath("/path", "to", "file.ext") === "path/to/file.ext"
|
||||
* resolvePath("/path", "to", "file.ext/") === "path/to/file.ext/"
|
||||
* resolvePath("/path", "dir", "..", "to", "file.ext") === "path/to/file.ext"
|
||||
* ```
|
||||
*/
|
||||
export function resolvePath(path: string, ...paths: (string | undefined)[]): string {
|
||||
return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a path into an array containing a root component (at index 0) and zero or more path
|
||||
* components (at indices > 0). The result is normalized.
|
||||
* If the path is relative, the root component is `""`.
|
||||
* If the path is absolute, the root component includes the first path separator (`/`).
|
||||
*
|
||||
* ```ts
|
||||
* getNormalizedPathComponents("to/dir/../file.ext", "/path/") === ["/", "path", "to", "file.ext"]
|
||||
*/
|
||||
export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) {
|
||||
return reducePathComponents(getPathComponents(path, currentDirectory));
|
||||
}
|
||||
|
||||
export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) {
|
||||
return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory));
|
||||
}
|
||||
|
||||
export function normalizePath(path: string): string {
|
||||
path = normalizeSlashes(path);
|
||||
const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(path)));
|
||||
return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized;
|
||||
}
|
||||
|
||||
function getPathWithoutRoot(pathComponents: readonly string[]) {
|
||||
if (pathComponents.length === 0) return "";
|
||||
return pathComponents.slice(1).join(directorySeparator);
|
||||
}
|
||||
|
||||
export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) {
|
||||
return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory));
|
||||
}
|
||||
|
||||
export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path {
|
||||
const nonCanonicalizedPath = isRootedDiskPath(fileName)
|
||||
? normalizePath(fileName)
|
||||
: getNormalizedAbsolutePath(fileName, basePath);
|
||||
return <Path>getCanonicalFileName(nonCanonicalizedPath);
|
||||
}
|
||||
|
||||
export function normalizePathAndParts(path: string): { path: string, parts: string[] } {
|
||||
path = normalizeSlashes(path);
|
||||
const [root, ...parts] = reducePathComponents(getPathComponents(path));
|
||||
if (parts.length) {
|
||||
const joinedParts = root + parts.join(directorySeparator);
|
||||
return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts };
|
||||
}
|
||||
else {
|
||||
return { path: root, parts };
|
||||
}
|
||||
}
|
||||
|
||||
//// Path Mutation
|
||||
|
||||
/**
|
||||
* Removes a trailing directory separator from a path, if it does not already have one.
|
||||
*
|
||||
* ```ts
|
||||
* removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext"
|
||||
* removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext"
|
||||
* ```
|
||||
*/
|
||||
export function removeTrailingDirectorySeparator(path: Path): Path;
|
||||
export function removeTrailingDirectorySeparator(path: string): string;
|
||||
export function removeTrailingDirectorySeparator(path: string) {
|
||||
if (hasTrailingDirectorySeparator(path)) {
|
||||
return path.substr(0, path.length - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trailing directory separator to a path, if it does not already have one.
|
||||
*
|
||||
* ```ts
|
||||
* ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/"
|
||||
* ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/"
|
||||
* ```
|
||||
*/
|
||||
export function ensureTrailingDirectorySeparator(path: Path): Path;
|
||||
export function ensureTrailingDirectorySeparator(path: string): string;
|
||||
export function ensureTrailingDirectorySeparator(path: string) {
|
||||
if (!hasTrailingDirectorySeparator(path)) {
|
||||
return path + directorySeparator;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed
|
||||
* with `./` or `../`) so as not to be confused with an unprefixed module name.
|
||||
*
|
||||
* ```ts
|
||||
* ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext"
|
||||
* ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext"
|
||||
* ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext"
|
||||
* ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext"
|
||||
* ```
|
||||
*/
|
||||
export function ensurePathIsNonModuleName(path: string): string {
|
||||
return !pathIsAbsolute(path) && !pathIsRelative(path) ? "./" + path : path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the extension of a path to the provided extension.
|
||||
*
|
||||
* ```ts
|
||||
* changeAnyExtension("/path/to/file.ext", ".js") === "/path/to/file.js"
|
||||
* ```
|
||||
*/
|
||||
export function changeAnyExtension(path: string, ext: string): string;
|
||||
/**
|
||||
* Changes the extension of a path to the provided extension if it has one of the provided extensions.
|
||||
*
|
||||
* ```ts
|
||||
* changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js"
|
||||
* changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext"
|
||||
* changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js"
|
||||
* ```
|
||||
*/
|
||||
export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string;
|
||||
export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) {
|
||||
const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path);
|
||||
return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path;
|
||||
}
|
||||
|
||||
//// Path Comparisons
|
||||
|
||||
// check path for these segments: '', '.'. '..'
|
||||
const relativePathSegmentRegExp = /(^|\/)\.{0,2}($|\/)/;
|
||||
|
||||
function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) {
|
||||
if (a === b) return Comparison.EqualTo;
|
||||
if (a === undefined) return Comparison.LessThan;
|
||||
if (b === undefined) return Comparison.GreaterThan;
|
||||
|
||||
// NOTE: Performance optimization - shortcut if the root segments differ as there would be no
|
||||
// need to perform path reduction.
|
||||
const aRoot = a.substring(0, getRootLength(a));
|
||||
const bRoot = b.substring(0, getRootLength(b));
|
||||
const result = compareStringsCaseInsensitive(aRoot, bRoot);
|
||||
if (result !== Comparison.EqualTo) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOTE: Performance optimization - shortcut if there are no relative path segments in
|
||||
// the non-root portion of the path
|
||||
const aRest = a.substring(aRoot.length);
|
||||
const bRest = b.substring(bRoot.length);
|
||||
if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) {
|
||||
return componentComparer(aRest, bRest);
|
||||
}
|
||||
|
||||
// The path contains a relative path segment. Normalize the paths and perform a slower component
|
||||
// by component comparison.
|
||||
const aComponents = reducePathComponents(getPathComponents(a));
|
||||
const bComponents = reducePathComponents(getPathComponents(b));
|
||||
const sharedLength = Math.min(aComponents.length, bComponents.length);
|
||||
for (let i = 1; i < sharedLength; i++) {
|
||||
const result = componentComparer(aComponents[i], bComponents[i]);
|
||||
if (result !== Comparison.EqualTo) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return compareValues(aComponents.length, bComponents.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-sensitive comparison of two paths. Path roots are always compared case-insensitively.
|
||||
*/
|
||||
export function comparePathsCaseSensitive(a: string, b: string) {
|
||||
return comparePathsWorker(a, b, compareStringsCaseSensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-insensitive comparison of two paths.
|
||||
*/
|
||||
export function comparePathsCaseInsensitive(a: string, b: string) {
|
||||
return comparePathsWorker(a, b, compareStringsCaseInsensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two paths using the provided case sensitivity.
|
||||
*/
|
||||
export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison;
|
||||
export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison;
|
||||
export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) {
|
||||
if (typeof currentDirectory === "string") {
|
||||
a = combinePaths(currentDirectory, a);
|
||||
b = combinePaths(currentDirectory, b);
|
||||
}
|
||||
else if (typeof currentDirectory === "boolean") {
|
||||
ignoreCase = currentDirectory;
|
||||
}
|
||||
return comparePathsWorker(a, b, getStringComparer(ignoreCase));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a `parent` path contains a `child` path using the provide case sensitivity.
|
||||
*/
|
||||
export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean;
|
||||
export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean;
|
||||
export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) {
|
||||
if (typeof currentDirectory === "string") {
|
||||
parent = combinePaths(currentDirectory, parent);
|
||||
child = combinePaths(currentDirectory, child);
|
||||
}
|
||||
else if (typeof currentDirectory === "boolean") {
|
||||
ignoreCase = currentDirectory;
|
||||
}
|
||||
if (parent === undefined || child === undefined) return false;
|
||||
if (parent === child) return true;
|
||||
const parentComponents = reducePathComponents(getPathComponents(parent));
|
||||
const childComponents = reducePathComponents(getPathComponents(child));
|
||||
if (childComponents.length < parentComponents.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive;
|
||||
for (let i = 0; i < parentComponents.length; i++) {
|
||||
const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer;
|
||||
if (!equalityComparer(parentComponents[i], childComponents[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback.
|
||||
* Comparison is case-sensitive between the canonical paths.
|
||||
*
|
||||
* @deprecated Use `containsPath` if possible.
|
||||
*/
|
||||
export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean {
|
||||
const canonicalFileName = getCanonicalFileName(fileName);
|
||||
const canonicalDirectoryName = getCanonicalFileName(directoryName);
|
||||
return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\");
|
||||
}
|
||||
|
||||
//// Relative Paths
|
||||
|
||||
export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) {
|
||||
const fromComponents = reducePathComponents(getPathComponents(from));
|
||||
const toComponents = reducePathComponents(getPathComponents(to));
|
||||
|
||||
let start: number;
|
||||
for (start = 0; start < fromComponents.length && start < toComponents.length; start++) {
|
||||
const fromComponent = getCanonicalFileName(fromComponents[start]);
|
||||
const toComponent = getCanonicalFileName(toComponents[start]);
|
||||
const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer;
|
||||
if (!comparer(fromComponent, toComponent)) break;
|
||||
}
|
||||
|
||||
if (start === 0) {
|
||||
return toComponents;
|
||||
}
|
||||
|
||||
const components = toComponents.slice(start);
|
||||
const relative: string[] = [];
|
||||
for (; start < fromComponents.length; start++) {
|
||||
relative.push("..");
|
||||
}
|
||||
return ["", ...relative, ...components];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a relative path that can be used to traverse between `from` and `to`.
|
||||
*/
|
||||
export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string;
|
||||
/**
|
||||
* Gets a relative path that can be used to traverse between `from` and `to`.
|
||||
*/
|
||||
export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures
|
||||
export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) {
|
||||
Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative");
|
||||
const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity;
|
||||
const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false;
|
||||
const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName);
|
||||
return getPathFromPathComponents(pathComponents);
|
||||
}
|
||||
|
||||
export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string {
|
||||
return !isRootedDiskPath(absoluteOrRelativePath)
|
||||
? absoluteOrRelativePath
|
||||
: getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
}
|
||||
|
||||
export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) {
|
||||
return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName));
|
||||
}
|
||||
|
||||
export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) {
|
||||
const pathComponents = getPathComponentsRelativeTo(
|
||||
resolvePath(currentDirectory, directoryPathOrUrl),
|
||||
resolvePath(currentDirectory, relativeOrAbsolutePath),
|
||||
equateStringsCaseSensitive,
|
||||
getCanonicalFileName
|
||||
);
|
||||
|
||||
const firstComponent = pathComponents[0];
|
||||
if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) {
|
||||
const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///";
|
||||
pathComponents[0] = prefix + firstComponent;
|
||||
}
|
||||
|
||||
return getPathFromPathComponents(pathComponents);
|
||||
}
|
||||
|
||||
//// Path Traversal
|
||||
|
||||
/**
|
||||
* Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result.
|
||||
*/
|
||||
export function forEachAncestorDirectory<T>(directory: Path, callback: (directory: Path) => T | undefined): T | undefined;
|
||||
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T | undefined): T | undefined;
|
||||
export function forEachAncestorDirectory<T>(directory: Path, callback: (directory: Path) => T | undefined): T | undefined {
|
||||
while (true) {
|
||||
const result = callback(directory);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const parentPath = getDirectoryPath(directory);
|
||||
if (parentPath === directory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
directory = parentPath;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -682,6 +682,7 @@ namespace ts {
|
|||
/*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer;
|
||||
// For testing
|
||||
/*@internal*/ now?(): Date;
|
||||
/*@internal*/ require?(baseDir: string, moduleName: string): RequireResult;
|
||||
}
|
||||
|
||||
export interface FileWatcher {
|
||||
|
@ -876,6 +877,15 @@ namespace ts {
|
|||
bufferFrom,
|
||||
base64decode: input => bufferFrom(input, "base64").toString("utf8"),
|
||||
base64encode: input => bufferFrom(input).toString("base64"),
|
||||
require: (baseDir, moduleName) => {
|
||||
try {
|
||||
const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem);
|
||||
return { module: require(modulePath), modulePath, error: undefined };
|
||||
}
|
||||
catch (error) {
|
||||
return { module: undefined, modulePath: undefined, error };
|
||||
}
|
||||
}
|
||||
};
|
||||
return nodeSystem;
|
||||
|
||||
|
@ -1022,6 +1032,7 @@ namespace ts {
|
|||
return watchDirectoryUsingFsWatch;
|
||||
}
|
||||
|
||||
// defer watchDirectoryRecursively as it depends on `ts.createMap()` which may not be usable yet.
|
||||
const watchDirectory = tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile" ?
|
||||
createWatchDirectoryUsing(fsWatchFile) :
|
||||
tscWatchDirectory === "RecursiveDirectoryUsingDynamicPriorityPolling" ?
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
"outFile": "../../built/local/compiler.js"
|
||||
},
|
||||
|
||||
"references": [],
|
||||
"references": [
|
||||
{ "path": "../shims" },
|
||||
],
|
||||
|
||||
"files": [
|
||||
"core.ts",
|
||||
|
@ -15,6 +17,7 @@
|
|||
|
||||
"types.ts",
|
||||
"sys.ts",
|
||||
"path.ts",
|
||||
"diagnosticInformationMap.generated.ts",
|
||||
"scanner.ts",
|
||||
"utilities.ts",
|
||||
|
|
|
@ -2681,6 +2681,7 @@ namespace ts {
|
|||
isArrayType?: boolean;
|
||||
}
|
||||
|
||||
// NOTE: Ensure this is up-to-date with src/debug/debug.ts
|
||||
export const enum FlowFlags {
|
||||
Unreachable = 1 << 0, // Unreachable code
|
||||
Start = 1 << 1, // Start of flow graph
|
||||
|
@ -5123,6 +5124,11 @@ namespace ts {
|
|||
/* @internal */ spec: ConfigFileSpecs;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type RequireResult<T = {}> =
|
||||
| { module: T, modulePath?: string, error: undefined }
|
||||
| { module: undefined, modulePath?: undefined, error: { stack?: string, message?: string } };
|
||||
|
||||
export interface CreateProgramOptions {
|
||||
rootNames: readonly string[];
|
||||
options: CompilerOptions;
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace ts {
|
|||
|
||||
/** Create a new escaped identifier map. */
|
||||
export function createUnderscoreEscapedMap<T>(): UnderscoreEscapedMap<T> {
|
||||
return new MapCtr<T>() as UnderscoreEscapedMap<T>;
|
||||
return new Map<T>() as UnderscoreEscapedMap<T>;
|
||||
}
|
||||
|
||||
export function hasEntries(map: ReadonlyUnderscoreEscapedMap<any> | undefined): map is ReadonlyUnderscoreEscapedMap<any> {
|
||||
|
@ -94,13 +94,6 @@ namespace ts {
|
|||
};
|
||||
}
|
||||
|
||||
export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path {
|
||||
const nonCanonicalizedPath = isRootedDiskPath(fileName)
|
||||
? normalizePath(fileName)
|
||||
: getNormalizedAbsolutePath(fileName, basePath);
|
||||
return <Path>getCanonicalFileName(nonCanonicalizedPath);
|
||||
}
|
||||
|
||||
export function changesAffectModuleResolution(oldOptions: CompilerOptions, newOptions: CompilerOptions): boolean {
|
||||
return oldOptions.configFilePath !== newOptions.configFilePath ||
|
||||
optionsHaveModuleResolutionChanges(oldOptions, newOptions);
|
||||
|
@ -4735,25 +4728,6 @@ namespace ts {
|
|||
});
|
||||
}
|
||||
|
||||
/** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */
|
||||
export function forEachAncestorDirectory<T>(directory: Path, callback: (directory: Path) => T | undefined): T | undefined;
|
||||
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T | undefined): T | undefined;
|
||||
export function forEachAncestorDirectory<T>(directory: Path, callback: (directory: Path) => T | undefined): T | undefined {
|
||||
while (true) {
|
||||
const result = callback(directory);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const parentPath = getDirectoryPath(directory);
|
||||
if (parentPath === directory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
directory = parentPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if the given type is the constructor type for an abstract class
|
||||
export function isAbstractConstructorType(type: Type): boolean {
|
||||
return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isAbstractConstructorSymbol(type.symbol);
|
||||
|
@ -7639,294 +7613,6 @@ namespace ts {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally, we represent paths as strings with '/' as the directory separator.
|
||||
* When we make system calls (eg: LanguageServiceHost.getDirectory()),
|
||||
* we expect the host to correctly handle paths in our specified format.
|
||||
*/
|
||||
export const directorySeparator = "/";
|
||||
const altDirectorySeparator = "\\";
|
||||
const urlSchemeSeparator = "://";
|
||||
const backslashRegExp = /\\/g;
|
||||
|
||||
/**
|
||||
* Normalize path separators.
|
||||
*/
|
||||
export function normalizeSlashes(path: string): string {
|
||||
return path.replace(backslashRegExp, directorySeparator);
|
||||
}
|
||||
|
||||
function isVolumeCharacter(charCode: number) {
|
||||
return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) ||
|
||||
(charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z);
|
||||
}
|
||||
|
||||
function getFileUrlVolumeSeparatorEnd(url: string, start: number) {
|
||||
const ch0 = url.charCodeAt(start);
|
||||
if (ch0 === CharacterCodes.colon) return start + 1;
|
||||
if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) {
|
||||
const ch2 = url.charCodeAt(start + 2);
|
||||
if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
|
||||
* If the root is part of a URL, the twos-complement of the root length is returned.
|
||||
*/
|
||||
function getEncodedRootLength(path: string): number {
|
||||
if (!path) return 0;
|
||||
const ch0 = path.charCodeAt(0);
|
||||
|
||||
// POSIX or UNC
|
||||
if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) {
|
||||
if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\")
|
||||
|
||||
const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2);
|
||||
if (p1 < 0) return path.length; // UNC: "//server" or "\\server"
|
||||
|
||||
return p1 + 1; // UNC: "//server/" or "\\server\"
|
||||
}
|
||||
|
||||
// DOS
|
||||
if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) {
|
||||
const ch2 = path.charCodeAt(2);
|
||||
if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\"
|
||||
if (path.length === 2) return 2; // DOS: "c:" (but not "c:d")
|
||||
}
|
||||
|
||||
// URL
|
||||
const schemeEnd = path.indexOf(urlSchemeSeparator);
|
||||
if (schemeEnd !== -1) {
|
||||
const authorityStart = schemeEnd + urlSchemeSeparator.length;
|
||||
const authorityEnd = path.indexOf(directorySeparator, authorityStart);
|
||||
if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path"
|
||||
// For local "file" URLs, include the leading DOS volume (if present).
|
||||
// Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a
|
||||
// special case interpreted as "the machine from which the URL is being interpreted".
|
||||
const scheme = path.slice(0, schemeEnd);
|
||||
const authority = path.slice(authorityStart, authorityEnd);
|
||||
if (scheme === "file" && (authority === "" || authority === "localhost") &&
|
||||
isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) {
|
||||
const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2);
|
||||
if (volumeSeparatorEnd !== -1) {
|
||||
if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) {
|
||||
// URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/"
|
||||
return ~(volumeSeparatorEnd + 1);
|
||||
}
|
||||
if (volumeSeparatorEnd === path.length) {
|
||||
// URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a"
|
||||
// but not "file:///c:d" or "file:///c%3ad"
|
||||
return ~volumeSeparatorEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ~(authorityEnd + 1); // URL: "file://server/", "http://server/"
|
||||
}
|
||||
return ~path.length; // URL: "file://server", "http://server"
|
||||
}
|
||||
|
||||
// relative
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
|
||||
*
|
||||
* For example:
|
||||
* ```ts
|
||||
* getRootLength("a") === 0 // ""
|
||||
* getRootLength("/") === 1 // "/"
|
||||
* getRootLength("c:") === 2 // "c:"
|
||||
* getRootLength("c:d") === 0 // ""
|
||||
* getRootLength("c:/") === 3 // "c:/"
|
||||
* getRootLength("c:\\") === 3 // "c:\\"
|
||||
* getRootLength("//server") === 7 // "//server"
|
||||
* getRootLength("//server/share") === 8 // "//server/"
|
||||
* getRootLength("\\\\server") === 7 // "\\\\server"
|
||||
* getRootLength("\\\\server\\share") === 8 // "\\\\server\\"
|
||||
* getRootLength("file:///path") === 8 // "file:///"
|
||||
* getRootLength("file:///c:") === 10 // "file:///c:"
|
||||
* getRootLength("file:///c:d") === 8 // "file:///"
|
||||
* getRootLength("file:///c:/path") === 11 // "file:///c:/"
|
||||
* getRootLength("file://server") === 13 // "file://server"
|
||||
* getRootLength("file://server/path") === 14 // "file://server/"
|
||||
* getRootLength("http://server") === 13 // "http://server"
|
||||
* getRootLength("http://server/path") === 14 // "http://server/"
|
||||
* ```
|
||||
*/
|
||||
export function getRootLength(path: string) {
|
||||
const rootLength = getEncodedRootLength(path);
|
||||
return rootLength < 0 ? ~rootLength : rootLength;
|
||||
}
|
||||
|
||||
// TODO(rbuckton): replace references with `resolvePath`
|
||||
export function normalizePath(path: string): string {
|
||||
return resolvePath(path);
|
||||
}
|
||||
|
||||
export function normalizePathAndParts(path: string): { path: string, parts: string[] } {
|
||||
path = normalizeSlashes(path);
|
||||
const [root, ...parts] = reducePathComponents(getPathComponents(path));
|
||||
if (parts.length) {
|
||||
const joinedParts = root + parts.join(directorySeparator);
|
||||
return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts };
|
||||
}
|
||||
else {
|
||||
return { path: root, parts };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path except for its basename. Semantics align with NodeJS's `path.dirname`
|
||||
* except that we support URLs as well.
|
||||
*
|
||||
* ```ts
|
||||
* getDirectoryPath("/path/to/file.ext") === "/path/to"
|
||||
* getDirectoryPath("/path/to/") === "/path"
|
||||
* getDirectoryPath("/") === "/"
|
||||
* ```
|
||||
*/
|
||||
export function getDirectoryPath(path: Path): Path;
|
||||
/**
|
||||
* Returns the path except for its basename. Semantics align with NodeJS's `path.dirname`
|
||||
* except that we support URLs as well.
|
||||
*
|
||||
* ```ts
|
||||
* getDirectoryPath("/path/to/file.ext") === "/path/to"
|
||||
* getDirectoryPath("/path/to/") === "/path"
|
||||
* getDirectoryPath("/") === "/"
|
||||
* ```
|
||||
*/
|
||||
export function getDirectoryPath(path: string): string;
|
||||
export function getDirectoryPath(path: string): string {
|
||||
path = normalizeSlashes(path);
|
||||
|
||||
// If the path provided is itself the root, then return it.
|
||||
const rootLength = getRootLength(path);
|
||||
if (rootLength === path.length) return path;
|
||||
|
||||
// return the leading portion of the path up to the last (non-terminal) directory separator
|
||||
// but not including any trailing directory separator.
|
||||
path = removeTrailingDirectorySeparator(path);
|
||||
return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator)));
|
||||
}
|
||||
|
||||
export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean {
|
||||
const canonicalFileName = getCanonicalFileName(fileName);
|
||||
const canonicalDirectoryName = getCanonicalFileName(directoryName);
|
||||
return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\");
|
||||
}
|
||||
|
||||
export function isUrl(path: string) {
|
||||
return getEncodedRootLength(path) < 0;
|
||||
}
|
||||
|
||||
export function pathIsRelative(path: string): boolean {
|
||||
return /^\.\.?($|[\\/])/.test(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path is an absolute path (e.g. starts with `/`, or a dos path
|
||||
* like `c:`, `c:\` or `c:/`).
|
||||
*/
|
||||
export function isRootedDiskPath(path: string) {
|
||||
return getEncodedRootLength(path) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path consists only of a path root.
|
||||
*/
|
||||
export function isDiskPathRoot(path: string) {
|
||||
const rootLength = getEncodedRootLength(path);
|
||||
return rootLength > 0 && rootLength === path.length;
|
||||
}
|
||||
|
||||
export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string {
|
||||
return !isRootedDiskPath(absoluteOrRelativePath)
|
||||
? absoluteOrRelativePath
|
||||
: getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
}
|
||||
|
||||
function pathComponents(path: string, rootLength: number) {
|
||||
const root = path.substring(0, rootLength);
|
||||
const rest = path.substring(rootLength).split(directorySeparator);
|
||||
if (rest.length && !lastOrUndefined(rest)) rest.pop();
|
||||
return [root, ...rest];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a path into an array containing a root component (at index 0) and zero or more path
|
||||
* components (at indices > 0). The result is not normalized.
|
||||
* If the path is relative, the root component is `""`.
|
||||
* If the path is absolute, the root component includes the first path separator (`/`).
|
||||
*/
|
||||
export function getPathComponents(path: string, currentDirectory = "") {
|
||||
path = combinePaths(currentDirectory, path);
|
||||
const rootLength = getRootLength(path);
|
||||
return pathComponents(path, rootLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce an array of path components to a more simplified path by navigating any
|
||||
* `"."` or `".."` entries in the path.
|
||||
*/
|
||||
export function reducePathComponents(components: readonly string[]) {
|
||||
if (!some(components)) return [];
|
||||
const reduced = [components[0]];
|
||||
for (let i = 1; i < components.length; i++) {
|
||||
const component = components[i];
|
||||
if (!component) continue;
|
||||
if (component === ".") continue;
|
||||
if (component === "..") {
|
||||
if (reduced.length > 1) {
|
||||
if (reduced[reduced.length - 1] !== "..") {
|
||||
reduced.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (reduced[0]) continue;
|
||||
}
|
||||
reduced.push(component);
|
||||
}
|
||||
return reduced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a path into an array containing a root component (at index 0) and zero or more path
|
||||
* components (at indices > 0). The result is normalized.
|
||||
* If the path is relative, the root component is `""`.
|
||||
* If the path is absolute, the root component includes the first path separator (`/`).
|
||||
*/
|
||||
export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) {
|
||||
return reducePathComponents(getPathComponents(path, currentDirectory));
|
||||
}
|
||||
|
||||
export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) {
|
||||
return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a parsed path consisting of a root component (at index 0) and zero or more path
|
||||
* segments (at indices > 0).
|
||||
*/
|
||||
export function getPathFromPathComponents(pathComponents: readonly string[]) {
|
||||
if (pathComponents.length === 0) return "";
|
||||
|
||||
const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]);
|
||||
return root + pathComponents.slice(1).join(directorySeparator);
|
||||
}
|
||||
|
||||
export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) {
|
||||
return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory));
|
||||
}
|
||||
|
||||
function getPathWithoutRoot(pathComponents: readonly string[]) {
|
||||
if (pathComponents.length === 0) return "";
|
||||
return pathComponents.slice(1).join(directorySeparator);
|
||||
}
|
||||
|
||||
export function discoverProbableSymlinks(files: readonly SourceFile[], getCanonicalFileName: GetCanonicalFileName, cwd: string): ReadonlyMap<string> {
|
||||
const result = createMap<string>();
|
||||
const symlinks = flatten<readonly [string, string]>(mapDefined(files, sf =>
|
||||
|
@ -7960,278 +7646,8 @@ namespace ts {
|
|||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) {
|
||||
const fromComponents = reducePathComponents(getPathComponents(from));
|
||||
const toComponents = reducePathComponents(getPathComponents(to));
|
||||
|
||||
let start: number;
|
||||
for (start = 0; start < fromComponents.length && start < toComponents.length; start++) {
|
||||
const fromComponent = getCanonicalFileName(fromComponents[start]);
|
||||
const toComponent = getCanonicalFileName(toComponents[start]);
|
||||
const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer;
|
||||
if (!comparer(fromComponent, toComponent)) break;
|
||||
}
|
||||
|
||||
if (start === 0) {
|
||||
return toComponents;
|
||||
}
|
||||
|
||||
const components = toComponents.slice(start);
|
||||
const relative: string[] = [];
|
||||
for (; start < fromComponents.length; start++) {
|
||||
relative.push("..");
|
||||
}
|
||||
return ["", ...relative, ...components];
|
||||
}
|
||||
|
||||
export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) {
|
||||
return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a relative path that can be used to traverse between `from` and `to`.
|
||||
*/
|
||||
export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string;
|
||||
/**
|
||||
* Gets a relative path that can be used to traverse between `from` and `to`.
|
||||
*/
|
||||
export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures
|
||||
export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) {
|
||||
Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative");
|
||||
const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity;
|
||||
const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false;
|
||||
const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName);
|
||||
return getPathFromPathComponents(pathComponents);
|
||||
}
|
||||
|
||||
export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) {
|
||||
const pathComponents = getPathComponentsRelativeTo(
|
||||
resolvePath(currentDirectory, directoryPathOrUrl),
|
||||
resolvePath(currentDirectory, relativeOrAbsolutePath),
|
||||
equateStringsCaseSensitive,
|
||||
getCanonicalFileName
|
||||
);
|
||||
|
||||
const firstComponent = pathComponents[0];
|
||||
if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) {
|
||||
const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///";
|
||||
pathComponents[0] = prefix + firstComponent;
|
||||
}
|
||||
|
||||
return getPathFromPathComponents(pathComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed
|
||||
* with `./` or `../`) so as not to be confused with an unprefixed module name.
|
||||
*/
|
||||
export function ensurePathIsNonModuleName(path: string): string {
|
||||
return getRootLength(path) === 0 && !pathIsRelative(path) ? "./" + path : path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path except for its containing directory name.
|
||||
* Semantics align with NodeJS's `path.basename` except that we support URL's as well.
|
||||
*
|
||||
* ```ts
|
||||
* getBaseFileName("/path/to/file.ext") === "file.ext"
|
||||
* getBaseFileName("/path/to/") === "to"
|
||||
* getBaseFileName("/") === ""
|
||||
* ```
|
||||
*/
|
||||
export function getBaseFileName(path: string): string;
|
||||
/**
|
||||
* Gets the portion of a path following the last (non-terminal) separator (`/`).
|
||||
* Semantics align with NodeJS's `path.basename` except that we support URL's as well.
|
||||
* If the base name has any one of the provided extensions, it is removed.
|
||||
*
|
||||
* ```ts
|
||||
* getBaseFileName("/path/to/file.ext", ".ext", true) === "file"
|
||||
* getBaseFileName("/path/to/file.js", ".ext", true) === "file.js"
|
||||
* ```
|
||||
*/
|
||||
export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string;
|
||||
export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) {
|
||||
path = normalizeSlashes(path);
|
||||
|
||||
// if the path provided is itself the root, then it has not file name.
|
||||
const rootLength = getRootLength(path);
|
||||
if (rootLength === path.length) return "";
|
||||
|
||||
// return the trailing portion of the path starting after the last (non-terminal) directory
|
||||
// separator but not including any trailing directory separator.
|
||||
path = removeTrailingDirectorySeparator(path);
|
||||
const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1));
|
||||
const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined;
|
||||
return extension ? name.slice(0, name.length - extension.length) : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines paths. If a path is absolute, it replaces any previous path.
|
||||
*/
|
||||
export function combinePaths(path: string, ...paths: (string | undefined)[]): string {
|
||||
if (path) path = normalizeSlashes(path);
|
||||
for (let relativePath of paths) {
|
||||
if (!relativePath) continue;
|
||||
relativePath = normalizeSlashes(relativePath);
|
||||
if (!path || getRootLength(relativePath) !== 0) {
|
||||
path = relativePath;
|
||||
}
|
||||
else {
|
||||
path = ensureTrailingDirectorySeparator(path) + relativePath;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines and resolves paths. If a path is absolute, it replaces any previous path. Any
|
||||
* `.` and `..` path components are resolved.
|
||||
*/
|
||||
export function resolvePath(path: string, ...paths: (string | undefined)[]): string {
|
||||
const combined = some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path);
|
||||
const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(combined)));
|
||||
return normalized && hasTrailingDirectorySeparator(combined) ? ensureTrailingDirectorySeparator(normalized) : normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path has a trailing separator (`/` or `\\`).
|
||||
*/
|
||||
export function hasTrailingDirectorySeparator(path: string) {
|
||||
if (path.length === 0) return false;
|
||||
const ch = path.charCodeAt(path.length - 1);
|
||||
return ch === CharacterCodes.slash || ch === CharacterCodes.backslash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a trailing directory separator from a path.
|
||||
* @param path The path.
|
||||
*/
|
||||
export function removeTrailingDirectorySeparator(path: Path): Path;
|
||||
export function removeTrailingDirectorySeparator(path: string): string;
|
||||
export function removeTrailingDirectorySeparator(path: string) {
|
||||
if (hasTrailingDirectorySeparator(path)) {
|
||||
return path.substr(0, path.length - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trailing directory separator to a path, if it does not already have one.
|
||||
* @param path The path.
|
||||
*/
|
||||
export function ensureTrailingDirectorySeparator(path: Path): Path;
|
||||
export function ensureTrailingDirectorySeparator(path: string): string;
|
||||
export function ensureTrailingDirectorySeparator(path: string) {
|
||||
if (!hasTrailingDirectorySeparator(path)) {
|
||||
return path + directorySeparator;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// check path for these segments: '', '.'. '..'
|
||||
const relativePathSegmentRegExp = /(^|\/)\.{0,2}($|\/)/;
|
||||
|
||||
function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) {
|
||||
if (a === b) return Comparison.EqualTo;
|
||||
if (a === undefined) return Comparison.LessThan;
|
||||
if (b === undefined) return Comparison.GreaterThan;
|
||||
|
||||
// NOTE: Performance optimization - shortcut if the root segments differ as there would be no
|
||||
// need to perform path reduction.
|
||||
const aRoot = a.substring(0, getRootLength(a));
|
||||
const bRoot = b.substring(0, getRootLength(b));
|
||||
const result = compareStringsCaseInsensitive(aRoot, bRoot);
|
||||
if (result !== Comparison.EqualTo) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOTE: Performance optimization - shortcut if there are no relative path segments in
|
||||
// the non-root portion of the path
|
||||
const aRest = a.substring(aRoot.length);
|
||||
const bRest = b.substring(bRoot.length);
|
||||
if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) {
|
||||
return componentComparer(aRest, bRest);
|
||||
}
|
||||
|
||||
// The path contains a relative path segment. Normalize the paths and perform a slower component
|
||||
// by component comparison.
|
||||
const aComponents = reducePathComponents(getPathComponents(a));
|
||||
const bComponents = reducePathComponents(getPathComponents(b));
|
||||
const sharedLength = Math.min(aComponents.length, bComponents.length);
|
||||
for (let i = 1; i < sharedLength; i++) {
|
||||
const result = componentComparer(aComponents[i], bComponents[i]);
|
||||
if (result !== Comparison.EqualTo) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return compareValues(aComponents.length, bComponents.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-sensitive comparison of two paths.
|
||||
*/
|
||||
export function comparePathsCaseSensitive(a: string, b: string) {
|
||||
return comparePathsWorker(a, b, compareStringsCaseSensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-insensitive comparison of two paths.
|
||||
*/
|
||||
export function comparePathsCaseInsensitive(a: string, b: string) {
|
||||
return comparePathsWorker(a, b, compareStringsCaseInsensitive);
|
||||
}
|
||||
|
||||
export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison;
|
||||
export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison;
|
||||
export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) {
|
||||
if (typeof currentDirectory === "string") {
|
||||
a = combinePaths(currentDirectory, a);
|
||||
b = combinePaths(currentDirectory, b);
|
||||
}
|
||||
else if (typeof currentDirectory === "boolean") {
|
||||
ignoreCase = currentDirectory;
|
||||
}
|
||||
return comparePathsWorker(a, b, getStringComparer(ignoreCase));
|
||||
}
|
||||
|
||||
export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean;
|
||||
export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean;
|
||||
export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) {
|
||||
if (typeof currentDirectory === "string") {
|
||||
parent = combinePaths(currentDirectory, parent);
|
||||
child = combinePaths(currentDirectory, child);
|
||||
}
|
||||
else if (typeof currentDirectory === "boolean") {
|
||||
ignoreCase = currentDirectory;
|
||||
}
|
||||
if (parent === undefined || child === undefined) return false;
|
||||
if (parent === child) return true;
|
||||
const parentComponents = reducePathComponents(getPathComponents(parent));
|
||||
const childComponents = reducePathComponents(getPathComponents(child));
|
||||
if (childComponents.length < parentComponents.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive;
|
||||
for (let i = 0; i < parentComponents.length; i++) {
|
||||
const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer;
|
||||
if (!equalityComparer(parentComponents[i], childComponents[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isDirectorySeparator(charCode: number): boolean {
|
||||
return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash;
|
||||
}
|
||||
|
||||
function stripLeadingDirectorySeparator(s: string): string | undefined {
|
||||
return isDirectorySeparator(s.charCodeAt(0)) ? s.slice(1) : undefined;
|
||||
return isAnyDirectorySeparator(s.charCodeAt(0)) ? s.slice(1) : undefined;
|
||||
}
|
||||
|
||||
export function tryRemoveDirectoryPrefix(path: string, dirPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined {
|
||||
|
@ -8254,10 +7670,6 @@ namespace ts {
|
|||
|
||||
const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question];
|
||||
|
||||
export function hasExtension(fileName: string): boolean {
|
||||
return stringContains(getBaseFileName(fileName), ".");
|
||||
}
|
||||
|
||||
export const commonPackageFolders: readonly string[] = ["node_modules", "bower_components", "jspm_packages"];
|
||||
|
||||
const implicitExcludePathRegexPattern = `(?!(${commonPackageFolders.join("|")})(/|$))`;
|
||||
|
@ -8722,13 +8134,6 @@ namespace ts {
|
|||
return <T>changeAnyExtension(path, newExtension, extensionsToRemove, /*ignoreCase*/ false);
|
||||
}
|
||||
|
||||
export function changeAnyExtension(path: string, ext: string): string;
|
||||
export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string;
|
||||
export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) {
|
||||
const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path);
|
||||
return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path;
|
||||
}
|
||||
|
||||
export function tryParsePattern(pattern: string): Pattern | undefined {
|
||||
// This should be verified outside of here and a proper error thrown.
|
||||
Debug.assert(hasZeroOrOneAsteriskCharacter(pattern));
|
||||
|
@ -8771,42 +8176,6 @@ namespace ts {
|
|||
return find<Extension>(extensionsToRemove, e => fileExtensionIs(path, e));
|
||||
}
|
||||
|
||||
function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) {
|
||||
if (typeof extensions === "string") extensions = [extensions];
|
||||
for (let extension of extensions) {
|
||||
if (!startsWith(extension, ".")) extension = "." + extension;
|
||||
if (path.length >= extension.length && path.charAt(path.length - extension.length) === ".") {
|
||||
const pathExtension = path.slice(path.length - extension.length);
|
||||
if (stringEqualityComparer(pathExtension, extension)) {
|
||||
return pathExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file extension for a path.
|
||||
*/
|
||||
export function getAnyExtensionFromPath(path: string): string;
|
||||
/**
|
||||
* Gets the file extension for a path, provided it is one of the provided extensions.
|
||||
*/
|
||||
export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string;
|
||||
export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string {
|
||||
// Retrieves any string from the final "." onwards from a base file name.
|
||||
// Unlike extensionFromPath, which throws an exception on unrecognized extensions.
|
||||
if (extensions) {
|
||||
return getAnyExtensionFromPathWorker(path, extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive);
|
||||
}
|
||||
const baseFileName = getBaseFileName(path);
|
||||
const extensionIndex = baseFileName.lastIndexOf(".");
|
||||
if (extensionIndex >= 0) {
|
||||
return baseFileName.substring(extensionIndex);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
|
||||
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
|
||||
}
|
||||
|
|
499
src/debug/debug.ts
Normal file
499
src/debug/debug.ts
Normal file
|
@ -0,0 +1,499 @@
|
|||
/// <reference lib="es2019" />
|
||||
|
||||
/* @internal */
|
||||
namespace Debug {
|
||||
interface Node {
|
||||
kind: number;
|
||||
}
|
||||
|
||||
type FunctionExpression = Node;
|
||||
type ArrowFunction = Node;
|
||||
type MethodDeclaration = Node;
|
||||
type Expression = Node;
|
||||
type SourceFile = Node;
|
||||
|
||||
interface SwitchStatement extends Node {
|
||||
caseBlock: CaseBlock;
|
||||
}
|
||||
|
||||
interface CaseBlock extends Node {
|
||||
clauses: (CaseClause | DefaultClause)[];
|
||||
}
|
||||
|
||||
interface CaseClause extends Node {
|
||||
_caseclauseBrand: any;
|
||||
expression: Expression;
|
||||
}
|
||||
|
||||
interface DefaultClause extends Node {
|
||||
_defaultClauseBrand: any;
|
||||
}
|
||||
|
||||
interface TypeScriptModule {
|
||||
readonly SyntaxKind: {
|
||||
readonly CaseClause: number;
|
||||
readonly DefaultClause: number;
|
||||
};
|
||||
|
||||
readonly FlowFlags: {
|
||||
readonly Unreachable: number,
|
||||
readonly Start: number,
|
||||
readonly BranchLabel: number,
|
||||
readonly LoopLabel: number,
|
||||
readonly Assignment: number,
|
||||
readonly TrueCondition: number,
|
||||
readonly FalseCondition: number,
|
||||
readonly SwitchClause: number,
|
||||
readonly ArrayMutation: number,
|
||||
readonly Call: number,
|
||||
readonly Referenced: number,
|
||||
readonly Shared: number,
|
||||
readonly PreFinally: number,
|
||||
readonly AfterFinally: number,
|
||||
readonly Label: number,
|
||||
readonly Condition: number,
|
||||
};
|
||||
|
||||
getSourceFileOfNode(node: Node): SourceFile;
|
||||
getSourceTextOfNodeFromSourceFile(sourceFile: SourceFile, node: Node, includeTrivia?: boolean): string;
|
||||
isDefaultClause(node: Node): node is DefaultClause;
|
||||
}
|
||||
|
||||
type FlowNode =
|
||||
| AfterFinallyFlow
|
||||
| PreFinallyFlow
|
||||
| FlowStart
|
||||
| FlowLabel
|
||||
| FlowAssignment
|
||||
| FlowCall
|
||||
| FlowCondition
|
||||
| FlowSwitchClause
|
||||
| FlowArrayMutation
|
||||
;
|
||||
|
||||
interface FlowNodeBase {
|
||||
flags: FlowFlags;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
interface AfterFinallyFlow extends FlowNodeBase {
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
interface PreFinallyFlow extends FlowNodeBase {
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
interface FlowStart extends FlowNodeBase {
|
||||
node?: FunctionExpression | ArrowFunction | MethodDeclaration;
|
||||
}
|
||||
|
||||
interface FlowLabel extends FlowNodeBase {
|
||||
antecedents: FlowNode[] | undefined;
|
||||
}
|
||||
|
||||
interface FlowAssignment extends FlowNodeBase {
|
||||
node: Expression;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
interface FlowCall extends FlowNodeBase {
|
||||
node: Expression;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
interface FlowCondition extends FlowNodeBase {
|
||||
node: Expression;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
interface FlowSwitchClause extends FlowNodeBase {
|
||||
switchStatement: SwitchStatement;
|
||||
clauseStart: number;
|
||||
clauseEnd: number;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
interface FlowArrayMutation extends FlowNodeBase {
|
||||
node: Expression;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
type FlowFlags = number;
|
||||
let FlowFlags: TypeScriptModule["FlowFlags"];
|
||||
let getSourceFileOfNode: TypeScriptModule["getSourceFileOfNode"];
|
||||
let getSourceTextOfNodeFromSourceFile: TypeScriptModule["getSourceTextOfNodeFromSourceFile"];
|
||||
let isDefaultClause: TypeScriptModule["isDefaultClause"];
|
||||
|
||||
export function init(ts: TypeScriptModule) {
|
||||
FlowFlags = ts.FlowFlags;
|
||||
getSourceFileOfNode = ts.getSourceFileOfNode;
|
||||
getSourceTextOfNodeFromSourceFile = ts.getSourceTextOfNodeFromSourceFile;
|
||||
isDefaultClause = ts.isDefaultClause;
|
||||
}
|
||||
|
||||
let nextDebugFlowId = -1;
|
||||
|
||||
function getDebugFlowNodeId(f: FlowNode) {
|
||||
if (!f.id) {
|
||||
f.id = nextDebugFlowId;
|
||||
nextDebugFlowId--;
|
||||
}
|
||||
return f.id;
|
||||
}
|
||||
|
||||
export function formatControlFlowGraph(flowNode: FlowNode) {
|
||||
const enum BoxCharacter {
|
||||
lr = "─",
|
||||
ud = "│",
|
||||
dr = "╭",
|
||||
dl = "╮",
|
||||
ul = "╯",
|
||||
ur = "╰",
|
||||
udr = "├",
|
||||
udl = "┤",
|
||||
dlr = "┬",
|
||||
ulr = "┴",
|
||||
udlr = "╫",
|
||||
}
|
||||
|
||||
const enum Connection {
|
||||
Up = 1 << 0,
|
||||
Down = 1 << 1,
|
||||
Left = 1 << 2,
|
||||
Right = 1 << 3,
|
||||
|
||||
UpDown = Up | Down,
|
||||
LeftRight = Left | Right,
|
||||
UpLeft = Up | Left,
|
||||
UpRight = Up | Right,
|
||||
DownLeft = Down | Left,
|
||||
DownRight = Down | Right,
|
||||
UpDownLeft = UpDown | Left,
|
||||
UpDownRight = UpDown | Right,
|
||||
UpLeftRight = Up | LeftRight,
|
||||
DownLeftRight = Down | LeftRight,
|
||||
UpDownLeftRight = UpDown | LeftRight,
|
||||
|
||||
NoChildren = 1 << 4,
|
||||
}
|
||||
|
||||
interface FlowGraphNode {
|
||||
id: number;
|
||||
flowNode: FlowNode;
|
||||
edges: FlowGraphEdge[];
|
||||
text: string;
|
||||
lane: number;
|
||||
endLane: number;
|
||||
level: number;
|
||||
}
|
||||
|
||||
interface FlowGraphEdge {
|
||||
source: FlowGraphNode;
|
||||
target: FlowGraphNode;
|
||||
}
|
||||
|
||||
const hasAntecedentFlags =
|
||||
FlowFlags.Assignment |
|
||||
FlowFlags.Condition |
|
||||
FlowFlags.SwitchClause |
|
||||
FlowFlags.ArrayMutation |
|
||||
FlowFlags.Call |
|
||||
FlowFlags.PreFinally |
|
||||
FlowFlags.AfterFinally;
|
||||
|
||||
const hasNodeFlags =
|
||||
FlowFlags.Start |
|
||||
FlowFlags.Assignment |
|
||||
FlowFlags.Call |
|
||||
FlowFlags.Condition |
|
||||
FlowFlags.ArrayMutation;
|
||||
|
||||
const links: Record<number, FlowGraphNode> = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null
|
||||
const nodes: FlowGraphNode[] = [];
|
||||
const edges: FlowGraphEdge[] = [];
|
||||
const root = buildGraphNode(flowNode);
|
||||
for (const node of nodes) {
|
||||
computeLevel(node);
|
||||
}
|
||||
|
||||
const height = computeHeight(root);
|
||||
const columnWidths = computeColumnWidths(height);
|
||||
computeLanes(root, 0);
|
||||
return renderGraph();
|
||||
|
||||
function isFlowSwitchClause(f: FlowNode): f is FlowSwitchClause {
|
||||
return !!(f.flags & FlowFlags.SwitchClause);
|
||||
}
|
||||
|
||||
function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[] } {
|
||||
return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents;
|
||||
}
|
||||
|
||||
function hasAntecedent(f: FlowNode): f is Extract<FlowNode, { antecedent: FlowNode }> {
|
||||
return !!(f.flags & hasAntecedentFlags);
|
||||
}
|
||||
|
||||
function hasNode(f: FlowNode): f is Extract<FlowNode, { node?: Node }> {
|
||||
return !!(f.flags & hasNodeFlags);
|
||||
}
|
||||
|
||||
function getChildren(node: FlowGraphNode) {
|
||||
const children: FlowGraphNode[] = [];
|
||||
for (const edge of node.edges) {
|
||||
if (edge.source === node) {
|
||||
children.push(edge.target);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
function getParents(node: FlowGraphNode) {
|
||||
const parents: FlowGraphNode[] = [];
|
||||
for (const edge of node.edges) {
|
||||
if (edge.target === node) {
|
||||
parents.push(edge.source);
|
||||
}
|
||||
}
|
||||
return parents;
|
||||
}
|
||||
|
||||
function buildGraphNode(flowNode: FlowNode) {
|
||||
const id = getDebugFlowNodeId(flowNode);
|
||||
let graphNode = links[id];
|
||||
if (!graphNode) {
|
||||
links[id] = graphNode = { id, flowNode, edges: [], text: renderFlowNode(flowNode), lane: -1, endLane: -1, level: -1 };
|
||||
nodes.push(graphNode);
|
||||
if (!(flowNode.flags & FlowFlags.PreFinally)) {
|
||||
if (hasAntecedents(flowNode)) {
|
||||
|
||||
for (const antecedent of flowNode.antecedents) {
|
||||
buildGraphEdge(graphNode, antecedent);
|
||||
}
|
||||
}
|
||||
else if (hasAntecedent(flowNode)) {
|
||||
buildGraphEdge(graphNode, flowNode.antecedent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return graphNode;
|
||||
}
|
||||
|
||||
function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode) {
|
||||
const target = buildGraphNode(antecedent);
|
||||
const edge: FlowGraphEdge = { source, target };
|
||||
edges.push(edge);
|
||||
source.edges.push(edge);
|
||||
target.edges.push(edge);
|
||||
}
|
||||
|
||||
function computeLevel(node: FlowGraphNode): number {
|
||||
if (node.level !== -1) {
|
||||
return node.level;
|
||||
}
|
||||
let level = 0;
|
||||
for (const parent of getParents(node)) {
|
||||
level = Math.max(level, computeLevel(parent) + 1);
|
||||
}
|
||||
return node.level = level;
|
||||
}
|
||||
|
||||
function computeHeight(node: FlowGraphNode): number {
|
||||
let height = 0;
|
||||
for (const child of getChildren(node)) {
|
||||
height = Math.max(height, computeHeight(child));
|
||||
}
|
||||
return height + 1;
|
||||
}
|
||||
|
||||
function computeColumnWidths(height: number) {
|
||||
const columns: number[] = fill(Array(height), 0);
|
||||
for (const node of nodes) {
|
||||
columns[node.level] = Math.max(columns[node.level], node.text.length);
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
function computeLanes(node: FlowGraphNode, lane: number) {
|
||||
if (node.lane === -1) {
|
||||
node.lane = lane;
|
||||
node.endLane = lane;
|
||||
const children = getChildren(node);
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (i > 0) lane++;
|
||||
const child = children[i];
|
||||
computeLanes(child, lane);
|
||||
if (child.endLane > node.endLane) {
|
||||
lane = child.endLane;
|
||||
}
|
||||
}
|
||||
node.endLane = lane;
|
||||
}
|
||||
}
|
||||
|
||||
function getHeader(flags: FlowFlags) {
|
||||
if (flags & FlowFlags.Start) return "Start";
|
||||
if (flags & FlowFlags.BranchLabel) return "Branch";
|
||||
if (flags & FlowFlags.LoopLabel) return "Loop";
|
||||
if (flags & FlowFlags.Assignment) return "Assignment";
|
||||
if (flags & FlowFlags.TrueCondition) return "True";
|
||||
if (flags & FlowFlags.FalseCondition) return "False";
|
||||
if (flags & FlowFlags.SwitchClause) return "SwitchClause";
|
||||
if (flags & FlowFlags.ArrayMutation) return "ArrayMutation";
|
||||
if (flags & FlowFlags.Call) return "Call";
|
||||
if (flags & FlowFlags.PreFinally) return "PreFinally";
|
||||
if (flags & FlowFlags.AfterFinally) return "AfterFinally";
|
||||
if (flags & FlowFlags.Unreachable) return "Unreachable";
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
function getNodeText(node: Node) {
|
||||
const sourceFile = getSourceFileOfNode(node);
|
||||
return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false);
|
||||
}
|
||||
|
||||
function renderFlowNode(flowNode: FlowNode) {
|
||||
let text = getHeader(flowNode.flags);
|
||||
if (hasNode(flowNode)) {
|
||||
if (flowNode.node) {
|
||||
text += ` (${getNodeText(flowNode.node)})`;
|
||||
}
|
||||
}
|
||||
else if (isFlowSwitchClause(flowNode)) {
|
||||
const clauses: string[] = [];
|
||||
for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) {
|
||||
const clause = flowNode.switchStatement.caseBlock.clauses[i];
|
||||
if (isDefaultClause(clause)) {
|
||||
clauses.push("default");
|
||||
}
|
||||
else {
|
||||
clauses.push(getNodeText(clause.expression));
|
||||
}
|
||||
}
|
||||
text += ` (${clauses.join(", ")})`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function renderGraph() {
|
||||
const columnCount = columnWidths.length;
|
||||
const laneCount = nodes.reduce((x, n) => Math.max(x, n.lane), 0) + 1;
|
||||
const lanes: string[] = fill(Array(laneCount), "");
|
||||
const grid: (FlowGraphNode | undefined)[][] = columnWidths.map(() => Array(laneCount));
|
||||
const connectors: Connection[][] = columnWidths.map(() => fill(Array(laneCount), 0));
|
||||
|
||||
// build connectors
|
||||
for (const node of nodes) {
|
||||
grid[node.level][node.lane] = node;
|
||||
const children = getChildren(node);
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
let connector: Connection = Connection.Right;
|
||||
if (child.lane === node.lane) connector |= Connection.Left;
|
||||
if (i > 0) connector |= Connection.Up;
|
||||
if (i < children.length - 1) connector |= Connection.Down;
|
||||
connectors[node.level][child.lane] |= connector;
|
||||
}
|
||||
if (children.length === 0) {
|
||||
connectors[node.level][node.lane] |= Connection.NoChildren;
|
||||
}
|
||||
const parents = getParents(node);
|
||||
for (let i = 0; i < parents.length; i++) {
|
||||
const parent = parents[i];
|
||||
let connector: Connection = Connection.Left;
|
||||
if (i > 0) connector |= Connection.Up;
|
||||
if (i < parents.length - 1) connector |= Connection.Down;
|
||||
connectors[node.level - 1][parent.lane] |= connector;
|
||||
}
|
||||
}
|
||||
|
||||
// fill in missing connectors
|
||||
for (let column = 0; column < columnCount; column++) {
|
||||
for (let lane = 0; lane < laneCount; lane++) {
|
||||
const left = column > 0 ? connectors[column - 1][lane] : 0;
|
||||
const above = lane > 0 ? connectors[column][lane - 1] : 0;
|
||||
let connector = connectors[column][lane];
|
||||
if (!connector) {
|
||||
if (left & Connection.Right) connector |= Connection.LeftRight;
|
||||
if (above & Connection.Down) connector |= Connection.UpDown;
|
||||
connectors[column][lane] = connector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let column = 0; column < columnCount; column++) {
|
||||
for (let lane = 0; lane < lanes.length; lane++) {
|
||||
const connector = connectors[column][lane];
|
||||
const fill = connector & Connection.Left ? BoxCharacter.lr : " ";
|
||||
const node = grid[column][lane];
|
||||
if (!node) {
|
||||
if (column < columnCount - 1) {
|
||||
writeLane(lane, repeat(fill, columnWidths[column] + 1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
writeLane(lane, node.text);
|
||||
if (column < columnCount - 1) {
|
||||
writeLane(lane, " ");
|
||||
writeLane(lane, repeat(fill, columnWidths[column] - node.text.length));
|
||||
}
|
||||
}
|
||||
writeLane(lane, getBoxCharacter(connector));
|
||||
writeLane(lane, connector & Connection.Right && column < columnCount - 1 && !grid[column + 1][lane] ? BoxCharacter.lr : " ");
|
||||
}
|
||||
}
|
||||
|
||||
return `\n${lanes.join("\n")}\n`;
|
||||
|
||||
function writeLane(lane: number, text: string) {
|
||||
lanes[lane] += text;
|
||||
}
|
||||
}
|
||||
|
||||
function getBoxCharacter(connector: Connection) {
|
||||
switch (connector) {
|
||||
case Connection.UpDown: return BoxCharacter.ud;
|
||||
case Connection.LeftRight: return BoxCharacter.lr;
|
||||
case Connection.UpLeft: return BoxCharacter.ul;
|
||||
case Connection.UpRight: return BoxCharacter.ur;
|
||||
case Connection.DownLeft: return BoxCharacter.dl;
|
||||
case Connection.DownRight: return BoxCharacter.dr;
|
||||
case Connection.UpDownLeft: return BoxCharacter.udl;
|
||||
case Connection.UpDownRight: return BoxCharacter.udr;
|
||||
case Connection.UpLeftRight: return BoxCharacter.ulr;
|
||||
case Connection.DownLeftRight: return BoxCharacter.dlr;
|
||||
case Connection.UpDownLeftRight: return BoxCharacter.udlr;
|
||||
}
|
||||
return " ";
|
||||
}
|
||||
|
||||
function fill<T>(array: T[], value: T) {
|
||||
if (array.fill) {
|
||||
array.fill(value);
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
array[i] = value;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function repeat(ch: string, length: number) {
|
||||
if (ch.repeat) {
|
||||
return length > 0 ? ch.repeat(length) : "";
|
||||
}
|
||||
let s = "";
|
||||
while (s.length < length) {
|
||||
s += ch;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Export as a module. NOTE: Can't use module exports as this is built using --outFile
|
||||
declare const module: { exports: {} };
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = Debug;
|
||||
}
|
||||
}
|
13
src/debug/tsconfig.json
Normal file
13
src/debug/tsconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": "../tsconfig-library-base",
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"lib": ["es2019"],
|
||||
"outFile": "../../built/local/compiler-debug.js",
|
||||
"declaration": false,
|
||||
"sourceMap": true
|
||||
},
|
||||
"files": [
|
||||
"debug.ts"
|
||||
]
|
||||
}
|
|
@ -796,7 +796,7 @@ namespace Harness.LanguageService {
|
|||
return mockHash(s);
|
||||
}
|
||||
|
||||
require(_initialDir: string, _moduleName: string): ts.server.RequireResult {
|
||||
require(_initialDir: string, _moduleName: string): ts.RequireResult {
|
||||
switch (_moduleName) {
|
||||
// Adds to the Quick Info a fixed string and a string from the config file
|
||||
// and replaces the first display part
|
||||
|
|
|
@ -341,7 +341,7 @@ interface Array<T> { length: number; [n: number]: T; }`
|
|||
private readonly currentDirectory: string;
|
||||
private readonly customWatchFile: HostWatchFile | undefined;
|
||||
private readonly customRecursiveWatchDirectory: HostWatchDirectory | undefined;
|
||||
public require: ((initialPath: string, moduleName: string) => server.RequireResult) | undefined;
|
||||
public require: ((initialPath: string, moduleName: string) => RequireResult) | undefined;
|
||||
|
||||
constructor(
|
||||
public withSafeList: boolean,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"outFile": "../../built/local/services.js"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../shims" },
|
||||
{ "path": "../compiler" },
|
||||
{ "path": "../jsTyping" }
|
||||
],
|
||||
|
|
212
src/shims/mapShim.ts
Normal file
212
src/shims/mapShim.ts
Normal file
|
@ -0,0 +1,212 @@
|
|||
/* @internal */
|
||||
namespace ts {
|
||||
// NOTE: Due to how the project-reference merging ends up working, `T` isn't considered referenced until `Map` merges with the definition
|
||||
// in src/compiler/core.ts
|
||||
// @ts-ignore
|
||||
export interface Map<T> {
|
||||
// full type defined in ~/src/compiler/core.ts
|
||||
}
|
||||
|
||||
export function createMapShim(): new <T>() => Map<T> {
|
||||
/** Create a MapLike with good performance. */
|
||||
function createDictionaryObject<T>(): Record<string, T> {
|
||||
const map = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null
|
||||
|
||||
// Using 'delete' on an object causes V8 to put the object in dictionary mode.
|
||||
// This disables creation of hidden classes, which are expensive when an object is
|
||||
// constantly changing shape.
|
||||
map.__ = undefined;
|
||||
delete map.__;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
interface MapEntry<T> {
|
||||
readonly key?: string;
|
||||
value?: T;
|
||||
|
||||
// Linked list references for iterators.
|
||||
nextEntry?: MapEntry<T>;
|
||||
previousEntry?: MapEntry<T>;
|
||||
|
||||
/**
|
||||
* Specifies if iterators should skip the next entry.
|
||||
* This will be set when an entry is deleted.
|
||||
* See https://github.com/Microsoft/TypeScript/pull/27292 for more information.
|
||||
*/
|
||||
skipNext?: boolean;
|
||||
}
|
||||
|
||||
class MapIterator<T, U extends (string | T | [string, T])> {
|
||||
private currentEntry?: MapEntry<T>;
|
||||
private selector: (key: string, value: T) => U;
|
||||
|
||||
constructor(currentEntry: MapEntry<T>, selector: (key: string, value: T) => U) {
|
||||
this.currentEntry = currentEntry;
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
public next(): { value: U, done: false } | { value: never, done: true } {
|
||||
// Navigate to the next entry.
|
||||
while (this.currentEntry) {
|
||||
const skipNext = !!this.currentEntry.skipNext;
|
||||
this.currentEntry = this.currentEntry.nextEntry;
|
||||
|
||||
if (!skipNext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentEntry) {
|
||||
return { value: this.selector(this.currentEntry.key!, this.currentEntry.value!), done: false };
|
||||
}
|
||||
else {
|
||||
return { value: undefined as never, done: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return class <T> implements Map<T> {
|
||||
private data = createDictionaryObject<MapEntry<T>>();
|
||||
public size = 0;
|
||||
|
||||
// Linked list references for iterators.
|
||||
// See https://github.com/Microsoft/TypeScript/pull/27292
|
||||
// for more information.
|
||||
|
||||
/**
|
||||
* The first entry in the linked list.
|
||||
* Note that this is only a stub that serves as starting point
|
||||
* for iterators and doesn't contain a key and a value.
|
||||
*/
|
||||
private readonly firstEntry: MapEntry<T>;
|
||||
private lastEntry: MapEntry<T>;
|
||||
|
||||
constructor() {
|
||||
// Create a first (stub) map entry that will not contain a key
|
||||
// and value but serves as starting point for iterators.
|
||||
this.firstEntry = {};
|
||||
// When the map is empty, the last entry is the same as the
|
||||
// first one.
|
||||
this.lastEntry = this.firstEntry;
|
||||
}
|
||||
|
||||
get(key: string): T | undefined {
|
||||
const entry = this.data[key] as MapEntry<T> | undefined;
|
||||
return entry && entry.value!;
|
||||
}
|
||||
|
||||
set(key: string, value: T): this {
|
||||
if (!this.has(key)) {
|
||||
this.size++;
|
||||
|
||||
// Create a new entry that will be appended at the
|
||||
// end of the linked list.
|
||||
const newEntry: MapEntry<T> = {
|
||||
key,
|
||||
value
|
||||
};
|
||||
this.data[key] = newEntry;
|
||||
|
||||
// Adjust the references.
|
||||
const previousLastEntry = this.lastEntry;
|
||||
previousLastEntry.nextEntry = newEntry;
|
||||
newEntry.previousEntry = previousLastEntry;
|
||||
this.lastEntry = newEntry;
|
||||
}
|
||||
else {
|
||||
this.data[key].value = value;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
has(key: string): boolean {
|
||||
// eslint-disable-next-line no-in-operator
|
||||
return key in this.data;
|
||||
}
|
||||
|
||||
delete(key: string): boolean {
|
||||
if (this.has(key)) {
|
||||
this.size--;
|
||||
const entry = this.data[key];
|
||||
delete this.data[key];
|
||||
|
||||
// Adjust the linked list references of the neighbor entries.
|
||||
const previousEntry = entry.previousEntry!;
|
||||
previousEntry.nextEntry = entry.nextEntry;
|
||||
if (entry.nextEntry) {
|
||||
entry.nextEntry.previousEntry = previousEntry;
|
||||
}
|
||||
|
||||
// When the deleted entry was the last one, we need to
|
||||
// adjust the lastEntry reference.
|
||||
if (this.lastEntry === entry) {
|
||||
this.lastEntry = previousEntry;
|
||||
}
|
||||
|
||||
// Adjust the forward reference of the deleted entry
|
||||
// in case an iterator still references it. This allows us
|
||||
// to throw away the entry, but when an active iterator
|
||||
// (which points to the current entry) continues, it will
|
||||
// navigate to the entry that originally came before the
|
||||
// current one and skip it.
|
||||
entry.previousEntry = undefined;
|
||||
entry.nextEntry = previousEntry;
|
||||
entry.skipNext = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.data = createDictionaryObject<MapEntry<T>>();
|
||||
this.size = 0;
|
||||
|
||||
// Reset the linked list. Note that we must adjust the forward
|
||||
// references of the deleted entries to ensure iterators stuck
|
||||
// in the middle of the list don't continue with deleted entries,
|
||||
// but can continue with new entries added after the clear()
|
||||
// operation.
|
||||
const firstEntry = this.firstEntry;
|
||||
let currentEntry = firstEntry.nextEntry;
|
||||
while (currentEntry) {
|
||||
const nextEntry = currentEntry.nextEntry;
|
||||
currentEntry.previousEntry = undefined;
|
||||
currentEntry.nextEntry = firstEntry;
|
||||
currentEntry.skipNext = true;
|
||||
|
||||
currentEntry = nextEntry;
|
||||
}
|
||||
firstEntry.nextEntry = undefined;
|
||||
this.lastEntry = firstEntry;
|
||||
}
|
||||
|
||||
keys(): Iterator<string> {
|
||||
return new MapIterator(this.firstEntry, key => key);
|
||||
}
|
||||
|
||||
values(): Iterator<T> {
|
||||
return new MapIterator(this.firstEntry, (_key, value) => value);
|
||||
}
|
||||
|
||||
entries(): Iterator<[string, T]> {
|
||||
return new MapIterator(this.firstEntry, (key, value) => [key, value] as [string, T]);
|
||||
}
|
||||
|
||||
forEach(action: (value: T, key: string) => void): void {
|
||||
const iterator = this.entries();
|
||||
while (true) {
|
||||
const iterResult = iterator.next();
|
||||
if (iterResult.done) {
|
||||
break;
|
||||
}
|
||||
|
||||
const [key, value] = iterResult.value;
|
||||
action(value, key);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
9
src/shims/tsconfig.json
Normal file
9
src/shims/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"outFile": "../../built/local/shims.js"
|
||||
},
|
||||
"files": [
|
||||
"mapShim.ts"
|
||||
]
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
]
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../shims", "prepend": true },
|
||||
{ "path": "../compiler", "prepend": true },
|
||||
{ "path": "../services", "prepend": true },
|
||||
{ "path": "../jsTyping", "prepend": true },
|
||||
|
@ -60,7 +61,7 @@
|
|||
"unittests/publicApi.ts",
|
||||
"unittests/reuseProgramStructure.ts",
|
||||
"unittests/semver.ts",
|
||||
"unittests/shimMap.ts",
|
||||
"unittests/createMapShim.ts",
|
||||
"unittests/transform.ts",
|
||||
"unittests/config/commandLineParsing.ts",
|
||||
"unittests/config/configurationExtension.ts",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
namespace ts {
|
||||
describe("unittests:: shimMap", () => {
|
||||
describe("unittests:: createMapShim", () => {
|
||||
|
||||
function testMapIterationAddedValues(map: Map<string>, useForEach: boolean): string {
|
||||
let resultString = "";
|
||||
|
@ -93,15 +93,15 @@ namespace ts {
|
|||
const nativeMapIteratorResult = testMapIterationAddedValues(nativeMap, /* useForEach */ false);
|
||||
assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator");
|
||||
|
||||
// Then, test the shimMap.
|
||||
let localShimMap = new (shimMap())<string>();
|
||||
// Then, test the map shim.
|
||||
const MapShim = createMapShim(); // tslint:disable-line variable-name
|
||||
let localShimMap = new MapShim<string>();
|
||||
const shimMapForEachResult = testMapIterationAddedValues(localShimMap, /* useForEach */ true);
|
||||
assert.equal(shimMapForEachResult, expectedResult, "shimMap-forEach");
|
||||
|
||||
localShimMap = new (shimMap())<string>();
|
||||
localShimMap = new MapShim<string>();
|
||||
const shimMapIteratorResult = testMapIterationAddedValues(localShimMap, /* useForEach */ false);
|
||||
assert.equal(shimMapIteratorResult, expectedResult, "shimMap-iterator");
|
||||
|
||||
});
|
||||
});
|
||||
}
|
|
@ -2,10 +2,12 @@
|
|||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./shims" },
|
||||
{ "path": "./tsc" },
|
||||
{ "path": "./tsserver" },
|
||||
{ "path": "./typingsInstaller" },
|
||||
{ "path": "./watchGuard" },
|
||||
{ "path": "./debug" },
|
||||
{ "path": "./cancellationToken" },
|
||||
{ "path": "./testRunner" }
|
||||
]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"tsserverlibrary.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../shims", "prepend": true },
|
||||
{ "path": "../compiler", "prepend": true },
|
||||
{ "path": "../jsTyping", "prepend": true },
|
||||
{ "path": "../services", "prepend": true },
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"typescriptServices.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../shims", "prepend": true },
|
||||
{ "path": "../compiler", "prepend": true },
|
||||
{ "path": "../jsTyping", "prepend": true },
|
||||
{ "path": "../services", "prepend": true }
|
||||
|
|
Loading…
Reference in a new issue