diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 7c4139e3e07..79a9aa8e019 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1490,17 +1490,60 @@ namespace ts { return a < b ? Comparison.LessThan : Comparison.GreaterThan; } + interface StringCollator { + compare(a: string, b: string): number; + equals(a: string, b: string): boolean; + } + // Gets string comparers compatible with the current host - function createCaseInsensitiveStringComparer(): (a: string, b: string) => number { + function createCaseInsensitiveStringComparers() { + 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 { + // 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, + equals: (a, b) => searchCollator.compare(a, b) === 0 + }; + } + + 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()), + equals: (a, b) => a.toLocaleUpperCase() === b.toLocaleUpperCase() + }; + } + + 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 ẞ). + // + // 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; + }, + equals: (a, b) => a.toUpperCase() === b.toUpperCase() + }; + } + // If the host supports Intl (ECMA-402), we use Intl for comparisons using the default // locale: if (typeof Intl === "object" && typeof Intl.Collator === "function") { - // Strings that differ in base or accents/diacritic marks compare as unequal. - // An `undefined` locale uses the default locale of the host. - // - // 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 - return new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }).compare; + return createIntlStringCollator(); } // If the host does not support Intl, we fall back to localeCompare: @@ -1510,31 +1553,14 @@ namespace ts { if (typeof String.prototype.localeCompare === "function" && typeof String.prototype.toLocaleUpperCase === "function" && "a".localeCompare("B") < 0) { - // 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 (a, b) => a.toLocaleUpperCase().localeCompare(b.toLocaleUpperCase()); + return createLocaleCompareStringCollator(); } // Otherwise, fall back to ordinal comparison: - // - // 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 ẞ). - // - // 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 (a, b) => { - const upperA = a.toUpperCase(); - const upperB = b.toUpperCase(); - return upperA < upperB ? Comparison.LessThan : - upperA > upperB ? Comparison.GreaterThan : - Comparison.EqualTo; - }; + return createOrdinalStringCollator(); } - const caseInsensitiveComparer = createCaseInsensitiveStringComparer(); + const caseInsensitiveCollator = createCaseInsensitiveStringComparers(); /** * Performs a case-insensitive comparison between two strings. @@ -1546,7 +1572,7 @@ namespace ts { if (a === b) return Comparison.EqualTo; if (a === undefined) return Comparison.LessThan; if (b === undefined) return Comparison.GreaterThan; - const result = caseInsensitiveComparer(a, b); + const result = caseInsensitiveCollator.compare(a, b); return result < 0 ? Comparison.LessThan : result > 0 ? Comparison.GreaterThan : Comparison.EqualTo; } @@ -1561,6 +1587,30 @@ namespace ts { 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.equals(a, b); + } + + /** + * Performs a case-sensitive equality comparison between two strings. + */ + export function equateStringsCaseSensitive(a: string | undefined, b: string | undefined) { + return 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; } @@ -1966,10 +2016,9 @@ namespace ts { return false; } - const stringComparer = ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; + const stringEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; for (let i = 0; i < parentComponents.length; i++) { - const result = stringComparer(parentComponents[i], childComponents[i]); - if (result !== Comparison.EqualTo) { + if (!stringEqualityComparer(parentComponents[i], childComponents[i])) { return false; } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b27d4811ecb..ef93dfe7d4f 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: compareStrings(checkJsDirectiveMatchResult[1], "@ts-check", /*ignoreCase*/ true) === Comparison.EqualTo, + enabled: equateStrings(checkJsDirectiveMatchResult[1], "@ts-check", /*ignoreCase*/ true), end: range.end, pos: range.pos }; diff --git a/src/compiler/program.ts b/src/compiler/program.ts index e87a4997f13..394dc2a7669 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1102,11 +1102,11 @@ 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 if (!options.lib) { - return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + return equateStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()); } else { - const stringComparer = host.useCaseSensitiveFileNames() ? compareStringsCaseSensitive : compareStringsCaseInsensitive; - return forEach(options.lib, libFileName => stringComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName)) === Comparison.EqualTo); + const stringEqualityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; + return forEach(options.lib, libFileName => stringEqualityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName))); } }