diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index b22d90a76f646..6c8c79adefb81 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -79,7 +79,7 @@ namespace ts { export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; - let filesWithInvalidatedNonRelativeUnresolvedImports: Map> | undefined; + let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyMap> | undefined; let allFilesHaveInvalidatedResolution = false; const nonRelativeExternalModuleResolutions = createMultiMap(); @@ -241,14 +241,14 @@ namespace ts { } function resolveNamesWithLocalCache( - names: string[], + names: ReadonlyArray, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, cache: Map>, perDirectoryCacheWithRedirects: CacheWithRedirects>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - reusedNames: string[] | undefined, + reusedNames: ReadonlyArray | undefined, logChanges: boolean): (R | undefined)[] { const path = resolutionHost.toPath(containingFile); @@ -675,7 +675,7 @@ namespace ts { ); } - function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: Map>) { + function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyMap>) { Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index d2b7c7869c568..bd31a04a0df13 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -128,7 +128,7 @@ interface Array {}` return s && isString((s).symLink); } - function invokeWatcherCallbacks(callbacks: T[], invokeCallback: (cb: T) => void): void { + function invokeWatcherCallbacks(callbacks: ReadonlyArray | undefined, invokeCallback: (cb: T) => void): void { if (callbacks) { // The array copy is made to ensure that even if one of the callback removes the callbacks, // we dont miss any callbacks following it @@ -650,15 +650,15 @@ interface Array {}` // For overriding the methods invokeWatchedDirectoriesCallback(folderFullPath: string, relativePath: string) { - invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath))!, cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath)); } invokeWatchedDirectoriesRecursiveCallback(folderFullPath: string, relativePath: string) { - invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(folderFullPath))!, cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath)); } private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, useFileNameInCallback?: boolean) { - invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath))!, ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind)); + invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind)); } private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { diff --git a/src/server/project.ts b/src/server/project.ts index ff2e3539b6bbc..35ccea6600818 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -795,41 +795,6 @@ namespace ts.server { } } - /* @internal */ - private extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: string[]): ReadonlyArray { - const cached = this.cachedUnresolvedImportsPerFile.get(file.path); - if (cached) { - // found cached result, return - return cached; - } - let unresolvedImports: string[] | undefined; - if (file.resolvedModules) { - file.resolvedModules.forEach((resolvedModule, name) => { - // pick unresolved non-relative names - if (!resolvedModule && !isExternalModuleNameRelative(name) && !isAmbientlyDeclaredModule(name)) { - // for non-scoped names extract part up-to the first slash - // for scoped names - extract up to the second slash - let trimmed = name.trim(); - let i = trimmed.indexOf("/"); - if (i !== -1 && trimmed.charCodeAt(0) === CharacterCodes.at) { - i = trimmed.indexOf("/", i + 1); - } - if (i !== -1) { - trimmed = trimmed.substr(0, i); - } - (unresolvedImports || (unresolvedImports = [])).push(trimmed); - } - }); - } - - this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray); - return unresolvedImports || emptyArray; - - function isAmbientlyDeclaredModule(name: string) { - return ambientModules.some(m => m === name); - } - } - /* @internal */ onFileAddedOrRemoved() { this.hasAddedorRemovedFiles = true; @@ -863,15 +828,7 @@ namespace ts.server { // (can reuse cached imports for files that were not changed) // 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch if (hasNewProgram || changedFiles.length) { - let result: string[] | undefined; - const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName())); - for (const sourceFile of this.program.getSourceFiles()) { - const unResolved = this.extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules); - if (unResolved !== emptyArray) { - (result || (result = [])).push(...unResolved); - } - } - this.lastCachedUnresolvedImportsList = result ? sortAndDeduplicate(result) : emptyArray; + this.lastCachedUnresolvedImportsList = getUnresolvedImports(this.program, this.cachedUnresolvedImportsPerFile); } this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasAddedorRemovedFiles); @@ -1218,6 +1175,25 @@ namespace ts.server { } } + function getUnresolvedImports(program: Program, cachedUnresolvedImportsPerFile: Map>): SortedReadonlyArray { + const ambientModules = program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName())); + return sortAndDeduplicate(flatMap(program.getSourceFiles(), sourceFile => + extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules, cachedUnresolvedImportsPerFile))); + } + function extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: ReadonlyArray, cachedUnresolvedImportsPerFile: Map>): ReadonlyArray { + return getOrUpdate(cachedUnresolvedImportsPerFile, file.path, () => { + if (!file.resolvedModules) return emptyArray; + let unresolvedImports: string[] | undefined; + file.resolvedModules.forEach((resolvedModule, name) => { + // pick unresolved non-relative names + if (!resolvedModule && !isExternalModuleNameRelative(name) && !ambientModules.some(m => m === name)) { + unresolvedImports = append(unresolvedImports, parsePackageName(name).packageName); + } + }); + return unresolvedImports || emptyArray; + }); + } + /** * If a file is opened and no tsconfig (or jsconfig) is found, * the file and its imports/references are put into an InferredProject.