Skip to content

Commit 87d0e77

Browse files
authored
Fix issues with global typings cache and what gets watches (#59869)
1 parent 7976d9c commit 87d0e77

File tree

124 files changed

+10909
-7814
lines changed

Some content is hidden

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

124 files changed

+10909
-7814
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52732,6 +52732,7 @@ function createBasicNodeBuilderModuleSpecifierResolutionHost(host: TypeCheckerHo
5273252732
readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined,
5273352733
getDefaultResolutionModeForFile: file => host.getDefaultResolutionModeForFile(file),
5273452734
getModeForResolutionAtIndex: (file, index) => host.getModeForResolutionAtIndex(file, index),
52735+
getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
5273552736
};
5273652737
}
5273752738

src/compiler/moduleNameResolver.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
ModuleResolutionKind,
7575
moduleResolutionOptionDeclarations,
7676
moduleResolutionSupportsPackageJsonExportsAndImports,
77+
ModuleSpecifierResolutionHost,
7778
noop,
7879
normalizePath,
7980
normalizeSlashes,
@@ -788,7 +789,7 @@ export function resolvePackageNameToPackageJson(
788789
): PackageJsonInfo | undefined {
789790
const moduleResolutionState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options);
790791

791-
return forEachAncestorDirectory(containingDirectory, ancestorDirectory => {
792+
return forEachAncestorDirectoryStoppingAtGlobalCache(host, containingDirectory, ancestorDirectory => {
792793
if (getBaseFileName(ancestorDirectory) !== "node_modules") {
793794
const nodeModulesFolder = combinePaths(ancestorDirectory, "node_modules");
794795
const candidate = combinePaths(nodeModulesFolder, packageName);
@@ -2383,7 +2384,8 @@ export interface PackageJsonInfoContents {
23832384
* @internal
23842385
*/
23852386
export function getPackageScopeForPath(directory: string, state: ModuleResolutionState): PackageJsonInfo | undefined {
2386-
return forEachAncestorDirectory(
2387+
return forEachAncestorDirectoryStoppingAtGlobalCache(
2388+
state.host,
23872389
directory,
23882390
dir => getPackageJsonInfo(dir, /*onlyRecordFailures*/ false, state),
23892391
);
@@ -3003,18 +3005,41 @@ function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions,
30033005
}
30043006

30053007
function lookup(extensions: Extensions) {
3006-
return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => {
3007-
if (getBaseFileName(ancestorDirectory) !== "node_modules") {
3008-
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(cache, moduleName, mode, ancestorDirectory, redirectedReference, state);
3009-
if (resolutionFromCache) {
3010-
return resolutionFromCache;
3008+
return forEachAncestorDirectoryStoppingAtGlobalCache(
3009+
state.host,
3010+
normalizeSlashes(directory),
3011+
ancestorDirectory => {
3012+
// Dont go past global cache location
3013+
if (getBaseFileName(ancestorDirectory) !== "node_modules") {
3014+
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(cache, moduleName, mode, ancestorDirectory, redirectedReference, state);
3015+
if (resolutionFromCache) {
3016+
return resolutionFromCache;
3017+
}
3018+
return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference));
30113019
}
3012-
return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference));
3013-
}
3014-
});
3020+
},
3021+
);
30153022
}
30163023
}
30173024

3025+
/**
3026+
* Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result.
3027+
* Stops at global cache location
3028+
* @internal
3029+
*/
3030+
export function forEachAncestorDirectoryStoppingAtGlobalCache<T, P extends string>(
3031+
host: ModuleResolutionHost | ModuleSpecifierResolutionHost,
3032+
directory: P,
3033+
callback: (directory: P) => T | undefined,
3034+
): T | undefined {
3035+
const globalCache = host?.getGlobalTypingsCacheLocation?.();
3036+
return forEachAncestorDirectory(directory, ancestorDirectory => {
3037+
const result = callback(ancestorDirectory as P);
3038+
if (result !== undefined) return result;
3039+
if (ancestorDirectory === globalCache) return false;
3040+
}) || undefined;
3041+
}
3042+
30183043
function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined {
30193044
const nodeModulesFolder = combinePaths(directory, "node_modules");
30203045
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
@@ -3258,14 +3283,18 @@ export function classicNameResolver(moduleName: string, containingFile: string,
32583283

32593284
if (!isExternalModuleNameRelative(moduleName)) {
32603285
// Climb up parent directories looking for a module.
3261-
const resolved = forEachAncestorDirectory(containingDirectory, directory => {
3262-
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(cache, moduleName, /*mode*/ undefined, directory, redirectedReference, state);
3263-
if (resolutionFromCache) {
3264-
return resolutionFromCache;
3265-
}
3266-
const searchName = normalizePath(combinePaths(directory, moduleName));
3267-
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state));
3268-
});
3286+
const resolved = forEachAncestorDirectoryStoppingAtGlobalCache(
3287+
state.host,
3288+
containingDirectory,
3289+
directory => {
3290+
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(cache, moduleName, /*mode*/ undefined, directory, redirectedReference, state);
3291+
if (resolutionFromCache) {
3292+
return resolutionFromCache;
3293+
}
3294+
const searchName = normalizePath(combinePaths(directory, moduleName));
3295+
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state));
3296+
},
3297+
);
32693298
if (resolved) return resolved;
32703299
if (extensions & (Extensions.TypeScript | Extensions.Declaration)) {
32713300
// If we didn't find the file normally, look it up in @types.

src/compiler/moduleSpecifiers.ts

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
flatMap,
3434
flatten,
3535
forEach,
36-
forEachAncestorDirectory,
36+
forEachAncestorDirectoryStoppingAtGlobalCache,
3737
FutureSourceFile,
3838
getBaseFileName,
3939
GetCanonicalFileName,
@@ -701,9 +701,14 @@ function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolut
701701
if (host.getNearestAncestorDirectoryWithPackageJson) {
702702
return host.getNearestAncestorDirectoryWithPackageJson(fileName);
703703
}
704-
return forEachAncestorDirectory(fileName, directory => {
705-
return host.fileExists(combinePaths(directory, "package.json")) ? directory : undefined;
706-
});
704+
return forEachAncestorDirectoryStoppingAtGlobalCache(
705+
host,
706+
fileName,
707+
directory =>
708+
host.fileExists(combinePaths(directory, "package.json")) ?
709+
directory :
710+
undefined,
711+
);
707712
}
708713

709714
/** @internal */
@@ -732,29 +737,33 @@ export function forEachFileNameOfModule<T>(
732737

733738
const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath();
734739
const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd);
735-
const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => {
736-
const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName)));
737-
if (!symlinkDirectories) return undefined; // Continue to ancestor directory
738-
739-
// Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts)
740-
if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) {
741-
return false; // Stop search, each ancestor directory will also hit this condition
742-
}
743-
744-
return forEach(targets, target => {
745-
if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) {
746-
return;
740+
const result = symlinkedDirectories && forEachAncestorDirectoryStoppingAtGlobalCache(
741+
host,
742+
getDirectoryPath(fullImportedFileName),
743+
realPathDirectory => {
744+
const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName)));
745+
if (!symlinkDirectories) return undefined; // Continue to ancestor directory
746+
747+
// Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts)
748+
if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) {
749+
return false; // Stop search, each ancestor directory will also hit this condition
747750
}
748751

749-
const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName);
750-
for (const symlinkDirectory of symlinkDirectories) {
751-
const option = resolvePath(symlinkDirectory, relative);
752-
const result = cb(option, target === referenceRedirect);
753-
shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths
754-
if (result) return result;
755-
}
756-
});
757-
});
752+
return forEach(targets, target => {
753+
if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) {
754+
return;
755+
}
756+
757+
const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName);
758+
for (const symlinkDirectory of symlinkDirectories) {
759+
const option = resolvePath(symlinkDirectory, relative);
760+
const result = cb(option, target === referenceRedirect);
761+
shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths
762+
if (result) return result;
763+
}
764+
});
765+
},
766+
);
758767
return result || (preferSymlinks
759768
? forEach(targets, p => shouldFilterIgnoredPaths && containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect))
760769
: undefined);

src/compiler/program.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,6 +2016,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
20162016
getFileIncludeReasons: () => fileReasons,
20172017
structureIsReused,
20182018
writeFile,
2019+
getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
20192020
};
20202021

20212022
onProgramCreateComplete();
@@ -2741,6 +2742,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
27412742
createHash: maybeBind(host, host.createHash),
27422743
getModuleResolutionCache: () => program.getModuleResolutionCache(),
27432744
trace: maybeBind(host, host.trace),
2745+
getGlobalTypingsCacheLocation: program.getGlobalTypingsCacheLocation,
27442746
};
27452747
}
27462748

src/compiler/resolutionCache.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ export interface ResolutionCacheHost extends MinimalResolutionCacheHost {
193193
scheduleInvalidateResolutionsOfFailedLookupLocations(): void;
194194
getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined;
195195
projectName?: string;
196-
getGlobalCache?(): string | undefined;
197196
globalCacheResolutionModuleName?(externalModuleName: string): string;
198197
writeLog(s: string): void;
199198
getCurrentProgram(): Program | undefined;
@@ -313,6 +312,11 @@ export function canWatchDirectoryOrFile(pathComponents: Readonly<PathPathCompone
313312
return length > perceivedOsRootLength + 1;
314313
}
315314

315+
/** @internal */
316+
export function canWatchDirectoryOrFilePath(path: Path) {
317+
return canWatchDirectoryOrFile(getPathComponents(path));
318+
}
319+
316320
/** @internal */
317321
export function canWatchAtTypes(atTypes: Path) {
318322
// Otherwise can watch directory only if we can watch the parent directory of node_modules/@types
@@ -328,7 +332,7 @@ function isInDirectoryPath(dirComponents: Readonly<PathPathComponents>, fileOrDi
328332
}
329333

330334
function canWatchAffectedPackageJsonOrNodeModulesOfAtTypes(fileOrDirPath: Path) {
331-
return canWatchDirectoryOrFile(getPathComponents(fileOrDirPath));
335+
return canWatchDirectoryOrFilePath(fileOrDirPath);
332336
}
333337

334338
/** @internal */
@@ -343,6 +347,7 @@ export function getDirectoryToWatchFailedLookupLocation(
343347
rootDir: string,
344348
rootPath: Path,
345349
rootPathComponents: Readonly<PathPathComponents>,
350+
isRootWatchable: boolean,
346351
getCurrentDirectory: () => string | undefined,
347352
preferNonRecursiveWatch: boolean | undefined,
348353
): DirectoryOfFailedLookupWatch | undefined {
@@ -356,7 +361,7 @@ export function getDirectoryToWatchFailedLookupLocation(
356361
const nodeModulesIndex = failedLookupPathComponents.indexOf("node_modules" as Path);
357362
if (nodeModulesIndex !== -1 && nodeModulesIndex + 1 <= perceivedOsRootLength + 1) return undefined; // node_modules not at position where it can be watched
358363
const lastNodeModulesIndex = failedLookupPathComponents.lastIndexOf("node_modules" as Path);
359-
if (isInDirectoryPath(rootPathComponents, failedLookupPathComponents)) {
364+
if (isRootWatchable && isInDirectoryPath(rootPathComponents, failedLookupPathComponents)) {
360365
if (failedLookupPathComponents.length > rootPathComponents.length + 1) {
361366
// Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution
362367
return getDirectoryOfFailedLookupWatch(
@@ -461,12 +466,13 @@ export function getDirectoryToWatchFailedLookupLocationFromTypeRoot(
461466
typeRootPath: Path,
462467
rootPath: Path,
463468
rootPathComponents: Readonly<PathPathComponents>,
469+
isRootWatchable: boolean,
464470
getCurrentDirectory: () => string | undefined,
465471
preferNonRecursiveWatch: boolean | undefined,
466472
filterCustomPath: (path: Path) => boolean, // Return true if this path can be used
467473
): Path | undefined {
468474
const typeRootPathComponents = getPathComponents(typeRootPath);
469-
if (isInDirectoryPath(rootPathComponents, typeRootPathComponents)) {
475+
if (isRootWatchable && isInDirectoryPath(rootPathComponents, typeRootPathComponents)) {
470476
// Because this is called when we are watching typeRoot, we dont need additional check whether typeRoot is not say c:/users/node_modules/@types when root is c:/
471477
return rootPath;
472478
}
@@ -531,12 +537,12 @@ function resolveModuleNameUsingGlobalCache(
531537
const host = getModuleResolutionHost(resolutionHost);
532538
const primaryResult = ts_resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference, mode);
533539
// return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts
534-
if (!resolutionHost.getGlobalCache) {
540+
if (!resolutionHost.getGlobalTypingsCacheLocation) {
535541
return primaryResult;
536542
}
537543

538544
// otherwise try to load typings from @types
539-
const globalCache = resolutionHost.getGlobalCache();
545+
const globalCache = resolutionHost.getGlobalTypingsCacheLocation();
540546
if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) {
541547
// create different collection of failed lookup locations for second pass
542548
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
@@ -620,6 +626,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
620626
const rootDir = getRootDirectoryOfResolutionCache(rootDirForResolution, getCurrentDirectory);
621627
const rootPath = resolutionHost.toPath(rootDir);
622628
const rootPathComponents = getPathComponents(rootPath);
629+
const isRootWatchable = canWatchDirectoryOrFile(rootPathComponents);
623630

624631
const isSymlinkCache = new Map<Path, boolean>();
625632
const packageDirWatchers = new Map<Path, PackageDirWatcher>(); // Watching packageDir if symlink otherwise watching dirPath
@@ -1118,6 +1125,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
11181125
rootDir,
11191126
rootPath,
11201127
rootPathComponents,
1128+
isRootWatchable,
11211129
getCurrentDirectory,
11221130
resolutionHost.preferNonRecursiveWatch,
11231131
);
@@ -1328,6 +1336,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
13281336
rootDir,
13291337
rootPath,
13301338
rootPathComponents,
1339+
isRootWatchable,
13311340
getCurrentDirectory,
13321341
resolutionHost.preferNonRecursiveWatch,
13331342
);
@@ -1629,6 +1638,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
16291638
resolutionHost.toPath(typeRoot),
16301639
rootPath,
16311640
rootPathComponents,
1641+
isRootWatchable,
16321642
getCurrentDirectory,
16331643
resolutionHost.preferNonRecursiveWatch,
16341644
dirPath => directoryWatchesOfFailedLookups.has(dirPath) || dirPathToSymlinkPackageRefCount.has(dirPath),

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7897,6 +7897,7 @@ export interface ModuleResolutionHost {
78977897
getCurrentDirectory?(): string;
78987898
getDirectories?(path: string): string[];
78997899
useCaseSensitiveFileNames?: boolean | (() => boolean) | undefined;
7900+
/** @internal */ getGlobalTypingsCacheLocation?(): string | undefined;
79007901
}
79017902

79027903
/**

src/harness/incrementalUtils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,32 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
499499
if (fileSize > ts.server.maxFileSize) return "";
500500
return text !== undefined ? text || undefined : readFile(fileName);
501501
};
502+
const getSourceFile = compilerHost.getSourceFile;
503+
compilerHost.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
504+
const projectScriptKind = project.getScriptKind(fileName);
505+
const scriptKind = ts.ensureScriptKind(fileName, /*scriptKind*/ undefined);
506+
if (scriptKind === projectScriptKind) return getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
507+
508+
let text: string | undefined;
509+
try {
510+
text = compilerHost.readFile(fileName);
511+
}
512+
catch (e) {
513+
onError?.(e.message);
514+
text = "";
515+
}
516+
return text !== undefined ?
517+
ts.createSourceFile(fileName, text, languageVersionOrOptions, /*setParentNodes*/ undefined, projectScriptKind) :
518+
undefined;
519+
};
502520
const resolutionHostCacheHost: ts.ResolutionCacheHost = {
503521
...compilerHost,
504522

505523
getCompilerHost: () => compilerHost,
506524
toPath: project.toPath.bind(project),
507525
getCompilationSettings: project.getCompilationSettings.bind(project),
508526
projectName: project.projectName,
509-
getGlobalCache: project.getGlobalCache.bind(project),
527+
getGlobalTypingsCacheLocation: project.getGlobalTypingsCacheLocation.bind(project),
510528
globalCacheResolutionModuleName: project.globalCacheResolutionModuleName.bind(project),
511529
fileIsOpen: project.fileIsOpen.bind(project),
512530
getCurrentProgram: () => project.getCurrentProgram(),
@@ -540,6 +558,7 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
540558
moduleResolutionCache,
541559
),
542560
);
561+
compilerHost.getGlobalTypingsCacheLocation = resolutionHostCacheHost.getGlobalTypingsCacheLocation;
543562
verifyProgramStructure(
544563
ts.createProgram({
545564
rootNames: project.getScriptFileNames(),

0 commit comments

Comments
 (0)