From 130c407708993304170a2720966cdddd4d907bd5 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 25 Oct 2017 16:30:19 -0700 Subject: [PATCH] More control over which collator to use in each situation --- src/compiler/core.ts | 394 ++++++++++++++++-------- src/compiler/parser.ts | 2 +- src/compiler/program.ts | 8 +- src/compiler/utilities.ts | 3 + src/harness/harness.ts | 4 +- src/harness/unittests/compileOnSave.ts | 6 +- src/server/session.ts | 3 +- src/services/navigateTo.ts | 6 +- src/services/navigationBar.ts | 9 +- src/services/refactors/extractSymbol.ts | 4 +- 10 files changed, 296 insertions(+), 143 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 3aea96ef354..3a706a9119d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1486,140 +1486,288 @@ namespace ts { /** * Compare two values for their order relative to each other. */ - export function compareValues(a: T | undefined, b: T | undefined) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - return a < b ? Comparison.LessThan : Comparison.GreaterThan; + export function compareValues(a: T, b: T) { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : + b === undefined ? Comparison.GreaterThan : + a < b ? Comparison.LessThan : + Comparison.GreaterThan; } /** * Compare two values for their equality. */ - export function equateValues(a: T | undefined, b: T | undefined) { + export function equateValues(a: T, b: T) { return a === b; } - interface StringCollator { - compare(a: string, b: string): number; - equate(a: string, b: string): boolean; + export interface StringCollator { + compare(a: string | undefined, b: string | undefined): number; + equate(a: string | undefined, b: string | undefined): boolean; } - // Gets string comparers compatible with the current host - function createCaseInsensitiveStringCollator() { - function createIntlStringCollator(): StringCollator { - // Strings that differ in base or accents/diacritic marks compare as unequal. - // An `undefined` locale uses the default locale of the host. - const sortCollator = new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }); - const searchCollator = new Intl.Collator(/*locales*/ undefined, { usage: "search", sensitivity: "accent" }); - return { + export interface StringCollators { + /** + * Gets a string collator for case-insensitive ordinal comparisons of strings. + * + * Ordinal comparisons are based on the difference between the unicode code points of + * both strings. Characters with multiple unicode representations are considered + * unequal. + * + * Case-insensitive comparisons compare both strings after applying `toUpperCase` to + * each string. + */ + readonly ordinalCaseInsensitive: StringCollator; + + /** + * Gets a string collator for case-sensitive ordinal comparisons of strings. + * + * Ordinal comparisons are based on the difference between the unicode code points of + * both strings. Characters with multiple unicode representations are considered + * unequal. They provide predictable ordering, but place "a" after "B". + */ + readonly ordinalCaseSensitive: StringCollator; + + /** + * Gets or sets a string collator for case-insensitive comparisons of strings in the host default locale. + * + * UI comparisons are based on the sort order of the host default locale. Ordering is not + * predictable between different host locales, but is best for displaying ordered data + * for UI presentation. Characters with multiple unicode representations may be considered + * equal. + * + * Case-insensitive comparisons compare strings that differ in only base characters or + * accents/diacritic marks as unequal. + */ + readonly uiCaseInsensitive: StringCollator; + + /** + * Gets a string collator for case-sensitive comparisons of strings in the host default locale. + * + * UI comparisons are based on the sort order of the host default locale. Ordering is not + * predictable between different host locales, but is best for displaying ordered data + * for UI presentation. Characters with multiple unicode representations may be considered + * equal. + */ + readonly uiCaseSensitive: StringCollator; + + /** + * Gets a string collator for case-insensitive comparisons of strings in an invariant locale. + * + * Invariant comparisons are based on the sort order of an invariant locale ('en-US'). + * They provide predictable ordering, placing "a" before "B". Characters with multiple + * unicode representations may be considered equal. Invariant comparisons are best used + * when interacting with the file system. + * + * Case-insensitive comparisons compare strings that differ in only base characters or + * accents/diacritic marks as unequal. + */ + readonly invariantCaseInsensitive: StringCollator; + + /** + * Gets a string collator for case-sensitive comparisons of strings in an invariant locale. + * + * Invariant comparisons are based on the sort order of an invariant locale ('en-US'). + * They provide predictable ordering, placing "a" before "B". Characters with multiple + * unicode representations may be considered equal. Invariant comparisons are best used + * when interacting with the file system. + */ + readonly invariantCaseSensitive: StringCollator; + + /** + * Gets or sets the locale for UI collators + */ + uiLocale: string | undefined; + + /** + * Creates a `StringCollator` for a specific locale and case sensitivity. + */ + create(locale: string | undefined, caseSensitive: boolean): StringCollator; + + /** + * Gets the ordinal `StringCollator` for the provided case sensitivity. + */ + getOrdinalCollator(caseSensitive: boolean): StringCollator; + + /** + * Gets the UI `StringCollator` for the provided case sensitivity. + */ + getUICollator(caseSensitive: boolean): StringCollator; + + /** + * Gets the invariant `StringCollator` for the provided case sensitivity. + */ + getInvariantCollator(caseSensitive: boolean): StringCollator; + + /** + * Gets a `StringCollator` for comparing code fragments for code generation. + */ + getCodeCollator(caseSensitive: boolean): StringCollator; + + /** + * Gets a `StringCollator` for comparing paths. + */ + getPathCollator(caseSensitive: boolean): StringCollator; + } + + export const StringCollator: StringCollators = (function () { + const invariantLocaleName = "en-US"; // we use en-US for the invariant locale + const create = getStringCollatorFactory(); + const ordinalCS: StringCollator = { + compare: compareValues, + equate: equateValues + }; + const ordinalCI: StringCollator = { + compare: (a, b) => compareValues(toUpperCase(a), toUpperCase(b)), + equate: (a, b) => toUpperCase(a) === toUpperCase(b) + }; + let invariantCI: StringCollator | undefined; + let invariantCS: StringCollator | undefined; + let uiCI: StringCollator | undefined; + let uiCS: StringCollator | undefined; + let uiLocale: string | undefined; + + return { + get ordinalCaseInsensitive() { return ordinalCI; }, + get ordinalCaseSensitive() { return ordinalCS; }, + get uiCaseInsensitive() { return uiCI || (uiCI = create(uiLocale, /*caseInsensitive*/ true)); }, + get uiCaseSensitive() { return uiCS || (uiCS = create(uiLocale, /*caseInsensitive*/ false)); }, + get invariantCaseInsensitive() { return invariantCI || (invariantCI = create(invariantLocaleName, /*caseInsensitive*/ true)); }, + get invariantCaseSensitive() { return invariantCS || (invariantCS = create(invariantLocaleName, /*caseInsensitive*/ false)); }, + get uiLocale() { return uiLocale; }, + set uiLocale(value) { + if (uiLocale !== value) { + uiLocale = value; + uiCI = undefined; + uiCS = undefined; + } + }, + create, + getOrdinalCollator, + getUICollator, + getInvariantCollator, + getCodeCollator: getInvariantCollator, + getPathCollator: getInvariantCollator + }; + + function getOrdinalCollator(caseInsensitive: boolean) { + return caseInsensitive ? StringCollator.ordinalCaseInsensitive : StringCollator.ordinalCaseSensitive; + } + + function getUICollator(caseInsensitive: boolean) { + return caseInsensitive ? StringCollator.uiCaseInsensitive : StringCollator.uiCaseSensitive; + } + + function getInvariantCollator(caseInsensitive: boolean) { + return caseInsensitive ? StringCollator.invariantCaseInsensitive : StringCollator.invariantCaseSensitive; + } + + function toUpperCase(value: string | undefined): string | undefined { + return value === undefined ? undefined : value.toUpperCase(); + } + + function compareDefined(a: string, b: string) { + return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; + } + + function compareWithCallback(a: string | undefined, b: string | undefined, comparer: (a: string, b: string) => number) { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : + b === undefined ? Comparison.GreaterThan : + toComparison(comparer(a, b)); + } + + function toComparison(value: number) { + return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo; + } + + function createIntlStringCollator(locale: string | undefined, caseInsensitive: boolean): StringCollator { + // Initialize the sort collator on first use + let sortComparer: Comparer = (a, b) => { // Intl.Collator.prototype.compare is bound to the collator. See NOTE in // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare - compare: sortCollator.compare, - equate: (a, b) => searchCollator.compare(a, b) === 0 + sortComparer = new Intl.Collator(locale, { usage: "sort", sensitivity: caseInsensitive ? "accent" : "variant" }).compare; + return sortComparer(a, b); + }; + + // Initialize the search collator on first use + let searchComparer: Comparer = (a, b) => { + // Intl.Collator.prototype.compare is bound to the collator. See NOTE in + // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare + searchComparer = new Intl.Collator(locale, { usage: "search", sensitivity: caseInsensitive ? "accent" : "variant" }).compare; + return searchComparer(a, b); }; - } - function createLocaleCompareStringCollator(): StringCollator { - // for case-insensitive comparisons we always map both strings to their - // upper-case form as some unicode characters do not properly round-trip to - // lowercase (such as ẞ). return { - compare: (a, b) => a.toLocaleUpperCase().localeCompare(b.toLocaleUpperCase()), - equate: (a, b) => a.toLocaleUpperCase() === b.toLocaleUpperCase() + compare: (a, b) => compareWithCallback(a, b, sortComparer), + equate: (a, b) => compareWithCallback(a, b, searchComparer) === 0 }; } - function createOrdinalStringCollator(): StringCollator { - // for case-insensitive comparisons we always map both strings to their - // upper-case form as some unicode characters do not properly round-trip to - // lowercase (such as ẞ). + function createLocaleCompareStringCollator(locale: string | undefined, caseInsensitive: boolean): StringCollator { + if (locale !== undefined) return getFallbackStringCollator(/*locale*/ undefined, caseInsensitive); + if (caseInsensitive) { + // for case-insensitive comparisons we always map both strings to their + // upper-case form as some unicode characters do not properly round-trip to + // lowercase (such as `ẞ` (German sharp capital s)). + return { + compare: (a, b) => compareWithCallback(a, b, localeCompareCaseInsensitive), + equate: (a, b) => compareWithCallback(a, b, localeCompareCaseInsensitive) === 0 + }; + } + else { + return { + compare: (a, b) => compareWithCallback(a, b, localeCompare), + equate: (a, b) => compareWithCallback(a, b, localeCompare) === 0 + }; + } + + function localeCompareCaseInsensitive(a: string, b: string) { + return a.toLocaleUpperCase().localeCompare(b.toLocaleUpperCase()); + } + + function localeCompare(a: string, b: string) { + return a.localeCompare(b); + } + } + + function getFallbackStringCollator(_locale: string | undefined, caseInsensitive: boolean): StringCollator { + if (caseInsensitive) return ordinalCI; + + function compareLowerCaseFirst(a: string, b: string) { + // An ordinal comparison puts "A" after "b", but for the UI we want "A" before "b". + // We first sort case insensitively. So "Aaa" will come before "baa". + // Then we sort case sensitively, so "aaa" will come before "Aaa". + return compareDefined(a.toUpperCase(), b.toUpperCase()) || compareDefined(a, b); + } + + return { + compare: (a, b) => compareWithCallback(a, b, compareLowerCaseFirst), + equate: ordinalCS.equate + }; + } + + function getStringCollatorFactory() { + // If the host supports Intl (ECMA-402), we use Intl for comparisons using the default + // locale: + if (typeof Intl === "object" && typeof Intl.Collator === "function") { + return createIntlStringCollator; + } + + // If the host does not support Intl, we fall back to localeCompare: // - // The ordinal comparison cannot properly handle comparison of the Turkish - // (dotted) i and (dotless) ı to the uppercase forms of (dotted) İ and (dotless) I. - // This is best handled by Intl and not supported in the fallback case. - return { - compare: (a, b) => { - const upperA = a.toUpperCase(); - const upperB = b.toUpperCase(); - return upperA < upperB ? Comparison.LessThan : - upperA > upperB ? Comparison.GreaterThan : - Comparison.EqualTo; - }, - equate: (a, b) => a.toUpperCase() === b.toUpperCase() - }; + // Node v0.10 provides incorrect results for comparisons using localeCompare, so we must + // verify the implementation. + if (typeof String.prototype.localeCompare === "function" && + typeof String.prototype.toLocaleUpperCase === "function" && + "a".localeCompare("B") < 0) { + return createLocaleCompareStringCollator; + } + + // Otherwise, fall back to ordinal comparison: + return getFallbackStringCollator; } - - // If the host supports Intl (ECMA-402), we use Intl for comparisons using the default - // locale: - if (typeof Intl === "object" && typeof Intl.Collator === "function") { - return createIntlStringCollator(); - } - - // If the host does not support Intl, we fall back to localeCompare: - // - // Node v0.10 provides incorrect results for comparisons using localeCompare, so we must - // verify the implementation. - if (typeof String.prototype.localeCompare === "function" && - typeof String.prototype.toLocaleUpperCase === "function" && - "a".localeCompare("B") < 0) { - return createLocaleCompareStringCollator(); - } - - // Otherwise, fall back to ordinal comparison: - return createOrdinalStringCollator(); - } - - const caseInsensitiveCollator = createCaseInsensitiveStringCollator(); - - /** - * Performs a case-insensitive comparison between two strings. - * - * If supported by the host, the default locale is used for comparisons. Otherwise, an ordinal - * comparison is used. - */ - export function compareStringsCaseInsensitive(a: string | undefined, b: string | undefined) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - const result = caseInsensitiveCollator.compare(a, b); - return result < 0 ? Comparison.LessThan : result > 0 ? Comparison.GreaterThan : Comparison.EqualTo; - } - - /** - * Performs a case-sensitive comparison between two strings. - */ - export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined) { - return compareValues(a, b); - } - - export function compareStrings(a: string | undefined, b: string | undefined, ignoreCase?: boolean): Comparison { - return ignoreCase ? compareStringsCaseInsensitive(a, b) : compareStringsCaseSensitive(a, b); - } - - /** - * Performs a case-insensitive equality comparison between two strings. - * - * If supported by the host, the default locale is used for comparisons. Otherwise, an ordinal - * comparison is used. - */ - export function equateStringsCaseInsensitive(a: string | undefined, b: string | undefined) { - return a === b - || a !== undefined - && b !== undefined - && caseInsensitiveCollator.equate(a, b); - } - - /** - * Performs a case-sensitive equality comparison between two strings. - */ - export function equateStringsCaseSensitive(a: string | undefined, b: string | undefined) { - return equateValues(a, b); - } - - export function equateStrings(a: string | undefined, b: string | undefined, ignoreCase?: boolean) { - return ignoreCase ? equateStringsCaseInsensitive(a, b) : equateStringsCaseSensitive(a, b); - } + })(); function getDiagnosticFileName(diagnostic: Diagnostic): string { return diagnostic.file ? diagnostic.file.fileName : undefined; @@ -2003,9 +2151,9 @@ namespace ts { const aComponents = getNormalizedPathComponents(a, currentDirectory); const bComponents = getNormalizedPathComponents(b, currentDirectory); const sharedLength = Math.min(aComponents.length, bComponents.length); - const comparer = ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; + const collator = StringCollator.getPathCollator(ignoreCase); for (let i = 0; i < sharedLength; i++) { - const result = comparer(aComponents[i], bComponents[i]); + const result = collator.compare(aComponents[i], bComponents[i]); if (result !== Comparison.EqualTo) { return result; } @@ -2026,9 +2174,10 @@ namespace ts { return false; } - const equalityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; + // File-system comparisons should use predictable ordering + const collator = StringCollator.getPathCollator(ignoreCase); for (let i = 0; i < parentComponents.length; i++) { - if (!equalityComparer(parentComponents[i], childComponents[i])) { + if (!collator.equate(parentComponents[i], childComponents[i])) { return false; } } @@ -2284,7 +2433,7 @@ namespace ts { // If there are no "includes", then just put everything in results[0]. const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]]; - const comparer = useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive; + const collator = StringCollator.getPathCollator(!useCaseSensitiveFileNames); for (const basePath of patterns.basePaths) { visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); } @@ -2293,7 +2442,7 @@ namespace ts { function visitDirectory(path: string, absolutePath: string, depth: number | undefined) { let { files, directories } = getFileSystemEntries(path); - files = files.slice().sort(comparer); + files = files.slice().sort(collator.compare); for (const current of files) { const name = combinePaths(path, current); @@ -2318,7 +2467,7 @@ namespace ts { } } - directories = directories.slice().sort(comparer); + directories = directories.slice().sort(collator.compare); for (const current of directories) { const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); @@ -2349,7 +2498,8 @@ namespace ts { } // Sort the offsets array using either the literal or canonical path representations. - includeBasePaths.sort(useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive); + const collator = StringCollator.getPathCollator(!useCaseSensitiveFileNames); + includeBasePaths.sort(collator.compare); // Iterate over each include base path and include unique base paths that are not a // subpath of an existing base path diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ef93dfe7d4f..bda31d83d86 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6069,7 +6069,7 @@ namespace ts { const checkJsDirectiveMatchResult = checkJsDirectiveRegEx.exec(comment); if (checkJsDirectiveMatchResult) { checkJsDirective = { - enabled: equateStrings(checkJsDirectiveMatchResult[1], "@ts-check", /*ignoreCase*/ true), + enabled: StringCollator.ordinalCaseInsensitive.equate(checkJsDirectiveMatchResult[1], "@ts-check"), end: range.end, pos: range.pos }; diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bca10eceb67..ff3143f3d96 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1101,12 +1101,14 @@ namespace ts { // If '--lib' is not specified, include default library file according to '--target' // otherwise, using options specified in '--lib' instead of '--target' default library file + + // File-system ordering should use a predictable order + const collator = StringCollator.getPathCollator(!host.useCaseSensitiveFileNames()); if (!options.lib) { - return equateStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()); + return collator.equate(file.fileName, getDefaultLibraryFileName()); } else { - const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; - return forEach(options.lib, libFileName => equalityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName))); + return forEach(options.lib, libFileName => collator.equate(file.fileName, combinePaths(defaultLibraryPath, libFileName))); } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 01bb36e9065..7ae7e84ad18 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3949,6 +3949,9 @@ namespace ts { trySetLanguageAndTerritory(language, /*territory*/ undefined, errors); } + // Set the locale for UI collation + StringCollator.uiLocale = locale; + function trySetLanguageAndTerritory(language: string, territory: string, errors?: Push): boolean { const compilerFilePath = normalizePath(sys.getExecutingFilePath()); const containingDirectoryPath = getDirectoryPath(compilerFilePath); diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 9622a6df332..8029c3672af 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1698,7 +1698,9 @@ namespace Harness { export function *iterateOutputs(outputFiles: Harness.Compiler.GeneratedFile[]): IterableIterator<[string, string]> { // Collect, test, and sort the fileNames - outputFiles.sort((a, b) => ts.compareStrings(cleanName(a.fileName), cleanName(b.fileName))); + // As this uses the file system, use a predictable order + const collator = ts.StringCollator.getPathCollator(/*ignoreCase*/ false); + outputFiles.sort((a, b) => collator.compare(cleanName(a.fileName), cleanName(b.fileName))); const dupeCase = ts.createMap(); // Yield them for (const outputFile of outputFiles) { diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 7be6ab5b323..8c7ae507991 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -13,8 +13,10 @@ namespace ts.projectSystem { describe("CompileOnSave affected list", () => { function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: FileOrFolder[] }[]) { const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; - const actualResult = response.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName)); - expectedFileList = expectedFileList.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName)); + // File-system ordering should use a predictable order + const collator = StringCollator.getPathCollator(/*ignoreCase*/ false); + const actualResult = response.sort((list1, list2) => collator.compare(list1.projectFileName, list2.projectFileName)); + expectedFileList = expectedFileList.sort((list1, list2) => collator.compare(list1.projectFileName, list2.projectFileName)); assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`); diff --git a/src/server/session.ts b/src/server/session.ts index e8ce752745e..ea09a9720f8 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1186,6 +1186,7 @@ namespace ts.server { const completions = project.getLanguageService().getCompletionsAtPosition(file, position); if (simplifiedResult) { + const comparer = StringCollator.uiCaseSensitive.compare; return mapDefined(completions && completions.entries, entry => { if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) { const { name, kind, kindModifiers, sortText, replacementSpan, hasAction } = entry; @@ -1193,7 +1194,7 @@ namespace ts.server { // Use `hasAction || undefined` to avoid serializing `false`. return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan, hasAction: hasAction || undefined }; } - }).sort((a, b) => compareStrings(a.name, b.name)); + }).sort((a, b) => comparer(a.name, b.name)); } else { return completions; diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index 2647a87ada7..3988a88fe78 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -175,12 +175,8 @@ namespace ts.NavigateTo { function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem): number { // TODO(cyrusn): get the gamut of comparisons that VS already uses here. - // Right now we just sort by kind first, and then by name of the item. - // We first sort case insensitively. So "Aaa" will come before "bar". - // Then we sort case sensitively, so "aaa" will come before "Aaa". return i1.matchKind - i2.matchKind || - compareStringsCaseInsensitive(i1.name, i2.name) || - compareStringsCaseSensitive(i1.name, i2.name); + StringCollator.uiCaseSensitive.compare(i1.name, i2.name); } function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index bf34f28fed6..91b7ec78a89 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -368,13 +368,8 @@ namespace ts.NavigationBar { function compareChildren(child1: NavigationBarNode, child2: NavigationBarNode): number { const name1 = tryGetName(child1.node), name2 = tryGetName(child2.node); - if (name1 && name2) { - const cmp = ts.compareStringsCaseInsensitive(name1, name2); - return cmp !== 0 ? cmp : navigationBarNodeKind(child1) - navigationBarNodeKind(child2); - } - else { - return name1 ? 1 : name2 ? -1 : navigationBarNodeKind(child1) - navigationBarNodeKind(child2); - } + return StringCollator.uiCaseInsensitive.compare(name1, name2) + || navigationBarNodeKind(child1) - navigationBarNodeKind(child2); } /** diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 84f6ffb0815..2a5ea4ec53b 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1154,7 +1154,9 @@ namespace ts.refactor.extractSymbol { const name1 = type1.symbol ? type1.symbol.getName() : ""; const name2 = type2.symbol ? type2.symbol.getName() : ""; - const nameDiff = compareStrings(name1, name2); + + // This is for code generation, use a predictable comparer. + const nameDiff = StringCollator.invariantCaseSensitive.compare(name1, name2); if (nameDiff !== 0) { return nameDiff; }