Cleanup, merge #19475

This commit is contained in:
Ron Buckton 2017-10-26 13:49:32 -07:00
parent 24437774a8
commit bfba32b71d
8 changed files with 132 additions and 55 deletions

View File

@ -159,12 +159,6 @@ namespace ts {
return <Path>getCanonicalFileName(nonCanonicalizedPath);
}
export const enum Comparison {
LessThan = -1,
EqualTo = 0,
GreaterThan = 1
}
export function length(array: ReadonlyArray<any>) {
return array ? array.length : 0;
}
@ -301,17 +295,33 @@ namespace ts {
Debug.fail();
}
export function contains<T>(array: ReadonlyArray<T>, value: T): boolean {
if (array) {
for (const v of array) {
if (v === value) {
return true;
}
function containsWithoutEqualityComparer<T>(array: ReadonlyArray<T>, value: T) {
for (const v of array) {
if (v === value) {
return true;
}
}
return false;
}
function containsWithEqualityComparer<T>(array: ReadonlyArray<T>, value: T, equalityComparer: EqualityComparer<T>) {
for (const v of array) {
if (equalityComparer(v, value)) {
return true;
}
}
return false;
}
export function contains<T>(array: ReadonlyArray<T>, value: T, equalityComparer?: EqualityComparer<T>): boolean {
if (array) {
return equalityComparer
? containsWithEqualityComparer(array, value, equalityComparer)
: containsWithoutEqualityComparer(array, value);
}
return false;
}
export function indexOf<T>(array: ReadonlyArray<T>, value: T): number {
if (array) {
for (let i = 0; i < array.length; i++) {
@ -649,21 +659,36 @@ namespace ts {
return [...array1, ...array2];
}
// TODO: fixme (N^2) - add optional comparer so collection can be sorted before deduplication.
export function deduplicate<T>(array: ReadonlyArray<T>, equalityComparer: (a: T, b: T) => boolean = equateValues): T[] {
let result: T[];
if (array) {
result = [];
loop: for (const item of array) {
for (const res of result) {
if (equalityComparer(res, item)) {
continue loop;
}
/**
* Creates a new array with duplicate entries removed.
* @param equalityComparer An optional `EqualityComparer` used to determine if two values are duplicates.
* @param comparer An optional `Comparer` used to sort entries before comparison. If supplied,
* results are returned in the original order found in `array`.
*/
export function deduplicate<T>(array: ReadonlyArray<T>, equalityComparer?: EqualityComparer<T>, comparer?: Comparer<T>): T[] {
if (!array) return undefined;
if (!comparer) return addRangeIfUnique([], array, equalityComparer);
return deduplicateWorker(array, equalityComparer, comparer);
}
function deduplicateWorker<T>(array: ReadonlyArray<T>, equalityComparer: EqualityComparer<T> = equateValues, comparer: Comparer<T>) {
// Perform a stable sort of the array. This ensures the first entry in a list of
// duplicates remains the first entry in the result.
const indices = sequence(0, array.length);
stableSortIndices(array, indices, comparer);
const deduplicated: number[] = [];
loop: for (const sourceIndex of indices) {
for (const targetIndex of deduplicated) {
if (equalityComparer(array[sourceIndex], array[targetIndex])) {
continue loop;
}
result.push(item);
}
deduplicated.push(sourceIndex);
}
return result;
// return deduplicated items in original order
return deduplicated.sort().map(i => array[i]);
}
export function arrayIsEqualTo<T>(array1: ReadonlyArray<T>, array2: ReadonlyArray<T>, equalityComparer: (a: T, b: T) => boolean = equateValues): boolean {
@ -731,7 +756,7 @@ namespace ts {
* are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted
* based on the provided comparer.
*/
export function relativeComplement<T>(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer<T> = compareValues, offsetA = 0, offsetB = 0): T[] | undefined {
export function relativeComplement<T>(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer<T>, offsetA = 0, offsetB = 0): T[] | undefined {
if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB;
const result: T[] = [];
outer: for (; offsetB < arrayB.length; offsetB++) {
@ -795,19 +820,27 @@ namespace ts {
start = start === undefined ? 0 : toOffset(from, start);
end = end === undefined ? from.length : toOffset(from, end);
for (let i = start; i < end && i < from.length; i++) {
const v = from[i];
if (v !== undefined) {
if (from[i] !== undefined) {
to.push(from[i]);
}
}
return to;
}
function addRangeIfUnique<T>(to: T[], from: ReadonlyArray<T>, equalityComparer?: EqualityComparer<T>): T[] | undefined {
for (let i = 0; i < from.length; i++) {
if (from[i] !== undefined) {
pushIfUnique(to, from[i], equalityComparer);
}
}
return to;
}
/**
* @return Whether the value was added.
*/
export function pushIfUnique<T>(array: T[], toAdd: T): boolean {
if (contains(array, toAdd)) {
export function pushIfUnique<T>(array: T[], toAdd: T, equalityComparer?: EqualityComparer<T>): boolean {
if (contains(array, toAdd, equalityComparer)) {
return false;
}
else {
@ -819,9 +852,9 @@ namespace ts {
/**
* Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array.
*/
export function appendIfUnique<T>(array: T[] | undefined, toAdd: T): T[] {
export function appendIfUnique<T>(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer<T>): T[] {
if (array) {
pushIfUnique(array, toAdd);
pushIfUnique(array, toAdd, equalityComparer);
return array;
}
else {
@ -829,14 +862,29 @@ namespace ts {
}
}
/**
* Creates an array of integers starting at `from` (inclusive) and ending at `to` (exclusive).
*/
function sequence(from: number, to: number) {
const numbers: number[] = [];
for (let i = from; i < to; i++) {
numbers.push(i);
}
return numbers;
}
function stableSortIndices<T>(array: ReadonlyArray<T>, indices: number[], comparer: Comparer<T>) {
// sort indices by value then position
indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y));
}
/**
* 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: 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
.map(i => array[i]); // get sorted array
export function stableSort<T>(array: ReadonlyArray<T>, comparer: Comparer<T>) {
const indices = sequence(0, array.length);
stableSortIndices(array, indices, comparer);
return indices.map(i => array[i]);
}
export function rangeEquals<T>(array1: ReadonlyArray<T>, array2: ReadonlyArray<T>, pos: number, end: number) {
@ -914,9 +962,6 @@ namespace ts {
return result;
}
export type Comparer<T> = (a: T, b: T) => Comparison;
export type EqualityComparer<T> = (a: T, b: T) => boolean;
/**
* 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
@ -1483,7 +1528,7 @@ namespace ts {
return headChain;
}
function equateValues<T>(a: T, b: T) {
export function equateValues<T>(a: T, b: T) {
return a === b;
}
@ -1512,10 +1557,9 @@ namespace ts {
return equateValues(a, b);
}
/**
* Compare two values for their order relative to each other.
*/
export function compareValues<T>(a: T, b: T) {
function compareComparableValues(a: string, b: string): Comparison;
function compareComparableValues(a: number, b: number): Comparison;
function compareComparableValues(a: string | number, b: string | number) {
return a === b ? Comparison.EqualTo :
a === undefined ? Comparison.LessThan :
b === undefined ? Comparison.GreaterThan :
@ -1523,6 +1567,13 @@ namespace ts {
Comparison.GreaterThan;
}
/**
* Compare two values for their order relative to each other.
*/
export function compareValues(a: number, b: number) {
return compareComparableValues(a, b);
}
/**
* Compare two strings using a case-insensitive ordinal comparison.
*
@ -1555,7 +1606,7 @@ namespace ts {
* value of each code-point.
*/
export function compareStringsCaseSensitive(a: string, b: string) {
return compareValues(a, b);
return compareComparableValues(a, b);
}
/**
@ -2488,7 +2539,11 @@ namespace ts {
if (!extraFileExtensions || extraFileExtensions.length === 0 || !needAllExtensions) {
return needAllExtensions ? allSupportedExtensions : supportedTypeScriptExtensions;
}
return deduplicate([...allSupportedExtensions, ...extraFileExtensions.map(e => e.extension)]);
return deduplicate(
[...allSupportedExtensions, ...extraFileExtensions.map(e => e.extension)],
equateStringsCaseSensitive,
compareStringsCaseSensitive
);
}
export function hasJavaScriptFileExtension(fileName: string) {

View File

@ -36,6 +36,22 @@ namespace ts {
push(...values: T[]): void;
}
/* @internal */
export type EqualityComparer<T> = (a: T, b: T) => boolean;
/* @internal */
export type Comparer<T> = (a: T, b: T) => Comparison;
/* @internal */
export const enum Comparison {
LessThan = -1,
EqualTo = 0,
GreaterThan = 1
}
/* @internal */
export type Selector<T, U> = (v: T) => U;
// branded string type used to store absolute, normalized and canonicalized paths
// arbitrary file name can be converted to Path via toPath function
export type Path = string & { __pathBrand: any };

View File

@ -860,7 +860,8 @@ namespace ts.server {
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.directoryStructureHost);
scriptInfo.attachToProject(this);
},
removed => this.detachScriptInfoFromProject(removed)
removed => this.detachScriptInfoFromProject(removed),
compareStringsCaseSensitive
);
const elapsed = timestamp() - start;
this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`);

View File

@ -958,7 +958,7 @@ namespace ts.server {
projects,
project => project.getLanguageService().findReferences(file, position),
/*comparer*/ undefined,
/*areEqual (TODO: fixme)*/ undefined
equateValues
);
}

View File

@ -288,8 +288,7 @@ namespace ts.server {
return index === 0 || value !== array[index - 1];
}
export function enumerateInsertsAndDeletes<T>(newItems: SortedReadonlyArray<T>, oldItems: SortedReadonlyArray<T>, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, compare?: Comparer<T>) {
compare = compare || compareValues;
export function enumerateInsertsAndDeletes<T>(newItems: SortedReadonlyArray<T>, oldItems: SortedReadonlyArray<T>, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, comparer: Comparer<T>) {
let newIndex = 0;
let oldIndex = 0;
const newLen = newItems.length;
@ -297,7 +296,7 @@ namespace ts.server {
while (newIndex < newLen && oldIndex < oldLen) {
const newItem = newItems[newIndex];
const oldItem = oldItems[oldIndex];
const compareResult = compare(newItem, oldItem);
const compareResult = comparer(newItem, oldItem);
if (compareResult === Comparison.LessThan) {
inserted(newItem);
newIndex++;

View File

@ -115,7 +115,10 @@ namespace ts.JsTyping {
// add typings for unresolved imports
if (unresolvedImports) {
const module = deduplicate(unresolvedImports.map(moduleId => nodeCoreModules.has(moduleId) ? "node" : moduleId));
const module = deduplicate(
unresolvedImports.map(moduleId => nodeCoreModules.has(moduleId) ? "node" : moduleId),
equateStringsCaseSensitive,
compareStringsCaseSensitive);
addInferredTypings(module, "Inferred typings from unresolved imports");
}
// Add the cached typing locations for inferred typings that are already installed

View File

@ -44,7 +44,10 @@ namespace ts.Completions.PathCompletions {
containsPath(rootDirectory, scriptPath, basePath, ignoreCase) ? scriptPath.substr(rootDirectory.length) : undefined);
// Now find a path for each potential directory that is to be merged with the one containing the script
return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory)));
return deduplicate(
map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory)),
equateStringsCaseSensitive,
compareStringsCaseSensitive);
}
function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): CompletionEntry[] {
@ -271,7 +274,7 @@ namespace ts.Completions.PathCompletions {
}
}
return deduplicate(nonRelativeModuleNames);
return deduplicate(nonRelativeModuleNames, equateStringsCaseSensitive, compareStringsCaseSensitive);
}
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo {

View File

@ -1757,7 +1757,7 @@ namespace ts {
const newLineCharacter = getNewLineOrDefaultFromHost(host);
const rulesProvider = getRuleProvider(formatOptions);
return flatMap(deduplicate(errorCodes), errorCode => {
return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => {
cancellationToken.throwIfCancellationRequested();
return codefix.getFixes({ errorCode, sourceFile, span, program, newLineCharacter, host, cancellationToken, rulesProvider });
});