Skip to content

Commit e801d79

Browse files
committed
Add special file name lower conversion routine and use that instead of toLowerCase
Fixes #31819 and #35559
1 parent 6c0aea5 commit e801d79

9 files changed

Lines changed: 69 additions & 20 deletions

File tree

src/compiler/commandLineParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2610,7 +2610,7 @@ namespace ts {
26102610
errors: Push<Diagnostic>,
26112611
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
26122612
): ParsedTsconfig | undefined {
2613-
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toLowerCase(extendedConfigPath);
2613+
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath);
26142614
let value: ExtendedConfigCacheEntry | undefined;
26152615
let extendedResult: TsConfigSourceFile;
26162616
let extendedConfig: ParsedTsconfig | undefined;
@@ -2914,7 +2914,7 @@ namespace ts {
29142914
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: readonly FileExtensionInfo[] = []): ExpandResult {
29152915
basePath = normalizePath(basePath);
29162916

2917-
const keyMapper = host.useCaseSensitiveFileNames ? identity : toLowerCase;
2917+
const keyMapper = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
29182918

29192919
// Literal file names (provided via the "files" array in tsconfig.json) are stored in a
29202920
// file map with a possibly case insensitive key. We use this map later when when including
@@ -3083,7 +3083,7 @@ namespace ts {
30833083
const match = wildcardDirectoryPattern.exec(spec);
30843084
if (match) {
30853085
return {
3086-
key: useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(),
3086+
key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]),
30873087
flags: watchRecursivePattern.test(spec) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None
30883088
};
30893089
}

src/compiler/core.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,32 @@ namespace ts {
13991399
/** Returns lower case string */
14001400
export function toLowerCase(x: string) { return x.toLowerCase(); }
14011401

1402+
const fileNameLowerCaseRegExp = /[^İi̇ıßa-z0-9\\/:\-_\. ]+/g;
1403+
export function toFileNameLowerCase(x: string) {
1404+
// Handle special characters and make those case sensitive instead
1405+
//
1406+
// |-#--|-Character-|-Char code-|-Desc------------------------------------------------------|
1407+
// | 1. | i | 105 | Ascii i |
1408+
// | 2. | I | 73 | Ascii I |
1409+
// |-------- Special characters ------------------------------------------------------------|
1410+
// | 3. | İ | 304 | Uppper case I with dot above |
1411+
// | 4. | i̇ | 105,775 | Lower case of İ (3rd item) |
1412+
// | 5. | İ | 73,775 | Upper case of i̇ (4th item), lower case is i̇ (4th item) |
1413+
// | 6. | ı | 305 | Lower case i without dot, upper case is I (2nd item) |
1414+
// | 7. | ß | 223 | Lower case sharp s
1415+
//
1416+
// Because item 3 is special where in its lowercase character has its own
1417+
// upper case form we cant convert its case.
1418+
// Rest special characters are either already in lower case format or
1419+
// they have corresponding upper case character so they dont need special handling
1420+
//
1421+
// But to avoid having to do string building for most common cases, also ignore
1422+
// a-z, 0-9, i̇, ı, ß, \, /, ., : and space
1423+
return fileNameLowerCaseRegExp.test(x) ?
1424+
x.replace(fileNameLowerCaseRegExp, toLowerCase) :
1425+
x;
1426+
}
1427+
14021428
/** Throws an error because a function is not implemented. */
14031429
export function notImplemented(): never {
14041430
throw new Error("Not implemented");
@@ -1860,7 +1886,7 @@ namespace ts {
18601886

18611887
export type GetCanonicalFileName = (fileName: string) => string;
18621888
export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName {
1863-
return useCaseSensitiveFileNames ? identity : toLowerCase;
1889+
return useCaseSensitiveFileNames ? identity : toFileNameLowerCase;
18641890
}
18651891

18661892
/** Represents a "prefix*suffix" pattern. */
@@ -2006,4 +2032,4 @@ namespace ts {
20062032
}
20072033
}
20082034
}
2009-
}
2035+
}

src/compiler/program.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2438,7 +2438,7 @@ namespace ts {
24382438
addFileToRefFileMap(fileName, file, refFile);
24392439

24402440
if (host.useCaseSensitiveFileNames()) {
2441-
const pathLowerCase = path.toLowerCase();
2441+
const pathLowerCase = toFileNameLowerCase(path);
24422442
// for case-sensitive file systems check if we've already seen some file with similar filename ignoring case
24432443
const existingFile = filesByNameIgnoreCase!.get(pathLowerCase);
24442444
if (existingFile) {

src/server/editorServices.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3354,7 +3354,7 @@ namespace ts.server {
33543354
else {
33553355
let exclude = false;
33563356
if (typeAcquisition.enable || typeAcquisition.enableAutoDiscovery) {
3357-
const baseName = getBaseFileName(normalizedNames[i].toLowerCase());
3357+
const baseName = getBaseFileName(toFileNameLowerCase(normalizedNames[i]));
33583358
if (fileExtensionIs(baseName, "js")) {
33593359
const inferredTypingName = removeFileExtension(baseName);
33603360
const cleanedTypingName = removeMinAndVersionNumbers(inferredTypingName);

src/server/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2219,7 +2219,7 @@ namespace ts.server {
22192219
}
22202220

22212221
getCanonicalFileName(fileName: string) {
2222-
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
2222+
const name = this.host.useCaseSensitiveFileNames ? fileName : toFileNameLowerCase(fileName);
22232223
return normalizePath(name);
22242224
}
22252225

src/services/shims.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -922,8 +922,8 @@ namespace ts {
922922
() => {
923923
const results = this.languageService.getDocumentHighlights(fileName, position, JSON.parse(filesToSearch));
924924
// workaround for VS document highlighting issue - keep only items from the initial file
925-
const normalizedName = normalizeSlashes(fileName).toLowerCase();
926-
return filter(results, r => normalizeSlashes(r.fileName).toLowerCase() === normalizedName);
925+
const normalizedName = toFileNameLowerCase(normalizeSlashes(fileName));
926+
return filter(results, r => toFileNameLowerCase(normalizeSlashes(r.fileName)) === normalizedName);
927927
});
928928
}
929929

@@ -1295,4 +1295,4 @@ namespace ts {
12951295
}
12961296
}
12971297

1298-
/* eslint-enable no-in-operator */
1298+
/* eslint-enable no-in-operator */

src/testRunner/unittests/paths.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,22 @@ describe("unittests:: core paths", () => {
289289
assert.strictEqual(ts.getRelativePathFromDirectory("file:///a/b/c", "file:///a/b", /*ignoreCase*/ false), "..");
290290
assert.strictEqual(ts.getRelativePathFromDirectory("file:///c:", "file:///d:", /*ignoreCase*/ false), "file:///d:/");
291291
});
292+
it("toFileNameLowerCase", () => {
293+
assert.strictEqual(
294+
ts.toFileNameLowerCase("/user/UserName/projects/Project/file.ts"),
295+
"/user/username/projects/project/file.ts"
296+
);
297+
assert.strictEqual(
298+
ts.toFileNameLowerCase("/user/UserName/projects/projectß/file.ts"),
299+
"/user/username/projects/projectß/file.ts"
300+
);
301+
assert.strictEqual(
302+
ts.toFileNameLowerCase("/user/UserName/projects/İproject/file.ts"),
303+
"/user/username/projects/İproject/file.ts"
304+
);
305+
assert.strictEqual(
306+
ts.toFileNameLowerCase("/user/UserName/projects/ı/file.ts"),
307+
"/user/username/projects/ı/file.ts"
308+
);
309+
});
292310
});

src/testRunner/unittests/tsserver/watchEnvironment.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,8 @@ namespace ts.projectSystem {
569569
});
570570

571571
describe("unittests:: tsserver:: watchEnvironment:: file names on case insensitive file system", () => {
572-
function verifyFileNames(projectRoot: string) {
572+
function verifyFileNames(projectRoot: string, projectRootPath: string) {
573+
const keyMapper = (str: string) => str.replace(projectRoot, projectRootPath);
573574
const file: File = {
574575
path: `${projectRoot}/foo.ts`,
575576
content: `import { foo } from "bar"`
@@ -580,11 +581,11 @@ namespace ts.projectSystem {
580581
const expectedWatchFiles = [libFile.path, `${projectRoot}/tsconfig.json`, `${projectRoot}/jsconfig.json`];
581582
checkWatchedFilesDetailed(
582583
host,
583-
expectedWatchFiles.map(toLowerCase),
584+
expectedWatchFiles.map(keyMapper),
584585
1,
585586
arrayToMap(
586587
expectedWatchFiles,
587-
toLowerCase,
588+
keyMapper,
588589
fileName => [{
589590
fileName,
590591
pollingInterval: PollingInterval.Low
@@ -595,12 +596,12 @@ namespace ts.projectSystem {
595596
const expectedWatchedDirectories = [`${projectRoot}/node_modules`, `${projectRoot}/node_modules/@types`];
596597
checkWatchedDirectoriesDetailed(
597598
host,
598-
expectedWatchedDirectories.map(toLowerCase),
599+
expectedWatchedDirectories.map(keyMapper),
599600
1,
600601
/*recursive*/ true,
601602
arrayToMap(
602603
expectedWatchedDirectories,
603-
toLowerCase,
604+
keyMapper,
604605
directoryName => [{
605606
directoryName,
606607
fallbackPollingInterval: PollingInterval.Medium,
@@ -611,11 +612,15 @@ namespace ts.projectSystem {
611612
}
612613

613614
it("project with ascii file names", () => {
614-
verifyFileNames(`${tscWatch.projects}/I`);
615+
verifyFileNames("/User/userName/Projects/I", "/user/username/projects/i");
616+
});
617+
618+
it("project with ascii file names with i", () => {
619+
verifyFileNames("/User/userName/Projects/i", "/user/username/projects/i");
615620
});
616621

617622
it("project with unicode file names", () => {
618-
verifyFileNames(`${tscWatch.projects}/İ`);
623+
verifyFileNames("/User/userName/Projects/İ", "/user/username/projects/İ");
619624
});
620625
});
621626
}

src/tsserver/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ namespace ts.server {
792792
// //server/location
793793
// ^ <- from 0 to this position
794794
const firstSlash = path.indexOf(directorySeparator, 2);
795-
return firstSlash !== -1 ? path.substring(0, firstSlash).toLowerCase() : path;
795+
return firstSlash !== -1 ? toFileNameLowerCase(path.substring(0, firstSlash)) : path;
796796
}
797797
const rootLength = getRootLength(path);
798798
if (rootLength === 0) {
@@ -801,7 +801,7 @@ namespace ts.server {
801801
}
802802
if (path.charCodeAt(1) === CharacterCodes.colon && path.charCodeAt(2) === CharacterCodes.slash) {
803803
// rooted path that starts with c:/... - extract drive letter
804-
return path.charAt(0).toLowerCase();
804+
return toFileNameLowerCase(path.charAt(0));
805805
}
806806
if (path.charCodeAt(0) === CharacterCodes.slash && path.charCodeAt(1) !== CharacterCodes.slash) {
807807
// rooted path that starts with slash - /somename - use key for current drive

0 commit comments

Comments
 (0)