Skip to content

Commit dee77ce

Browse files
committed
Added 'equateStrings' for string equality comparisons (rather than sorting)
1 parent 5f8b392 commit dee77ce

3 files changed

Lines changed: 84 additions & 35 deletions

File tree

src/compiler/core.ts

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,17 +1490,60 @@ namespace ts {
14901490
return a < b ? Comparison.LessThan : Comparison.GreaterThan;
14911491
}
14921492

1493+
interface StringCollator {
1494+
compare(a: string, b: string): number;
1495+
equals(a: string, b: string): boolean;
1496+
}
1497+
14931498
// Gets string comparers compatible with the current host
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") {
1499+
function createCaseInsensitiveStringComparers() {
1500+
function createIntlStringCollator(): StringCollator {
14981501
// Strings that differ in base or accents/diacritic marks compare as unequal.
14991502
// An `undefined` locale uses the default locale of the host.
1503+
const sortCollator = new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" });
1504+
const searchCollator = new Intl.Collator(/*locales*/ undefined, { usage: "search", sensitivity: "accent" });
1505+
return {
1506+
// Intl.Collator.prototype.compare is bound to the collator. See NOTE in
1507+
// http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare
1508+
compare: sortCollator.compare,
1509+
equals: (a, b) => searchCollator.compare(a, b) === 0
1510+
};
1511+
}
1512+
1513+
function createLocaleCompareStringCollator(): StringCollator {
1514+
// for case-insensitive comparisons we always map both strings to their
1515+
// upper-case form as some unicode characters do not properly round-trip to
1516+
// lowercase (such as ẞ).
1517+
return {
1518+
compare: (a, b) => a.toLocaleUpperCase().localeCompare(b.toLocaleUpperCase()),
1519+
equals: (a, b) => a.toLocaleUpperCase() === b.toLocaleUpperCase()
1520+
};
1521+
}
1522+
1523+
function createOrdinalStringCollator(): StringCollator {
1524+
// for case-insensitive comparisons we always map both strings to their
1525+
// upper-case form as some unicode characters do not properly round-trip to
1526+
// lowercase (such as ẞ).
15001527
//
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;
1528+
// The ordinal comparison cannot properly handle comparison of the Turkish
1529+
// (dotted) i and (dotless) ı to the uppercase forms of (dotted) İ and (dotless) I.
1530+
// This is best handled by Intl and not supported in the fallback case.
1531+
return {
1532+
compare: (a, b) => {
1533+
const upperA = a.toUpperCase();
1534+
const upperB = b.toUpperCase();
1535+
return upperA < upperB ? Comparison.LessThan :
1536+
upperA > upperB ? Comparison.GreaterThan :
1537+
Comparison.EqualTo;
1538+
},
1539+
equals: (a, b) => a.toUpperCase() === b.toUpperCase()
1540+
};
1541+
}
1542+
1543+
// If the host supports Intl (ECMA-402), we use Intl for comparisons using the default
1544+
// locale:
1545+
if (typeof Intl === "object" && typeof Intl.Collator === "function") {
1546+
return createIntlStringCollator();
15041547
}
15051548

15061549
// If the host does not support Intl, we fall back to localeCompare:
@@ -1510,31 +1553,14 @@ namespace ts {
15101553
if (typeof String.prototype.localeCompare === "function" &&
15111554
typeof String.prototype.toLocaleUpperCase === "function" &&
15121555
"a".localeCompare("B") < 0) {
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());
1556+
return createLocaleCompareStringCollator();
15171557
}
15181558

15191559
// 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-
};
1560+
return createOrdinalStringCollator();
15351561
}
15361562

1537-
const caseInsensitiveComparer = createCaseInsensitiveStringComparer();
1563+
const caseInsensitiveCollator = createCaseInsensitiveStringComparers();
15381564

15391565
/**
15401566
* Performs a case-insensitive comparison between two strings.
@@ -1546,7 +1572,7 @@ namespace ts {
15461572
if (a === b) return Comparison.EqualTo;
15471573
if (a === undefined) return Comparison.LessThan;
15481574
if (b === undefined) return Comparison.GreaterThan;
1549-
const result = caseInsensitiveComparer(a, b);
1575+
const result = caseInsensitiveCollator.compare(a, b);
15501576
return result < 0 ? Comparison.LessThan : result > 0 ? Comparison.GreaterThan : Comparison.EqualTo;
15511577
}
15521578

@@ -1561,6 +1587,30 @@ namespace ts {
15611587
return ignoreCase ? compareStringsCaseInsensitive(a, b) : compareStringsCaseSensitive(a, b);
15621588
}
15631589

1590+
/**
1591+
* Performs a case-insensitive equality comparison between two strings.
1592+
*
1593+
* If supported by the host, the default locale is used for comparisons. Otherwise, an ordinal
1594+
* comparison is used.
1595+
*/
1596+
export function equateStringsCaseInsensitive(a: string | undefined, b: string | undefined) {
1597+
return a === b
1598+
|| a !== undefined
1599+
&& b !== undefined
1600+
&& caseInsensitiveCollator.equals(a, b);
1601+
}
1602+
1603+
/**
1604+
* Performs a case-sensitive equality comparison between two strings.
1605+
*/
1606+
export function equateStringsCaseSensitive(a: string | undefined, b: string | undefined) {
1607+
return a === b;
1608+
}
1609+
1610+
export function equateStrings(a: string | undefined, b: string | undefined, ignoreCase?: boolean) {
1611+
return ignoreCase ? equateStringsCaseInsensitive(a, b) : equateStringsCaseSensitive(a, b);
1612+
}
1613+
15641614
function getDiagnosticFileName(diagnostic: Diagnostic): string {
15651615
return diagnostic.file ? diagnostic.file.fileName : undefined;
15661616
}
@@ -1966,10 +2016,9 @@ namespace ts {
19662016
return false;
19672017
}
19682018

1969-
const stringComparer = ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive;
2019+
const stringEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive;
19702020
for (let i = 0; i < parentComponents.length; i++) {
1971-
const result = stringComparer(parentComponents[i], childComponents[i]);
1972-
if (result !== Comparison.EqualTo) {
2021+
if (!stringEqualityComparer(parentComponents[i], childComponents[i])) {
19732022
return false;
19742023
}
19752024
}

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6069,7 +6069,7 @@ namespace ts {
60696069
const checkJsDirectiveMatchResult = checkJsDirectiveRegEx.exec(comment);
60706070
if (checkJsDirectiveMatchResult) {
60716071
checkJsDirective = {
6072-
enabled: compareStrings(checkJsDirectiveMatchResult[1], "@ts-check", /*ignoreCase*/ true) === Comparison.EqualTo,
6072+
enabled: equateStrings(checkJsDirectiveMatchResult[1], "@ts-check", /*ignoreCase*/ true),
60736073
end: range.end,
60746074
pos: range.pos
60756075
};

src/compiler/program.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,11 +1102,11 @@ namespace ts {
11021102
// If '--lib' is not specified, include default library file according to '--target'
11031103
// otherwise, using options specified in '--lib' instead of '--target' default library file
11041104
if (!options.lib) {
1105-
return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
1105+
return equateStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames());
11061106
}
11071107
else {
1108-
const stringComparer = host.useCaseSensitiveFileNames() ? compareStringsCaseSensitive : compareStringsCaseInsensitive;
1109-
return forEach(options.lib, libFileName => stringComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName)) === Comparison.EqualTo);
1108+
const stringEqualityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive;
1109+
return forEach(options.lib, libFileName => stringEqualityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName)));
11101110
}
11111111
}
11121112

0 commit comments

Comments
 (0)