Skip to content

Commit 80ad0de

Browse files
authored
Fixes to handle file names in module resolution watching and createGetCanonicalFileName (#36106)
* Add test case to verify directory casing preservation when watching * Fix unicode file name handling when watching failed lookup locations * Add special file name lower conversion routine and use that instead of toLowerCase Fixes #31819 and #35559 * Remove unicode from code * Replace toLocaleLowerCase on filenames with ts.toFileNameLowerCase * Make the intent of using toFileNameLowerCase more clear and why we make the restriction on turkish I with dot on top of it * Update baselines for newly added tests in master
1 parent ad8feb5 commit 80ad0de

File tree

185 files changed

+2867
-2696
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

185 files changed

+2867
-2696
lines changed

src/compiler/commandLineParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2618,7 +2618,7 @@ namespace ts {
26182618
errors: Push<Diagnostic>,
26192619
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
26202620
): ParsedTsconfig | undefined {
2621-
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toLowerCase(extendedConfigPath);
2621+
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath);
26222622
let value: ExtendedConfigCacheEntry | undefined;
26232623
let extendedResult: TsConfigSourceFile;
26242624
let extendedConfig: ParsedTsconfig | undefined;
@@ -2922,7 +2922,7 @@ namespace ts {
29222922
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: readonly FileExtensionInfo[] = []): ExpandResult {
29232923
basePath = normalizePath(basePath);
29242924

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

29272927
// Literal file names (provided via the "files" array in tsconfig.json) are stored in a
29282928
// file map with a possibly case insensitive key. We use this map later when when including
@@ -3091,7 +3091,7 @@ namespace ts {
30913091
const match = wildcardDirectoryPattern.exec(spec);
30923092
if (match) {
30933093
return {
3094-
key: useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(),
3094+
key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]),
30953095
flags: watchRecursivePattern.test(spec) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None
30963096
};
30973097
}

src/compiler/core.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,46 @@ namespace ts {
14041404
/** Returns lower case string */
14051405
export function toLowerCase(x: string) { return x.toLowerCase(); }
14061406

1407+
// We convert the file names to lower case as key for file name on case insensitive file system
1408+
// While doing so we need to handle special characters (eg \u0130) to ensure that we dont convert
1409+
// it to lower case, fileName with its lowercase form can exist along side it.
1410+
// Handle special characters and make those case sensitive instead
1411+
//
1412+
// |-#--|-Unicode--|-Char code-|-Desc-------------------------------------------------------------------|
1413+
// | 1. | i | 105 | Ascii i |
1414+
// | 2. | I | 73 | Ascii I |
1415+
// |-------- Special characters ------------------------------------------------------------------------|
1416+
// | 3. | \u0130 | 304 | Uppper case I with dot above |
1417+
// | 4. | i,\u0307 | 105,775 | i, followed by 775: Lower case of (3rd item) |
1418+
// | 5. | I,\u0307 | 73,775 | I, followed by 775: Upper case of (4th item), lower case is (4th item) |
1419+
// | 6. | \u0131 | 305 | Lower case i without dot, upper case is I (2nd item) |
1420+
// | 7. | \u00DF | 223 | Lower case sharp s |
1421+
//
1422+
// Because item 3 is special where in its lowercase character has its own
1423+
// upper case form we cant convert its case.
1424+
// Rest special characters are either already in lower case format or
1425+
// they have corresponding upper case character so they dont need special handling
1426+
//
1427+
// But to avoid having to do string building for most common cases, also ignore
1428+
// a-z, 0-9, \u0131, \u00DF, \, /, ., : and space
1429+
const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g;
1430+
/**
1431+
* Case insensitive file systems have descripencies in how they handle some characters (eg. turkish Upper case I with dot on top - \u0130)
1432+
* This function is used in places where we want to make file name as a key on these systems
1433+
* It is possible on mac to be able to refer to file name with I with dot on top as a fileName with its lower case form
1434+
* But on windows we cannot. Windows can have fileName with I with dot on top next to its lower case and they can not each be referred with the lowercase forms
1435+
* Technically we would want this function to be platform sepcific as well but
1436+
* our api has till now only taken caseSensitive as the only input and just for some characters we dont want to update API and ensure all customers use those api
1437+
* We could use upper case and we would still need to deal with the descripencies but
1438+
* we want to continue using lower case since in most cases filenames are lowercasewe and wont need any case changes and avoid having to store another string for the key
1439+
* So for this function purpose, we go ahead and assume character I with dot on top it as case sensitive since its very unlikely to use lower case form of that special character
1440+
*/
1441+
export function toFileNameLowerCase(x: string) {
1442+
return fileNameLowerCaseRegExp.test(x) ?
1443+
x.replace(fileNameLowerCaseRegExp, toLowerCase) :
1444+
x;
1445+
}
1446+
14071447
/** Throws an error because a function is not implemented. */
14081448
export function notImplemented(): never {
14091449
throw new Error("Not implemented");
@@ -1865,7 +1905,7 @@ namespace ts {
18651905

18661906
export type GetCanonicalFileName = (fileName: string) => string;
18671907
export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName {
1868-
return useCaseSensitiveFileNames ? identity : toLowerCase;
1908+
return useCaseSensitiveFileNames ? identity : toFileNameLowerCase;
18691909
}
18701910

18711911
/** Represents a "prefix*suffix" pattern. */

src/compiler/program.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,7 @@ namespace ts {
13971397
}
13981398
if (resolveTypeReferenceDirectiveNamesWorker) {
13991399
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
1400-
const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase());
1400+
const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName));
14011401
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath, getResolvedProjectReferenceToRedirect(newSourceFile.originalFileName));
14021402
// ensure that types resolutions are still correct
14031403
const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
@@ -2190,7 +2190,7 @@ namespace ts {
21902190
}
21912191

21922192
function getLibFileFromReference(ref: FileReference) {
2193-
const libName = ref.fileName.toLocaleLowerCase();
2193+
const libName = toFileNameLowerCase(ref.fileName);
21942194
const libFileName = libMap.get(libName);
21952195
if (libFileName) {
21962196
return getSourceFile(combinePaths(defaultLibraryPath, libFileName));
@@ -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) {
@@ -2650,7 +2650,7 @@ namespace ts {
26502650

26512651
function processTypeReferenceDirectives(file: SourceFile) {
26522652
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
2653-
const typeDirectives = map(file.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase());
2653+
const typeDirectives = map(file.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName));
26542654
if (!typeDirectives) {
26552655
return;
26562656
}
@@ -2661,7 +2661,7 @@ namespace ts {
26612661
const ref = file.typeReferenceDirectives[i];
26622662
const resolvedTypeReferenceDirective = resolutions[i];
26632663
// store resolved type directive on the file
2664-
const fileName = ref.fileName.toLocaleLowerCase();
2664+
const fileName = toFileNameLowerCase(ref.fileName);
26652665
setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective);
26662666
processTypeReferenceDirective(
26672667
fileName,
@@ -2753,7 +2753,7 @@ namespace ts {
27532753

27542754
function processLibReferenceDirectives(file: SourceFile) {
27552755
forEach(file.libReferenceDirectives, libReference => {
2756-
const libName = libReference.fileName.toLocaleLowerCase();
2756+
const libName = toFileNameLowerCase(libReference.fileName);
27572757
const libFileName = libMap.get(libName);
27582758
if (libFileName) {
27592759
// we ignore any 'no-default-lib' reference set on this file.
@@ -3200,7 +3200,7 @@ namespace ts {
32003200
blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain));
32013201
}
32023202

3203-
const emitFileKey = !host.useCaseSensitiveFileNames() ? emitFilePath.toLocaleLowerCase() : emitFilePath;
3203+
const emitFileKey = !host.useCaseSensitiveFileNames() ? toFileNameLowerCase(emitFilePath) : emitFilePath;
32043204
// Report error if multiple files write into same file
32053205
if (emitFilesSeen.has(emitFileKey)) {
32063206
// Already seen the same emit file - report error

src/compiler/resolutionCache.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ namespace ts {
176176
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
177177
const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory()));
178178
const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217
179+
const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0;
179180

180181
// TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames
181182
const typeRootsWatches = createMap<FileWatcher>();
@@ -439,15 +440,23 @@ namespace ts {
439440
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
440441
// Ensure failed look up is normalized path
441442
failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
442-
Debug.assert(failedLookupLocation.length === failedLookupLocationPath.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`);
443-
const subDirectoryInRoot = failedLookupLocationPath.indexOf(directorySeparator, rootPath.length + 1);
444-
if (subDirectoryInRoot !== -1) {
443+
const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator);
444+
const failedLookupSplit = failedLookupLocation.split(directorySeparator);
445+
Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`);
446+
if (failedLookupPathSplit.length > rootSplitLength + 1) {
445447
// Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution
446-
return { dir: failedLookupLocation.substr(0, subDirectoryInRoot), dirPath: failedLookupLocationPath.substr(0, subDirectoryInRoot) as Path };
448+
return {
449+
dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator),
450+
dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path
451+
};
447452
}
448453
else {
449454
// Always watch root directory non recursively
450-
return { dir: rootDir!, dirPath: rootPath, nonRecursive: false }; // TODO: GH#18217
455+
return {
456+
dir: rootDir!,
457+
dirPath: rootPath,
458+
nonRecursive: false
459+
};
451460
}
452461
}
453462

src/compiler/transformers/declarations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ namespace ts {
401401
forEach(sourceFile.libReferenceDirectives, ref => {
402402
const lib = host.getLibFileFromReference(ref);
403403
if (lib) {
404-
ret.set(ref.fileName.toLocaleLowerCase(), true);
404+
ret.set(toFileNameLowerCase(ref.fileName), true);
405405
}
406406
});
407407
return ret;

0 commit comments

Comments
 (0)