From 3dc53fcbc757e44624a4d37444964ede824d412a Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 14 Jul 2017 13:56:41 -0700 Subject: [PATCH 1/2] Minor cleanups in builder --- src/compiler/core.ts | 6 ++-- src/server/builder.ts | 77 ++++++++++++----------------------------- src/server/project.ts | 2 +- src/server/utilities.ts | 28 +++++++++++++++ 4 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9f5872540f44b..6da3a5b77aa68 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -780,7 +780,7 @@ namespace ts { /** * Stable sort of an array. Elements equal to each other maintain their relative position in the array. */ - export function stableSort(array: ReadonlyArray, comparer: (x: T, y: T) => Comparison = compareValues) { + export function stableSort(array: ReadonlyArray, comparer: Comparer = compareValues) { return array .map((_, i) => i) // create array of indices .sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)) // sort indices by value then position @@ -852,6 +852,8 @@ namespace ts { return result; } + export type Comparer = (a: T, b: T) => Comparison; + /** * Performs a binary search, finding the index at which 'value' occurs in 'array'. * If no such index is found, returns the 2's-complement of first index at which @@ -859,7 +861,7 @@ namespace ts { * @param array A sorted array whose first element must be no larger than number * @param number The value to be searched for in the array. */ - export function binarySearch(array: ReadonlyArray, value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number { + export function binarySearch(array: ReadonlyArray, value: T, comparer?: Comparer, offset?: number): number { if (!array || array.length === 0) { return -1; } diff --git a/src/server/builder.ts b/src/server/builder.ts index 8fd8fbf1e6543..7b8cb8121be4f 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -207,46 +207,16 @@ namespace ts.server { referencedBy: ModuleBuilderFileInfo[] = []; scriptVersionForReferences: string; - static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): number { - const l = lf.scriptInfo.fileName; - const r = rf.scriptInfo.fileName; - return (l < r ? -1 : (l > r ? 1 : 0)); - } - - static addToReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) { - if (array.length === 0) { - array.push(fileInfo); - return; - } - - const insertIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - if (insertIndex < 0) { - array.splice(~insertIndex, 0, fileInfo); - } - } - - static removeFromReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) { - if (!array || array.length === 0) { - return; - } - - if (array[0] === fileInfo) { - array.splice(0, 1); - return; - } - - const removeIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - if (removeIndex >= 0) { - array.splice(removeIndex, 1); - } + static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison { + return compareStrings(lf.scriptInfo.fileName, rf.scriptInfo.fileName); } addReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - ModuleBuilderFileInfo.addToReferenceList(this.referencedBy, fileInfo); + insertSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); } removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - ModuleBuilderFileInfo.removeFromReferenceList(this.referencedBy, fileInfo); + removeSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); } removeFileReferences() { @@ -276,10 +246,7 @@ namespace ts.server { } const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path); - if (referencedFilePaths.length > 0) { - return map(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos); - } - return []; + return referencedFilePaths.map(f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos); } protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) { @@ -326,22 +293,24 @@ namespace ts.server { const oldReference = oldReferences[oldIndex]; const newReference = newReferences[newIndex]; const compare = ModuleBuilderFileInfo.compareFileInfos(oldReference, newReference); - if (compare < 0) { - // New reference is greater then current reference. That means - // the current reference doesn't exist anymore after parsing. So delete - // references. - oldReference.removeReferencedBy(fileInfo); - oldIndex++; - } - else if (compare > 0) { - // A new reference info. Add it. - newReference.addReferencedBy(fileInfo); - newIndex++; - } - else { - // Equal. Go to next - oldIndex++; - newIndex++; + switch (compare) { + case Comparison.LessThan: + // New reference is greater then current reference. That means + // the current reference doesn't exist anymore after parsing. So delete + // references. + oldReference.removeReferencedBy(fileInfo); + oldIndex++; + break; + case Comparison.GreaterThan: + // A new reference info. Add it. + newReference.addReferencedBy(fileInfo); + newIndex++; + break; + case Comparison.EqualTo: + // Equal. Go to next + oldIndex++; + newIndex++; + break; } } // Clean old references diff --git a/src/server/project.ts b/src/server/project.ts index fab71474851a1..1bb0d5ad73137 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -783,7 +783,7 @@ namespace ts.server { // We need to use a set here since the code can contain the same import twice, // but that will only be one dependency. // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - const referencedFiles = createMap(); + const referencedFiles = createMap(); if (sourceFile.imports && sourceFile.imports.length > 0) { const checker: TypeChecker = this.program.getTypeChecker(); for (const importName of sourceFile.imports) { diff --git a/src/server/utilities.ts b/src/server/utilities.ts index a3dcd9161722e..3631e9fc06229 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -273,4 +273,32 @@ namespace ts.server { } } } + + export function insertSorted(array: T[], insert: T, compare: Comparer): void { + if (array.length === 0) { + array.push(insert); + return; + } + + const insertIndex = binarySearch(array, insert, compare); + if (insertIndex < 0) { + array.splice(~insertIndex, 0, insert); + } + } + + export function removeSorted(array: T[], remove: T, compare: Comparer): void { + if (!array || array.length === 0) { + return; + } + + if (array[0] === remove) { + array.splice(0, 1); + return; + } + + const removeIndex = binarySearch(array, remove, compare); + if (removeIndex >= 0) { + array.splice(removeIndex, 1); + } + } } \ No newline at end of file From 6e61c517c2250ff20430d340c156a590d4ff7c38 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 17 Jul 2017 07:43:28 -0700 Subject: [PATCH 2/2] Use enumerateInsertsAndDeletes --- src/server/builder.ts | 56 ++++++++++---------------------------- src/server/project.ts | 4 +-- src/server/types.ts | 6 +++- src/server/typingsCache.ts | 2 +- src/server/utilities.ts | 56 +++++++++++++++++++++----------------- 5 files changed, 54 insertions(+), 70 deletions(-) diff --git a/src/server/builder.ts b/src/server/builder.ts index 7b8cb8121be4f..bc86780fbbe23 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -203,8 +203,8 @@ namespace ts.server { } class ModuleBuilderFileInfo extends BuilderFileInfo { - references: ModuleBuilderFileInfo[] = []; - referencedBy: ModuleBuilderFileInfo[] = []; + references = createSortedArray(); + readonly referencedBy = createSortedArray(); scriptVersionForReferences: string; static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison { @@ -223,7 +223,7 @@ namespace ts.server { for (const reference of this.references) { reference.removeReferencedBy(this); } - this.references = []; + this.references = createSortedArray(); } } @@ -240,13 +240,13 @@ namespace ts.server { super.clear(); } - private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] { + private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray { if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) { - return []; + return createSortedArray(); } const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path); - return referencedFilePaths.map(f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos); + return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos); } protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) { @@ -286,41 +286,15 @@ namespace ts.server { const newReferences = this.getReferencedFileInfos(fileInfo); const oldReferences = fileInfo.references; - - let oldIndex = 0; - let newIndex = 0; - while (oldIndex < oldReferences.length && newIndex < newReferences.length) { - const oldReference = oldReferences[oldIndex]; - const newReference = newReferences[newIndex]; - const compare = ModuleBuilderFileInfo.compareFileInfos(oldReference, newReference); - switch (compare) { - case Comparison.LessThan: - // New reference is greater then current reference. That means - // the current reference doesn't exist anymore after parsing. So delete - // references. - oldReference.removeReferencedBy(fileInfo); - oldIndex++; - break; - case Comparison.GreaterThan: - // A new reference info. Add it. - newReference.addReferencedBy(fileInfo); - newIndex++; - break; - case Comparison.EqualTo: - // Equal. Go to next - oldIndex++; - newIndex++; - break; - } - } - // Clean old references - for (let i = oldIndex; i < oldReferences.length; i++) { - oldReferences[i].removeReferencedBy(fileInfo); - } - // Update new references - for (let i = newIndex; i < newReferences.length; i++) { - newReferences[i].addReferencedBy(fileInfo); - } + enumerateInsertsAndDeletes(newReferences, oldReferences, + /*inserted*/ newReference => newReference.addReferencedBy(fileInfo), + /*deleted*/ oldReference => { + // New reference is greater then current reference. That means + // the current reference doesn't exist anymore after parsing. So delete + // references. + oldReference.removeReferencedBy(fileInfo); + }, + /*compare*/ ModuleBuilderFileInfo.compareFileInfos); fileInfo.references = newReferences; fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion(); diff --git a/src/server/project.ts b/src/server/project.ts index 1bb0d5ad73137..3b09fc48e8519 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -554,7 +554,7 @@ namespace ts.server { for (const sourceFile of this.program.getSourceFiles()) { this.extractUnresolvedImportsFromSourceFile(sourceFile, result); } - this.lastCachedUnresolvedImportsList = toSortedReadonlyArray(result); + this.lastCachedUnresolvedImportsList = toSortedArray(result); } unresolvedImports = this.lastCachedUnresolvedImportsList; @@ -1057,7 +1057,7 @@ namespace ts.server { } getExternalFiles(): SortedReadonlyArray { - return toSortedReadonlyArray(flatMap(this.plugins, plugin => { + return toSortedArray(flatMap(this.plugins, plugin => { if (typeof plugin.getExternalFiles !== "function") return; try { return plugin.getExternalFiles(this); diff --git a/src/server/types.ts b/src/server/types.ts index 72d6a7c6cfc3f..07b94fe827e19 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -20,8 +20,12 @@ declare namespace ts.server { require?(initialPath: string, moduleName: string): RequireResult; } + export interface SortedArray extends Array { + " __sortedArrayBrand": any; + } + export interface SortedReadonlyArray extends ReadonlyArray { - " __sortedReadonlyArrayBrand": any; + " __sortedArrayBrand": any; } export interface TypingInstallerRequest { diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index a97b12ff7a879..3ef37685da2c9 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -110,7 +110,7 @@ namespace ts.server { this.perProjectCache.set(projectName, { compilerOptions, typeAcquisition, - typings: toSortedReadonlyArray(newTypings), + typings: toSortedArray(newTypings), unresolvedImports, poisoned: false }); diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 3631e9fc06229..30146c0a92809 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -9,7 +9,7 @@ namespace ts.server { verbose } - export const emptyArray: ReadonlyArray = []; + export const emptyArray: SortedReadonlyArray = createSortedArray(); export interface Logger { close(): void; @@ -190,39 +190,45 @@ namespace ts.server { return `/dev/null/inferredProject${counter}*`; } - export function toSortedReadonlyArray(arr: string[]): SortedReadonlyArray { - arr.sort(); - return arr; + export function createSortedArray(): SortedArray { + return [] as SortedArray; } - export function enumerateInsertsAndDeletes(a: SortedReadonlyArray, b: SortedReadonlyArray, inserted: (item: T) => void, deleted: (item: T) => void, compare?: (a: T, b: T) => Comparison) { + export function toSortedArray(arr: string[]): SortedArray; + export function toSortedArray(arr: T[], comparer: Comparer): SortedArray; + export function toSortedArray(arr: T[], comparer?: Comparer): SortedArray { + arr.sort(comparer); + return arr as SortedArray; + } + + export function enumerateInsertsAndDeletes(newItems: SortedReadonlyArray, oldItems: SortedReadonlyArray, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, compare?: Comparer) { compare = compare || compareValues; - let aIndex = 0; - let bIndex = 0; - const aLen = a.length; - const bLen = b.length; - while (aIndex < aLen && bIndex < bLen) { - const aItem = a[aIndex]; - const bItem = b[bIndex]; - const compareResult = compare(aItem, bItem); + let newIndex = 0; + let oldIndex = 0; + const newLen = newItems.length; + const oldLen = oldItems.length; + while (newIndex < newLen && oldIndex < oldLen) { + const newItem = newItems[newIndex]; + const oldItem = oldItems[oldIndex]; + const compareResult = compare(newItem, oldItem); if (compareResult === Comparison.LessThan) { - inserted(aItem); - aIndex++; + inserted(newItem); + newIndex++; } else if (compareResult === Comparison.GreaterThan) { - deleted(bItem); - bIndex++; + deleted(oldItem); + oldIndex++; } else { - aIndex++; - bIndex++; + newIndex++; + oldIndex++; } } - while (aIndex < aLen) { - inserted(a[aIndex++]); + while (newIndex < newLen) { + inserted(newItems[newIndex++]); } - while (bIndex < bLen) { - deleted(b[bIndex++]); + while (oldIndex < oldLen) { + deleted(oldItems[oldIndex++]); } } @@ -274,7 +280,7 @@ namespace ts.server { } } - export function insertSorted(array: T[], insert: T, compare: Comparer): void { + export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { if (array.length === 0) { array.push(insert); return; @@ -286,7 +292,7 @@ namespace ts.server { } } - export function removeSorted(array: T[], remove: T, compare: Comparer): void { + export function removeSorted(array: SortedArray, remove: T, compare: Comparer): void { if (!array || array.length === 0) { return; }