Skip to content

Commit e60bbac

Browse files
Fix declaration emit when the packages are included through symlinks (#37438)
* Convert symlink scenarios to virtual FS where its symlinks are correctly maintained Adds test for #36866 * Fix the casing issue when redirects differ in casing of the file * Make ModuleSpecifierResolutionHost internal * Refactoring for ModuleSpecifierResolutionHost * If any of the file path option is from node_modules folder, consider only paths in node_modules folder * Update src/services/utilities.ts Co-Authored-By: Andrew Branch <[email protected]> Co-authored-by: Andrew Branch <[email protected]>
1 parent a510cad commit e60bbac

27 files changed

+1611
-320
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4066,9 +4066,10 @@ namespace ts {
40664066
tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? {
40674067
getCommonSourceDirectory: (host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "",
40684068
getSourceFiles: () => host.getSourceFiles(),
4069-
getCurrentDirectory: maybeBind(host, host.getCurrentDirectory),
4069+
getCurrentDirectory: () => host.getCurrentDirectory(),
40704070
getProbableSymlinks: maybeBind(host, host.getProbableSymlinks),
4071-
useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames)
4071+
useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames),
4072+
redirectTargetsMap: host.redirectTargetsMap,
40724073
} : undefined },
40734074
encounteredError: false,
40744075
visitedTypes: undefined,
@@ -5028,9 +5029,7 @@ namespace ts {
50285029
specifierCompilerOptions,
50295030
contextFile,
50305031
moduleResolverHost,
5031-
host.getSourceFiles(),
50325032
{ importModuleSpecifierPreference: isBundle ? "non-relative" : "relative" },
5033-
host.redirectTargetsMap,
50345033
));
50355034
links.specifierCache = links.specifierCache || createMap();
50365035
links.specifierCache.set(contextFile.path, specifier);
@@ -6450,7 +6449,7 @@ namespace ts {
64506449
const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames);
64516450
const resolverHost = {
64526451
getCanonicalFileName,
6453-
getCurrentDirectory: context.tracker.moduleResolverHost.getCurrentDirectory ? () => context.tracker.moduleResolverHost!.getCurrentDirectory!() : () => "",
6452+
getCurrentDirectory: () => context.tracker.moduleResolverHost!.getCurrentDirectory(),
64546453
getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory()
64556454
};
64566455
const newName = getResolvedExternalModuleName(resolverHost, targetFile);

src/compiler/emitter.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,6 @@ namespace ts {
794794
isEmitBlocked: returnFalse,
795795
readFile: f => host.readFile(f),
796796
fileExists: f => host.fileExists(f),
797-
directoryExists: host.directoryExists && (f => host.directoryExists!(f)),
798797
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
799798
getProgramBuildInfo: returnUndefined,
800799
getSourceFileFromReference: returnUndefined,

src/compiler/moduleSpecifiers.ts

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,9 @@ namespace ts.moduleSpecifiers {
4141
importingSourceFileName: Path,
4242
toFileName: string,
4343
host: ModuleSpecifierResolutionHost,
44-
files: readonly SourceFile[],
45-
redirectTargetsMap: RedirectTargetsMap,
4644
oldImportSpecifier: string,
4745
): string | undefined {
48-
const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferencesForUpdate(compilerOptions, oldImportSpecifier));
46+
const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier));
4947
if (res === oldImportSpecifier) return undefined;
5048
return res;
5149
}
@@ -57,23 +55,19 @@ namespace ts.moduleSpecifiers {
5755
importingSourceFileName: Path,
5856
toFileName: string,
5957
host: ModuleSpecifierResolutionHost,
60-
files: readonly SourceFile[],
6158
preferences: UserPreferences = {},
62-
redirectTargetsMap: RedirectTargetsMap,
6359
): string {
64-
return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferences(preferences, compilerOptions, importingSourceFile));
60+
return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferences(preferences, compilerOptions, importingSourceFile));
6561
}
6662

6763
export function getNodeModulesPackageName(
6864
compilerOptions: CompilerOptions,
6965
importingSourceFileName: Path,
7066
nodeModulesFileName: string,
7167
host: ModuleSpecifierResolutionHost,
72-
files: readonly SourceFile[],
73-
redirectTargetsMap: RedirectTargetsMap,
7468
): string | undefined {
7569
const info = getInfo(importingSourceFileName, host);
76-
const modulePaths = getAllModulePaths(files, importingSourceFileName, nodeModulesFileName, info.getCanonicalFileName, host, redirectTargetsMap);
70+
const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host);
7771
return firstDefined(modulePaths,
7872
moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions, /*packageNameOnly*/ true));
7973
}
@@ -83,12 +77,10 @@ namespace ts.moduleSpecifiers {
8377
importingSourceFileName: Path,
8478
toFileName: string,
8579
host: ModuleSpecifierResolutionHost,
86-
files: readonly SourceFile[],
87-
redirectTargetsMap: RedirectTargetsMap,
8880
preferences: Preferences
8981
): string {
9082
const info = getInfo(importingSourceFileName, host);
91-
const modulePaths = getAllModulePaths(files, importingSourceFileName, toFileName, info.getCanonicalFileName, host, redirectTargetsMap);
83+
const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host);
9284
return firstDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)) ||
9385
getLocalModuleSpecifier(toFileName, info, compilerOptions, preferences);
9486
}
@@ -99,16 +91,14 @@ namespace ts.moduleSpecifiers {
9991
compilerOptions: CompilerOptions,
10092
importingSourceFile: SourceFile,
10193
host: ModuleSpecifierResolutionHost,
102-
files: readonly SourceFile[],
10394
userPreferences: UserPreferences,
104-
redirectTargetsMap: RedirectTargetsMap,
10595
): readonly string[] {
10696
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
10797
if (ambient) return [ambient];
10898

10999
const info = getInfo(importingSourceFile.path, host);
110100
const moduleSourceFile = getSourceFileOfNode(moduleSymbol.valueDeclaration || getNonAugmentationDeclaration(moduleSymbol));
111-
const modulePaths = getAllModulePaths(files, importingSourceFile.path, moduleSourceFile.originalFileName, info.getCanonicalFileName, host, redirectTargetsMap);
101+
const modulePaths = getAllModulePaths(importingSourceFile.path, moduleSourceFile.originalFileName, host);
112102

113103
const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile);
114104
const global = mapDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions));
@@ -179,26 +169,24 @@ namespace ts.moduleSpecifiers {
179169
}
180170

181171
export function forEachFileNameOfModule<T>(
182-
files: readonly SourceFile[],
183172
importingFileName: string,
184173
importedFileName: string,
185-
getCanonicalFileName: GetCanonicalFileName,
186174
host: ModuleSpecifierResolutionHost,
187-
redirectTargetsMap: RedirectTargetsMap,
188175
preferSymlinks: boolean,
189176
cb: (fileName: string) => T | undefined
190177
): T | undefined {
191-
const redirects = redirectTargetsMap.get(importedFileName);
192-
const importedFileNames = redirects ? [...redirects, importedFileName] : [importedFileName];
193-
const cwd = host.getCurrentDirectory ? host.getCurrentDirectory() : "";
178+
const getCanonicalFileName = hostGetCanonicalFileName(host);
179+
const cwd = host.getCurrentDirectory();
180+
const redirects = host.redirectTargetsMap.get(toPath(importedFileName, cwd, getCanonicalFileName)) || emptyArray;
181+
const importedFileNames = [importedFileName, ...redirects];
194182
const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd));
195183
if (!preferSymlinks) {
196184
const result = forEach(targets, cb);
197185
if (result) return result;
198186
}
199187
const links = host.getProbableSymlinks
200-
? host.getProbableSymlinks(files)
201-
: discoverProbableSymlinks(files, getCanonicalFileName, cwd);
188+
? host.getProbableSymlinks(host.getSourceFiles())
189+
: discoverProbableSymlinks(host.getSourceFiles(), getCanonicalFileName, cwd);
202190

203191
const compareStrings = (!host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames()) ? compareStringsCaseSensitive : compareStringsCaseInsensitive;
204192
const result = forEachEntry(links, (resolved, path) => {
@@ -224,20 +212,20 @@ namespace ts.moduleSpecifiers {
224212
* Looks for existing imports that use symlinks to this module.
225213
* Symlinks will be returned first so they are preferred over the real path.
226214
*/
227-
function getAllModulePaths(files: readonly SourceFile[], importingFileName: string, importedFileName: string, getCanonicalFileName: GetCanonicalFileName, host: ModuleSpecifierResolutionHost, redirectTargetsMap: RedirectTargetsMap): readonly string[] {
228-
const cwd = host.getCurrentDirectory ? host.getCurrentDirectory() : "";
215+
function getAllModulePaths(importingFileName: string, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly string[] {
216+
const cwd = host.getCurrentDirectory();
217+
const getCanonicalFileName = hostGetCanonicalFileName(host);
229218
const allFileNames = createMap<string>();
219+
let importedFileFromNodeModules = false;
230220
forEachFileNameOfModule(
231-
files,
232221
importingFileName,
233222
importedFileName,
234-
getCanonicalFileName,
235223
host,
236-
redirectTargetsMap,
237224
/*preferSymlinks*/ true,
238225
path => {
239226
// dont return value, so we collect everything
240227
allFileNames.set(path, getCanonicalFileName(path));
228+
importedFileFromNodeModules = importedFileFromNodeModules || pathContainsNodeModules(path);
241229
}
242230
);
243231

@@ -251,7 +239,10 @@ namespace ts.moduleSpecifiers {
251239
let pathsInDirectory: string[] | undefined;
252240
allFileNames.forEach((canonicalFileName, fileName) => {
253241
if (startsWith(canonicalFileName, directoryStart)) {
254-
(pathsInDirectory || (pathsInDirectory = [])).push(fileName);
242+
// If the importedFile is from node modules, use only paths in node_modules folder as option
243+
if (!importedFileFromNodeModules || pathContainsNodeModules(fileName)) {
244+
(pathsInDirectory || (pathsInDirectory = [])).push(fileName);
245+
}
255246
allFileNames.delete(fileName);
256247
}
257248
});

src/compiler/program.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,6 @@ namespace ts {
14931493
// Before falling back to the host
14941494
return host.fileExists(f);
14951495
},
1496-
...(host.directoryExists ? { directoryExists: f => host.directoryExists!(f) } : {}),
14971496
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
14981497
getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo(),
14991498
getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref),

src/compiler/transformers/declarations.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,7 @@ namespace ts {
351351
toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName),
352352
toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName),
353353
host,
354-
host.getSourceFiles(),
355354
/*preferences*/ undefined,
356-
host.redirectTargetsMap
357355
);
358356
if (!pathIsRelative(specifier)) {
359357
// If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration
@@ -376,7 +374,7 @@ namespace ts {
376374

377375
// omit references to files from node_modules (npm may disambiguate module
378376
// references when installing this package, making the path is unreliable).
379-
if (startsWith(fileName, "node_modules/") || fileName.indexOf("/node_modules/") !== -1) {
377+
if (startsWith(fileName, "node_modules/") || pathContainsNodeModules(fileName)) {
380378
return;
381379
}
382380

src/compiler/types.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3187,8 +3187,7 @@ namespace ts {
31873187
file: Path;
31883188
}
31893189

3190-
// TODO: This should implement TypeCheckerHost but that's an internal type.
3191-
export interface Program extends ScriptReferenceHost, ModuleSpecifierResolutionHost {
3190+
export interface Program extends ScriptReferenceHost {
31923191
getCurrentDirectory(): string;
31933192
/**
31943193
* Get a list of root file names that were passed to a 'createProgram'
@@ -3290,6 +3289,10 @@ namespace ts {
32903289
/*@internal*/ getProbableSymlinks(): ReadonlyMap<string>;
32913290
}
32923291

3292+
/*@internal*/
3293+
export interface Program extends TypeCheckerHost, ModuleSpecifierResolutionHost {
3294+
}
3295+
32933296
/* @internal */
32943297
export type RedirectTargetsMap = ReadonlyMap<readonly string[]>;
32953298

@@ -6420,14 +6423,19 @@ namespace ts {
64206423
getCurrentDirectory?(): string;
64216424
}
64226425

6423-
export interface ModuleSpecifierResolutionHost extends GetEffectiveTypeRootsHost {
6426+
/*@internal*/
6427+
export interface ModuleSpecifierResolutionHost {
64246428
useCaseSensitiveFileNames?(): boolean;
64256429
fileExists?(path: string): boolean;
6430+
getCurrentDirectory(): string;
64266431
readFile?(path: string): string | undefined;
64276432
/* @internal */
64286433
getProbableSymlinks?(files: readonly SourceFile[]): ReadonlyMap<string>;
64296434
/* @internal */
64306435
getGlobalTypingsCacheLocation?(): string | undefined;
6436+
6437+
getSourceFiles(): readonly SourceFile[];
6438+
readonly redirectTargetsMap: RedirectTargetsMap;
64316439
}
64326440

64336441
// Note: this used to be deprecated in our public API, but is still used internally
@@ -6441,7 +6449,7 @@ namespace ts {
64416449
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
64426450
reportInaccessibleUniqueSymbolError?(): void;
64436451
reportLikelyUnsafeImportRequiredError?(specifier: string): void;
6444-
moduleResolverHost?: ModuleSpecifierResolutionHost & { getSourceFiles(): readonly SourceFile[], getCommonSourceDirectory(): string };
6452+
moduleResolverHost?: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string };
64456453
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
64466454
trackExternalModuleSymbolOfImportTypeNode?(symbol: Symbol): void;
64476455
}

src/compiler/utilities.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3753,6 +3753,14 @@ namespace ts {
37533753
};
37543754
}
37553755

3756+
export function hostUsesCaseSensitiveFileNames(host: { useCaseSensitiveFileNames?(): boolean; }): boolean {
3757+
return host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false;
3758+
}
3759+
3760+
export function hostGetCanonicalFileName(host: { useCaseSensitiveFileNames?(): boolean; }): GetCanonicalFileName {
3761+
return createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host));
3762+
}
3763+
37563764
export interface ResolveModuleNameResolutionHost {
37573765
getCanonicalFileName(p: string): string;
37583766
getCommonSourceDirectory(): string;

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ interface Array<T> { length: number; [n: number]: T; }`
7070
}
7171

7272
export type FileOrFolderOrSymLink = File | Folder | SymLink;
73-
function isFile(fileOrFolderOrSymLink: FileOrFolderOrSymLink): fileOrFolderOrSymLink is File {
73+
export function isFile(fileOrFolderOrSymLink: FileOrFolderOrSymLink): fileOrFolderOrSymLink is File {
7474
return isString((<File>fileOrFolderOrSymLink).content);
7575
}
7676

77-
function isSymLink(fileOrFolderOrSymLink: FileOrFolderOrSymLink): fileOrFolderOrSymLink is SymLink {
77+
export function isSymLink(fileOrFolderOrSymLink: FileOrFolderOrSymLink): fileOrFolderOrSymLink is SymLink {
7878
return isString((<SymLink>fileOrFolderOrSymLink).symLink);
7979
}
8080

src/services/codefixes/helpers.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,10 @@ namespace ts.codefix {
1616
}
1717
}
1818

19-
function getModuleSpecifierResolverHost(context: TypeConstructionContext): SymbolTracker["moduleResolverHost"] {
20-
return {
21-
directoryExists: context.host.directoryExists ? d => context.host.directoryExists!(d) : undefined,
22-
fileExists: context.host.fileExists ? f => context.host.fileExists!(f) : undefined,
23-
getCurrentDirectory: context.host.getCurrentDirectory ? () => context.host.getCurrentDirectory() : undefined,
24-
readFile: context.host.readFile ? f => context.host.readFile!(f) : undefined,
25-
useCaseSensitiveFileNames: context.host.useCaseSensitiveFileNames ? () => context.host.useCaseSensitiveFileNames!() : undefined,
26-
getSourceFiles: () => context.program.getSourceFiles(),
27-
getCommonSourceDirectory: () => context.program.getCommonSourceDirectory(),
28-
};
29-
}
30-
3119
export function getNoopSymbolTrackerWithResolver(context: TypeConstructionContext): SymbolTracker {
3220
return {
3321
trackSymbol: noop,
34-
moduleResolverHost: getModuleSpecifierResolverHost(context),
22+
moduleResolverHost: getModuleSpecifierResolverHost(context.program, context.host),
3523
};
3624
}
3725

0 commit comments

Comments
 (0)