Skip to content

Commit

Permalink
Minor cleanups in builder (#17208)
Browse files Browse the repository at this point in the history
* Minor cleanups in builder

* Use enumerateInsertsAndDeletes
  • Loading branch information
Andy authored Jul 17, 2017
1 parent 240f1f1 commit 555776e
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 102 deletions.
6 changes: 4 additions & 2 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(array: ReadonlyArray<T>, comparer: (x: T, y: T) => Comparison = compareValues) {
export function stableSort<T>(array: ReadonlyArray<T>, comparer: Comparer<T> = 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
Expand Down Expand Up @@ -852,14 +852,16 @@ namespace ts {
return result;
}

export type Comparer<T> = (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
* number[index] exceeds number.
* @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<T>(array: ReadonlyArray<T>, value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number {
export function binarySearch<T>(array: ReadonlyArray<T>, value: T, comparer?: Comparer<T>, offset?: number): number {
if (!array || array.length === 0) {
return -1;
}
Expand Down
87 changes: 15 additions & 72 deletions src/server/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,57 +203,27 @@ namespace ts.server {
}

class ModuleBuilderFileInfo extends BuilderFileInfo {
references: ModuleBuilderFileInfo[] = [];
referencedBy: ModuleBuilderFileInfo[] = [];
references = createSortedArray<ModuleBuilderFileInfo>();
readonly referencedBy = createSortedArray<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() {
for (const reference of this.references) {
reference.removeReferencedBy(this);
}
this.references = [];
this.references = createSortedArray<ModuleBuilderFileInfo>();
}
}

Expand All @@ -270,16 +240,13 @@ namespace ts.server {
super.clear();
}

private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] {
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray<ModuleBuilderFileInfo> {
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
return [];
return createSortedArray();
}

const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path);
if (referencedFilePaths.length > 0) {
return map<Path, ModuleBuilderFileInfo>(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos);
}
return [];
return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos);
}

protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) {
Expand Down Expand Up @@ -319,39 +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);
if (compare < 0) {
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);
oldIndex++;
}
else if (compare > 0) {
// A new reference info. Add it.
newReference.addReferencedBy(fileInfo);
newIndex++;
}
else {
// Equal. Go to next
oldIndex++;
newIndex++;
}
}
// 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);
}
},
/*compare*/ ModuleBuilderFileInfo.compareFileInfos);

fileInfo.references = newReferences;
fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion();
Expand Down
6 changes: 3 additions & 3 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<boolean>();
const referencedFiles = createMap<true>();
if (sourceFile.imports && sourceFile.imports.length > 0) {
const checker: TypeChecker = this.program.getTypeChecker();
for (const importName of sourceFile.imports) {
Expand Down Expand Up @@ -1057,7 +1057,7 @@ namespace ts.server {
}

getExternalFiles(): SortedReadonlyArray<string> {
return toSortedReadonlyArray(flatMap(this.plugins, plugin => {
return toSortedArray(flatMap(this.plugins, plugin => {
if (typeof plugin.getExternalFiles !== "function") return;
try {
return plugin.getExternalFiles(this);
Expand Down
6 changes: 5 additions & 1 deletion src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ declare namespace ts.server {
require?(initialPath: string, moduleName: string): RequireResult;
}

export interface SortedArray<T> extends Array<T> {
" __sortedArrayBrand": any;
}

export interface SortedReadonlyArray<T> extends ReadonlyArray<T> {
" __sortedReadonlyArrayBrand": any;
" __sortedArrayBrand": any;
}

export interface TypingInstallerRequest {
Expand Down
2 changes: 1 addition & 1 deletion src/server/typingsCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ namespace ts.server {
this.perProjectCache.set(projectName, {
compilerOptions,
typeAcquisition,
typings: toSortedReadonlyArray(newTypings),
typings: toSortedArray(newTypings),
unresolvedImports,
poisoned: false
});
Expand Down
80 changes: 57 additions & 23 deletions src/server/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace ts.server {
verbose
}

export const emptyArray: ReadonlyArray<any> = [];
export const emptyArray: SortedReadonlyArray<never> = createSortedArray<never>();

export interface Logger {
close(): void;
Expand Down Expand Up @@ -190,39 +190,45 @@ namespace ts.server {
return `/dev/null/inferredProject${counter}*`;
}

export function toSortedReadonlyArray(arr: string[]): SortedReadonlyArray<string> {
arr.sort();
return <any>arr;
export function createSortedArray<T>(): SortedArray<T> {
return [] as SortedArray<T>;
}

export function enumerateInsertsAndDeletes<T>(a: SortedReadonlyArray<T>, b: SortedReadonlyArray<T>, inserted: (item: T) => void, deleted: (item: T) => void, compare?: (a: T, b: T) => Comparison) {
export function toSortedArray(arr: string[]): SortedArray<string>;
export function toSortedArray<T>(arr: T[], comparer: Comparer<T>): SortedArray<T>;
export function toSortedArray<T>(arr: T[], comparer?: Comparer<T>): SortedArray<T> {
arr.sort(comparer);
return arr as SortedArray<T>;
}

export function enumerateInsertsAndDeletes<T>(newItems: SortedReadonlyArray<T>, oldItems: SortedReadonlyArray<T>, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, compare?: Comparer<T>) {
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++]);
}
}

Expand Down Expand Up @@ -273,4 +279,32 @@ namespace ts.server {
}
}
}

export function insertSorted<T>(array: SortedArray<T>, insert: T, compare: Comparer<T>): 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<T>(array: SortedArray<T>, remove: T, compare: Comparer<T>): 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);
}
}
}

0 comments on commit 555776e

Please sign in to comment.