From 0b345dac63234a2014ec1cd305a629cf3b60bf2c Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 9 Jul 2021 10:48:49 -0700 Subject: [PATCH 1/3] Make getSourceOfProjectReferenceRedirect take a Path --- src/compiler/program.ts | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 81539867f51bc..e96df1e8bf42e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1163,7 +1163,7 @@ namespace ts { // The originalFileName could not be actual source file name if file found was d.ts from referecned project // So in this case try to look up if this is output from referenced project, if it is use the redirected project in that case - const resultFromDts = getRedirectReferenceForResolutionFromSourceOfProject(file.originalFileName, file.path); + const resultFromDts = getRedirectReferenceForResolutionFromSourceOfProject(file.path); if (resultFromDts) return resultFromDts; // If preserveSymlinks is true, module resolution wont jump the symlink @@ -1171,13 +1171,12 @@ namespace ts { // Note:: Currently we try the real path only if the // file is from node_modules to avoid having to run real path on all file paths if (!host.realpath || !options.preserveSymlinks || !stringContains(file.originalFileName, nodeModulesPathPart)) return undefined; - const realDeclarationFileName = host.realpath(file.originalFileName); - const realDeclarationPath = toPath(realDeclarationFileName); - return realDeclarationPath === file.path ? undefined : getRedirectReferenceForResolutionFromSourceOfProject(realDeclarationFileName, realDeclarationPath); + const realDeclarationPath = toPath(host.realpath(file.originalFileName)); + return realDeclarationPath === file.path ? undefined : getRedirectReferenceForResolutionFromSourceOfProject(realDeclarationPath); } - function getRedirectReferenceForResolutionFromSourceOfProject(fileName: string, filePath: Path) { - const source = getSourceOfProjectReferenceRedirect(fileName); + function getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path) { + const source = getSourceOfProjectReferenceRedirect(filePath); if (isString(source)) return getResolvedProjectReferenceToRedirect(source); if (!source) return undefined; // Output of .d.ts file so return resolved ref that matches the out file name @@ -2472,7 +2471,7 @@ namespace ts { function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, reason: FileIncludeReason): void { getSourceFileFromReferenceWorker( fileName, - fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, ignoreNoDefaultLib, reason, packageId), // TODO: GH#18217 + fileName => findSourceFile(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId), // TODO: GH#18217 (diagnostic, ...args) => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, diagnostic, args), reason ); @@ -2514,20 +2513,21 @@ namespace ts { } // Get source file from normalized fileName - function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { + function findSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { tracing?.push(tracing.Phase.Program, "findSourceFile", { fileName, isDefaultLib: isDefaultLib || undefined, fileIncludeKind: (FileIncludeKind as any)[reason.kind], }); - const result = findSourceFileWorker(fileName, path, isDefaultLib, ignoreNoDefaultLib, reason, packageId); + const result = findSourceFileWorker(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId); tracing?.pop(); return result; } - function findSourceFileWorker(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { + function findSourceFileWorker(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { + const path = toPath(fileName); if (useSourceOfProjectReferenceRedirect) { - let source = getSourceOfProjectReferenceRedirect(fileName); + let source = getSourceOfProjectReferenceRedirect(path); // If preserveSymlinks is true, module resolution wont jump the symlink // but the resolved real path may be the .d.ts from project reference // Note:: Currently we try the real path only if the @@ -2537,12 +2537,12 @@ namespace ts { options.preserveSymlinks && isDeclarationFileName(fileName) && stringContains(fileName, nodeModulesPathPart)) { - const realPath = host.realpath(fileName); - if (realPath !== fileName) source = getSourceOfProjectReferenceRedirect(realPath); + const realPath = toPath(host.realpath(fileName)); + if (realPath !== path) source = getSourceOfProjectReferenceRedirect(realPath); } if (source) { const file = isString(source) ? - findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, reason, packageId) : + findSourceFile(source, isDefaultLib, ignoreNoDefaultLib, reason, packageId) : undefined; if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); return file; @@ -2750,8 +2750,8 @@ namespace ts { return ts.forEachResolvedProjectReference(resolvedProjectReferences, cb); } - function getSourceOfProjectReferenceRedirect(file: string) { - if (!isDeclarationFileName(file)) return undefined; + function getSourceOfProjectReferenceRedirect(path: Path) { + if (!isDeclarationFileName(path)) return undefined; if (mapFromToProjectReferenceRedirectSource === undefined) { mapFromToProjectReferenceRedirectSource = new Map(); forEachResolvedProjectReference(resolvedRef => { @@ -2772,7 +2772,7 @@ namespace ts { } }); } - return mapFromToProjectReferenceRedirectSource.get(toPath(file)); + return mapFromToProjectReferenceRedirectSource.get(path); } function isSourceOfProjectReferenceRedirect(fileName: string) { @@ -2954,10 +2954,8 @@ namespace ts { modulesWithElidedImports.set(file.path, true); } else if (shouldAddFile) { - const path = toPath(resolvedFileName); findSourceFile( resolvedFileName, - path, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.Import, file: file.path, index, }, @@ -3691,7 +3689,7 @@ namespace ts { useSourceOfProjectReferenceRedirect: boolean; toPath(fileName: string): Path; getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; - getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined; + getSourceOfProjectReferenceRedirect(path: Path): SourceOfProjectReferenceRedirect | undefined; forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined; } @@ -3778,7 +3776,7 @@ namespace ts { } function fileExistsIfProjectReferenceDts(file: string) { - const source = host.getSourceOfProjectReferenceRedirect(file); + const source = host.getSourceOfProjectReferenceRedirect(host.toPath(file)); return source !== undefined ? isString(source) ? originalFileExists.call(host.compilerHost, source) : true : undefined; From 6ce7989194aa55d89bcb3ccbf4a29ca0441db54f Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 7 Jul 2021 10:34:48 -0700 Subject: [PATCH 2/3] Add useCaseSensitiveFileNames to ModuleResolutionHost ...so that path comparisons can use it during module resolution. --- src/compiler/moduleNameResolver.ts | 9 +++++++-- src/compiler/program.ts | 2 +- src/compiler/types.ts | 1 + src/compiler/utilities.ts | 2 ++ src/harness/harnessLanguageService.ts | 3 ++- src/server/project.ts | 1 + src/testRunner/unittests/moduleResolution.ts | 5 +++-- tests/baselines/reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 9 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index a1c993dc57e74..b5f83ae0b7109 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -280,6 +280,11 @@ namespace ts { } const nodeModulesAtTypes = combinePaths("node_modules", "@types"); + function arePathsEqual(path1: string, path2: string, host: ModuleResolutionHost): boolean { + const useCaseSensitiveFileNames = typeof host.useCaseSensitiveFileNames === "function" ? host.useCaseSensitiveFileNames() : host.useCaseSensitiveFileNames; + return comparePaths(path1, path2, !useCaseSensitiveFileNames) === Comparison.EqualTo; + } + /** * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups @@ -343,7 +348,7 @@ namespace ts { resolvedTypeReferenceDirective = { primary, resolvedFileName, - originalPath: fileName === resolvedFileName ? undefined : fileName, + originalPath: arePathsEqual(fileName, resolvedFileName, host) ? undefined : fileName, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName), }; @@ -1122,7 +1127,7 @@ namespace ts { let resolvedValue = resolved.value; if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { const path = realPath(resolvedValue.path, host, traceEnabled); - const originalPath = path === resolvedValue.path ? undefined : resolvedValue.path; + const originalPath = arePathsEqual(path, resolvedValue.path, host) ? undefined : resolvedValue.path; resolvedValue = { ...resolvedValue, path, originalPath }; } // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index e96df1e8bf42e..f7c0f00932f6f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -3778,7 +3778,7 @@ namespace ts { function fileExistsIfProjectReferenceDts(file: string) { const source = host.getSourceOfProjectReferenceRedirect(host.toPath(file)); return source !== undefined ? - isString(source) ? originalFileExists.call(host.compilerHost, source) : true : + isString(source) ? originalFileExists.call(host.compilerHost, source) as boolean : true : undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1af5eefc9c85c..96f947b85ebf3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6461,6 +6461,7 @@ namespace ts { realpath?(path: string): string; getCurrentDirectory?(): string; getDirectories?(path: string): string[]; + useCaseSensitiveFileNames?: boolean | (() => boolean); } /** diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9e695428cc150..58c43594069e9 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6210,7 +6210,9 @@ namespace ts { } export interface SymlinkedDirectory { + /** Matches the casing returned by `realpath`. Used to compute the `realpath` of children. */ real: string; + /** toPath(real). Stored to avoid repeated recomputation. */ realPath: Path; } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 85bd824da9c45..6f7245b01a9ff 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -325,7 +325,8 @@ namespace Harness.LanguageService { readFile: fileName => { const scriptInfo = this.getScriptInfo(fileName); return scriptInfo && scriptInfo.content; - } + }, + useCaseSensitiveFileNames: this.useCaseSensitiveFileNames() }; this.getModuleResolutionsForFile = (fileName) => { const scriptInfo = this.getScriptInfo(fileName)!; diff --git a/src/server/project.ts b/src/server/project.ts index bbc0c5624197f..30560bc3e2b2a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1706,6 +1706,7 @@ namespace ts.server { readFile: this.projectService.host.readFile.bind(this.projectService.host), getDirectories: this.projectService.host.getDirectories.bind(this.projectService.host), trace: this.projectService.host.trace?.bind(this.projectService.host), + useCaseSensitiveFileNames: this.program.useCaseSensitiveFileNames(), }; } return this.projectService.host; diff --git a/src/testRunner/unittests/moduleResolution.ts b/src/testRunner/unittests/moduleResolution.ts index e9f44a9e6bb77..a11978dab4993 100644 --- a/src/testRunner/unittests/moduleResolution.ts +++ b/src/testRunner/unittests/moduleResolution.ts @@ -65,11 +65,12 @@ namespace ts { fileExists: path => { assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); return map.has(path); - } + }, + useCaseSensitiveFileNames: true }; } else { - return { readFile, realpath, fileExists: path => map.has(path) }; + return { readFile, realpath, fileExists: path => map.has(path), useCaseSensitiveFileNames: true }; } function readFile(path: string): string | undefined { const file = map.get(path); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 876553628398e..a8aa81a17a866 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3059,6 +3059,7 @@ declare namespace ts { realpath?(path: string): string; getCurrentDirectory?(): string; getDirectories?(path: string): string[]; + useCaseSensitiveFileNames?: boolean | (() => boolean); } /** * Represents the result of module resolution. diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index dc5a6420faa10..0eb359f9ad9ce 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3059,6 +3059,7 @@ declare namespace ts { realpath?(path: string): string; getCurrentDirectory?(): string; getDirectories?(path: string): string[]; + useCaseSensitiveFileNames?: boolean | (() => boolean); } /** * Represents the result of module resolution. From 9df6f710fb7d840b639d8762e7d45b0b2716eab2 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 9 Jul 2021 16:04:42 -0700 Subject: [PATCH 3/3] Re-enable realpathSync.native for case-insensitive file systems --- src/compiler/sys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 1724a05120b1b..4f853cbb146cf 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -1279,7 +1279,7 @@ namespace ts { const platform: string = _os.platform(); const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); - const realpathSync = useCaseSensitiveFileNames ? (_fs.realpathSync.native ?? _fs.realpathSync) : _fs.realpathSync; + const realpathSync = _fs.realpathSync.native ?? _fs.realpathSync; const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); const getCurrentDirectory = memoize(() => process.cwd());