Skip to content

Commit 5f8b392

Browse files
committed
Revert use of Intl/localeCompare for CS comparisons
1 parent 763d17e commit 5f8b392

1 file changed

Lines changed: 44 additions & 76 deletions

File tree

src/compiler/core.ts

Lines changed: 44 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,110 +1483,78 @@ namespace ts {
14831483
return headChain;
14841484
}
14851485

1486-
function toComparison(value: number) {
1487-
return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo;
1488-
}
1489-
1490-
function compareNonNullValues<T>(a: T, b: T): Comparison {
1491-
return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo;
1492-
}
1493-
1494-
function compareValuesWithCallback<T>(a: T | undefined, b: T | undefined, comparer: (a: T, b: T) => number) {
1486+
export function compareValues<T>(a: T | undefined, b: T | undefined) {
14951487
if (a === b) return Comparison.EqualTo;
14961488
if (a === undefined) return Comparison.LessThan;
14971489
if (b === undefined) return Comparison.GreaterThan;
1498-
return toComparison(comparer(a, b));
1499-
}
1500-
1501-
export function compareValues<T>(a: T | undefined, b: T | undefined): Comparison {
1502-
return compareValuesWithCallback(a, b, compareNonNullValues);
1503-
}
1504-
1505-
interface StringComparers {
1506-
caseSensitive(a: string, b: string): number;
1507-
caseInsensitive(a: string, b: string): number;
1490+
return a < b ? Comparison.LessThan : Comparison.GreaterThan;
15081491
}
15091492

15101493
// Gets string comparers compatible with the current host
1511-
function createStringComparers() {
1512-
function createIntlComparers(): StringComparers {
1513-
// Strings that differ in base, accents/diacritic marks, or case compare as unequal.
1514-
// An `undefined` locale uses the default locale of the host.
1515-
const caseSensitiveCollator = new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "variant" });
1516-
1494+
function createCaseInsensitiveStringComparer(): (a: string, b: string) => number {
1495+
// If the host supports Intl (ECMA-402), we use Intl for comparisons using the default
1496+
// locale:
1497+
if (typeof Intl === "object" && typeof Intl.Collator === "function") {
15171498
// Strings that differ in base or accents/diacritic marks compare as unequal.
15181499
// An `undefined` locale uses the default locale of the host.
1519-
const caseInsensitiveCollator = new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" });
1520-
1521-
return {
1522-
caseSensitive: (a, b) => caseSensitiveCollator.compare(a, b),
1523-
caseInsensitive: (a, b) => caseInsensitiveCollator.compare(a, b)
1524-
};
1525-
}
1526-
1527-
function createStringLocaleComparers(): StringComparers {
1528-
// for case-insensitive comparisons we always map both strings to their
1529-
// upper-case form as some unicode characters do not properly round-trip to
1530-
// lowercase (such as ẞ).
1531-
return {
1532-
caseSensitive: (a, b) => a.localeCompare(b),
1533-
caseInsensitive: (a, b) => a.toLocaleUpperCase().localeCompare(b.toLocaleUpperCase())
1534-
};
1535-
}
1536-
1537-
function createOrdinalComparers(): StringComparers {
1538-
// for case-insensitive comparisons we always map both strings to their
1539-
// upper-case form as some unicode characters do not properly round-trip to
1540-
// lowercase (such as ẞ).
15411500
//
1542-
// The ordinal comparison cannot properly handle comparison of the Turkish
1543-
// (dotted) i and (dotless) ı to the uppercase forms of (dotted) İ and (dotless) I.
1544-
// This is best handled by Intl and not supported in the fallback case.
1545-
return {
1546-
caseSensitive: compareNonNullValues,
1547-
caseInsensitive: (a, b) => compareNonNullValues(a.toUpperCase(), b.toUpperCase())
1548-
};
1501+
// Intl.Collator.prototype.compare is bound to the collator. See NOTE in
1502+
// http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare
1503+
return new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }).compare;
15491504
}
15501505

1551-
// If the host supports Intl (ECMA-402), we use Intl for comparisons using the default
1552-
// locale.
1553-
if (typeof Intl === "object" && typeof Intl.Collator === "function") {
1554-
return createIntlComparers();
1555-
}
1556-
1557-
// If the host does not support Intl (Safari, Node v0.10), we fall back to localeCompare.
1506+
// If the host does not support Intl, we fall back to localeCompare:
1507+
//
15581508
// Node v0.10 provides incorrect results for comparisons using localeCompare, so we must
15591509
// verify the implementation.
15601510
if (typeof String.prototype.localeCompare === "function" &&
15611511
typeof String.prototype.toLocaleUpperCase === "function" &&
15621512
"a".localeCompare("B") < 0) {
1563-
return createStringLocaleComparers();
1564-
}
1565-
1566-
// fall back to ordinal comparison
1567-
return createOrdinalComparers();
1513+
// for case-insensitive comparisons we always map both strings to their
1514+
// upper-case form as some unicode characters do not properly round-trip to
1515+
// lowercase (such as ẞ).
1516+
return (a, b) => a.toLocaleUpperCase().localeCompare(b.toLocaleUpperCase());
1517+
}
1518+
1519+
// Otherwise, fall back to ordinal comparison:
1520+
//
1521+
// for case-insensitive comparisons we always map both strings to their
1522+
// upper-case form as some unicode characters do not properly round-trip to
1523+
// lowercase (such as ẞ).
1524+
//
1525+
// The ordinal comparison cannot properly handle comparison of the Turkish
1526+
// (dotted) i and (dotless) ı to the uppercase forms of (dotted) İ and (dotless) I.
1527+
// This is best handled by Intl and not supported in the fallback case.
1528+
return (a, b) => {
1529+
const upperA = a.toUpperCase();
1530+
const upperB = b.toUpperCase();
1531+
return upperA < upperB ? Comparison.LessThan :
1532+
upperA > upperB ? Comparison.GreaterThan :
1533+
Comparison.EqualTo;
1534+
};
15681535
}
15691536

1570-
const stringComparers = createStringComparers();
1537+
const caseInsensitiveComparer = createCaseInsensitiveStringComparer();
15711538

15721539
/**
1573-
* Performs a case-sensitive comparison between two strings.
1540+
* Performs a case-insensitive comparison between two strings.
15741541
*
15751542
* If supported by the host, the default locale is used for comparisons. Otherwise, an ordinal
15761543
* comparison is used.
15771544
*/
1578-
export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined) {
1579-
return compareValuesWithCallback(a, b, stringComparers.caseSensitive);
1545+
export function compareStringsCaseInsensitive(a: string | undefined, b: string | undefined) {
1546+
if (a === b) return Comparison.EqualTo;
1547+
if (a === undefined) return Comparison.LessThan;
1548+
if (b === undefined) return Comparison.GreaterThan;
1549+
const result = caseInsensitiveComparer(a, b);
1550+
return result < 0 ? Comparison.LessThan : result > 0 ? Comparison.GreaterThan : Comparison.EqualTo;
15801551
}
15811552

15821553
/**
1583-
* Performs a case-insensitive comparison between two strings.
1584-
*
1585-
* If supported by the host, the default locale is used for comparisons. Otherwise, an ordinal
1586-
* comparison is used.
1554+
* Performs a case-sensitive comparison between two strings.
15871555
*/
1588-
export function compareStringsCaseInsensitive(a: string | undefined, b: string | undefined) {
1589-
return compareValuesWithCallback(a, b, stringComparers.caseInsensitive);
1556+
export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined) {
1557+
return compareValues(a, b);
15901558
}
15911559

15921560
export function compareStrings(a: string | undefined, b: string | undefined, ignoreCase?: boolean): Comparison {

0 commit comments

Comments
 (0)