From a3b9467d4159ac0d3ff833149ad1517e249598c8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 28 Aug 2017 17:09:07 -0700 Subject: [PATCH] Resolve only once in the given directory for name --- src/compiler/resolutionCache.ts | 104 +++++++++++++++++++++++++------- src/compiler/watchedProgram.ts | 5 +- src/server/project.ts | 12 ++++ 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index ca82a68f489..7dff3f55155 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,6 +14,9 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; createHasInvalidatedResolution(): HasInvalidatedResolution; + startCachingPerDirectoryResolution(): void; + finishCachingPerDirectoryResolution(): void; + setRootDirectory(dir: string): void; clear(): void; @@ -28,7 +31,7 @@ namespace ts { /** watcher for the directory of failed lookup */ watcher: FileWatcher; /** map with key being the failed lookup location path and value being the actual location */ - mapLocations: MultiMap; + mapLocations: Map; } export interface ResolutionCacheHost extends ModuleResolutionHost { @@ -39,6 +42,7 @@ namespace ts { getCachedPartialSystem?(): CachedPartialSystem; projectName?: string; getGlobalCache?(): string | undefined; + writeLog(s: string): void; } const MAX_DIRPATHS_TO_RECURSE = 5; @@ -51,7 +55,9 @@ namespace ts { // The key in the map is source file's path. // The values are Map of resolutions with key being name lookedup. const resolvedModuleNames = createMap>(); + const perDirectoryResolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); + const perDirectoryResolvedTypeReferenceDirectives = createMap>(); const directoryWatchesOfFailedLookups = createMap(); const failedLookupLocationToDirPath = createMap(); @@ -60,6 +66,8 @@ namespace ts { return { startRecordingFilesWithChangedResolutions, finishRecordingFilesWithChangedResolutions, + startCachingPerDirectoryResolution, + finishCachingPerDirectoryResolution, resolveModuleNames, resolveTypeReferenceDirectives, invalidateResolutionOfFile, @@ -87,6 +95,7 @@ namespace ts { failedLookupLocationToDirPath.clear(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); + Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0); } function startRecordingFilesWithChangedResolutions() { @@ -105,6 +114,15 @@ namespace ts { return path => collected && collected.has(path); } + function startCachingPerDirectoryResolution() { + Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0); + } + + function finishCachingPerDirectoryResolution() { + perDirectoryResolvedModuleNames.clear(); + perDirectoryResolvedTypeReferenceDirectives.clear(); + } + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts @@ -131,6 +149,7 @@ namespace ts { names: string[], containingFile: string, cache: Map>, + perDirectoryCache: Map>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, getResult: (s: T) => R, getResultFileName: (result: R) => string | undefined, @@ -138,6 +157,12 @@ namespace ts { const path = resolutionHost.toPath(containingFile); const currentResolutionsInFile = cache.get(path); + const dirPath = getDirectoryPath(path); + let perDirectoryResolution = perDirectoryCache.get(dirPath); + if (!perDirectoryResolution) { + perDirectoryResolution = createMap(); + perDirectoryCache.set(dirPath, perDirectoryResolution); + } const newResolutions: Map = createMap(); const resolvedModules: R[] = []; @@ -148,12 +173,24 @@ namespace ts { let resolution = newResolutions.get(name); if (!resolution) { const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name); + if (existingResolution) { + // Remove from the cache since we would update the resolution in new file ourselves + currentResolutionsInFile.delete(name); + } + if (moduleResolutionIsValid(existingResolution)) { // ok, it is safe to use existing name resolution results resolution = existingResolution; } else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost); + const resolutionInDirectory = perDirectoryResolution && perDirectoryResolution.get(name); + if (resolutionInDirectory) { + resolution = resolutionInDirectory; + } + else { + resolution = loader(name, containingFile, compilerOptions, resolutionHost); + perDirectoryResolution.set(name, resolution); + } updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations); } newResolutions.set(name, resolution); @@ -169,6 +206,11 @@ namespace ts { resolvedModules.push(getResult(resolution)); } + // Close all the file watchers for the names that arent required any more + if (currentResolutionsInFile) { + clearMap(currentResolutionsInFile, resolution => withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher)); + } + // replace old results with a new one cache.set(path, newResolutions); return resolvedModules; @@ -208,25 +250,35 @@ namespace ts { } function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - return resolveNamesWithLocalCache(typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, - m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false); + resolutionHost.writeLog(`resolveTypeReferenceDirectives: ${typeDirectiveNames} in ${containingFile}`); + return resolveNamesWithLocalCache( + typeDirectiveNames, containingFile, + resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, + resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, + /*logChanges*/ false + ); } function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] { - return resolveNamesWithLocalCache(moduleNames, containingFile, resolvedModuleNames, resolveModuleName, - m => m.resolvedModule, r => r.resolvedFileName, logChanges); + resolutionHost.writeLog(`resolveModuleNames: ${moduleNames} in ${containingFile}`); + return resolveNamesWithLocalCache( + moduleNames, containingFile, + resolvedModuleNames, perDirectoryResolvedModuleNames, + resolveModuleName, m => m.resolvedModule, r => r.resolvedFileName, + logChanges + ); } function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { const cachedDir = failedLookupLocationToDirPath.get(failedLookupLocationPath); if (cachedDir) { - watchFailedLookupLocationInDirectory(cachedDir, failedLookupLocation, failedLookupLocationPath, /*dir*/ undefined); + watchFailedLookupLocationInDirectory(cachedDir, failedLookupLocationPath, /*dir*/ undefined); return; } if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { // Watch in directory of rootPath - watchFailedLookupLocationInDirectory(rootPath, failedLookupLocation, failedLookupLocationPath, rootDir); + watchFailedLookupLocationInDirectory(rootPath, failedLookupLocationPath, rootDir); return; } @@ -235,7 +287,7 @@ namespace ts { for (let i = 0; i < MAX_DIRPATHS_TO_RECURSE; i++) { const parentPath = getDirectoryPath(dirPath); if (directoryWatchesOfFailedLookups.has(dirPath) || parentPath === dirPath) { - watchFailedLookupLocationInDirectory(dirPath, failedLookupLocation, failedLookupLocationPath, dir); + watchFailedLookupLocationInDirectory(dirPath, failedLookupLocationPath, dir); return; } dirPath = parentPath; @@ -245,7 +297,7 @@ namespace ts { // Verify there are no watches in parent directory const ancestorDirPath = getAncestorDirectoryWithWatches(dirPath); // We wont need directory if we are using ancestor since its already cached - watchFailedLookupLocationInDirectory(ancestorDirPath || dirPath, failedLookupLocation, failedLookupLocationPath, dir); + watchFailedLookupLocationInDirectory(ancestorDirPath || dirPath, failedLookupLocationPath, dir); } function getAncestorDirectoryWithWatches(dirPath: Path) { @@ -258,16 +310,17 @@ namespace ts { return undefined; } - function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocation: string, failedLookupLocationPath: Path, dir: string | undefined) { + function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocationPath: Path, dir: string | undefined) { failedLookupLocationToDirPath.set(failedLookupLocationPath, dirPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); if (watches) { - watches.mapLocations.add(failedLookupLocationPath, failedLookupLocation); + const existingCount = watches.mapLocations.get(failedLookupLocationPath) || 0; + watches.mapLocations.set(failedLookupLocationPath, existingCount + 1); } else { Debug.assert(dir !== undefined); - const mapLocations = createMultiMap(); - mapLocations.add(failedLookupLocationPath, failedLookupLocation); + const mapLocations = createMap(); + mapLocations.set(failedLookupLocationPath, 1); directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), mapLocations @@ -294,19 +347,24 @@ namespace ts { }, WatchDirectoryFlags.Recursive); } - function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { + function closeFailedLookupLocationWatcher(_failedLookupLocation: string, failedLookupLocationPath: Path) { const dirPath = failedLookupLocationToDirPath.get(failedLookupLocationPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); - watches.mapLocations.remove(failedLookupLocationPath, failedLookupLocation); - // If this was last failed lookup location being tracked by the dir watcher, - // remove the failed lookup location path to dir Path entry - if (!watches.mapLocations.has(failedLookupLocationPath)) { + const refCount = watches.mapLocations.get(failedLookupLocationPath); + if (refCount === 1) { + // If this was last failed lookup location being tracked by the dir watcher, + // remove the failed lookup location path to dir Path entry + watches.mapLocations.delete(failedLookupLocationPath); failedLookupLocationToDirPath.delete(failedLookupLocationPath); + + // If there are no more files that need this watcher alive, close the watcher + if (watches.mapLocations.size === 0) { + watches.watcher.close(); + directoryWatchesOfFailedLookups.delete(dirPath); + } } - // If there are no more files that need this watcher alive, close the watcher - if (watches.mapLocations.size === 0) { - watches.watcher.close(); - directoryWatchesOfFailedLookups.delete(dirPath); + else { + watches.mapLocations.set(failedLookupLocationPath, refCount - 1); } } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 07a783c3c47..dbac0861aa5 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -293,7 +293,8 @@ namespace ts { getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation, getCachedPartialSystem, - onInvalidatedResolution: scheduleProgramUpdate + onInvalidatedResolution: scheduleProgramUpdate, + writeLog }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost); @@ -330,8 +331,10 @@ namespace ts { beforeCompile(compilerOptions); // Compile the program + resolutionCache.startCachingPerDirectoryResolution(); compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); + resolutionCache.finishCachingPerDirectoryResolution(); builder.onProgramUpdateGraph(program, hasInvalidatedResolution); // Update watches diff --git a/src/server/project.ts b/src/server/project.ts index 8d391c312b1..78987d398d4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -372,6 +372,11 @@ namespace ts.server { return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; } + /*@internal*/ + writeLog(s: string) { + this.projectService.logger.info(s); + } + private setInternalCompilerOptionsForEmittingJsFiles() { if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) { this.compilerOptions.noEmitForJsFiles = true; @@ -767,7 +772,10 @@ namespace ts.server { private updateGraphWorker() { const oldProgram = this.program; + + this.resolutionCache.startCachingPerDirectoryResolution(); this.program = this.languageService.getProgram(); + this.resolutionCache.finishCachingPerDirectoryResolution(); // bump up the version if // - oldProgram is not set - this is a first time updateGraph is called @@ -795,6 +803,10 @@ namespace ts.server { // Watch the missing files missingFilePath => this.addMissingFileWatcher(missingFilePath) ); + + // Update typeRoots watch + // Watch the type locations that would be added to program as part of automatic type resolutions + // TODO } const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray;