diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index f30c2a63a6ca9..100e399004da7 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -734,8 +734,9 @@ namespace ts { /* @internal */ export function createModeAwareCache(): ModeAwareCache { - const underlying = new Map(); - const memoizedReverseKeys = new Map(); + const underlying = new Map(); + type ModeAwareCacheKey = string & { __modeAwareCacheKey: any; }; + const memoizedReverseKeys = new Map(); const cache: ModeAwareCache = { get(specifier, mode) { @@ -765,22 +766,30 @@ namespace ts { return cache; function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) { - const result = mode === undefined ? specifier : `${mode}|${specifier}`; + const result = (mode === undefined ? specifier : `${mode}|${specifier}`) as ModeAwareCacheKey; memoizedReverseKeys.set(result, [specifier, mode]); return result; } } /* @internal */ - export function zipToModeAwareCache(file: SourceFile, keys: readonly string[] | readonly FileReference[], values: readonly V[]): ModeAwareCache { + export function getResolutionName(entry: FileReference | StringLiteralLike) { + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + return isStringLiteralLike(entry) ? entry.text : entry.fileName.toLowerCase(); + } + + /* @internal */ + export function getResolutionMode(entry: FileReference | StringLiteralLike, file: SourceFile) { + return isStringLiteralLike(entry) ? getModeForUsageLocation(file, entry) : entry.resolutionMode || file.impliedNodeFormat; + } + + /* @internal */ + export function zipToModeAwareCache(file: SourceFile, keys: readonly StringLiteralLike[] | readonly FileReference[], values: readonly V[]): ModeAwareCache { Debug.assert(keys.length === values.length); const map = createModeAwareCache(); for (let i = 0; i < keys.length; ++i) { const entry = keys[i]; - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const name = !isString(entry) ? entry.fileName.toLowerCase() : entry; - const mode = !isString(entry) ? entry.resolutionMode || file.impliedNodeFormat : getModeForResolutionAtIndex(file, i); - map.set(name, mode, values[i]); + map.set(getResolutionName(entry), getResolutionMode(entry, file), values[i]); } return map; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 1fdb28469a33b..85d34e9106d57 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -623,17 +623,20 @@ namespace ts { } /* @internal */ - export function loadWithModeAwareCache(names: string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { + export function loadWithModeAwareCache(names: readonly StringLiteralLike[] | readonly string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, resolutionInfo: ModuleResolutionInfo | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { if (names.length === 0) { return []; } const resolutions: T[] = []; const cache = new Map(); let i = 0; - for (const name of names) { + for (const entry of resolutionInfo ? resolutionInfo.names : names) { let result: T; - const mode = getModeForResolutionAtIndex(containingFile, i); + const mode = !isString(entry) ? + getModeForUsageLocation(containingFile, entry) : + getModeForResolutionAtIndex(containingFile, i); i++; + const name = isString(entry) ? entry : entry.text; const cacheKey = mode !== undefined ? `${mode}|${name}` : name; if (cache.has(cacheKey)) { result = cache.get(cacheKey)!; @@ -1098,24 +1101,41 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache | undefined; let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined; - let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: SourceFile, containingFileName: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[]; + let actualResolveModuleNamesWorker: ( + moduleNames: readonly StringLiteralLike[], + containingFile: SourceFile, + containingFileName: string, + redirectedReference: ResolvedProjectReference | undefined, + resolutionInfo: ModuleResolutionInfo | undefined, + ) => (ResolvedModuleFull | undefined)[]; const hasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse; if (host.resolveModuleNames) { - actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFileName, reusedNames, redirectedReference, options, containingFile).map(resolved => { - // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. - if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { - return resolved as ResolvedModuleFull; - } - const withExtension = clone(resolved) as ResolvedModuleFull; - withExtension.extension = extensionFromPath(resolved.resolvedFileName); - return withExtension; - }); + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) => + host.resolveModuleNames!( + moduleNames.map(literal => literal.text), + containingFileName, + resolutionInfo?.reusedNames?.map(literal => literal.text), + redirectedReference, + options, + containingFile, + resolutionInfo, + ).map(resolved => { + // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. + if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { + return resolved as ResolvedModuleFull; + } + const withExtension = clone(resolved) as ResolvedModuleFull; + withExtension.extension = extensionFromPath(resolved.resolvedFileName); + return withExtension; + }); moduleResolutionCache = host.getModuleResolutionCache?.(); } else { moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options); - const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; // TODO: GH#18217 - actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, _reusedNames, redirectedReference) => loadWithModeAwareCache(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader); + const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => + resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule; + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) => + loadWithModeAwareCache(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo, loader); } let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[]; @@ -1398,18 +1418,15 @@ namespace ts { } } - function pullDiagnosticsFromCache(names: string[] | readonly FileReference[], containingFile: SourceFile) { + function pullDiagnosticsFromCache(names: readonly StringLiteralLike[] | readonly FileReference[], containingFile: SourceFile) { if (!moduleResolutionCache) return; const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); - const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined; const containingDir = getDirectoryPath(containingFileName); const redirectedReference = getRedirectReferenceForResolution(containingFile); - let i = 0; for (const n of names) { // mimics logic done in the resolution cache, should be resilient to upgrading it to use `FileReference`s for non-type-reference modal lookups to make it rely on the index in the list less - const mode = typeof n === "string" ? getModeForResolutionAtIndex(containingFile, i) : getModeForFileReference(n, containingFileMode); - const name = typeof n === "string" ? n : n.fileName; - i++; + const mode = getResolutionMode(n, containingFile); + const name = getResolutionName(n); // only nonrelative names hit the cache, and, at least as of right now, only nonrelative names can issue diagnostics // (Since diagnostics are only issued via import or export map lookup) // This may totally change if/when the issue of output paths not mapping to input files is fixed in a broader context @@ -1421,13 +1438,13 @@ namespace ts { } } - function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, reusedNames: string[] | undefined): readonly ResolvedModuleFull[] { + function resolveModuleNamesWorker(moduleNames: readonly StringLiteralLike[], containingFile: SourceFile, resolutionInfo: ModuleResolutionInfo | undefined): readonly (ResolvedModuleFull | undefined)[] { if (!moduleNames.length) return emptyArray; const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); const redirectedReference = getRedirectReferenceForResolution(containingFile); tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName }); performance.mark("beforeResolveModule"); - const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, reusedNames, redirectedReference); + const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo); performance.mark("afterResolveModule"); performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule"); tracing?.pop(); @@ -1530,11 +1547,11 @@ namespace ts { return classifiableNames; } - function resolveModuleNamesReusingOldState(moduleNames: string[], file: SourceFile): readonly (ResolvedModuleFull | undefined)[] { + function resolveModuleNamesReusingOldState(moduleNames: readonly StringLiteralLike[], file: SourceFile): readonly (ResolvedModuleFull | undefined)[] { if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, // the best we can do is fallback to the default logic. - return resolveModuleNamesWorker(moduleNames, file, /*reusedNames*/ undefined); + return resolveModuleNamesWorker(moduleNames, file, /*resolutionInfo*/ undefined); } const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); @@ -1548,10 +1565,8 @@ namespace ts { // Since we assume the filesystem does not change during program creation, // it is safe to reuse resolutions from the earlier call. const result: (ResolvedModuleFull | undefined)[] = []; - let i = 0; for (const moduleName of moduleNames) { - const resolvedModule = file.resolvedModules.get(moduleName, getModeForResolutionAtIndex(file, i)); - i++; + const resolvedModule = file.resolvedModules.get(moduleName.text, getModeForUsageLocation(file, moduleName)); result.push(resolvedModule); } return result; @@ -1562,7 +1577,7 @@ namespace ts { // With this information, we can infer some module resolutions without performing resolution. /** An ordered list of module names for which we cannot recover the resolution. */ - let unknownModuleNames: string[] | undefined; + let unknownModuleNames: StringLiteralLike[] | undefined; /** * The indexing of elements in this list matches that of `moduleNames`. * @@ -1573,7 +1588,7 @@ namespace ts { * * ResolvedModuleFull instance: can be reused. */ let result: (ResolvedModuleFull | undefined)[] | undefined; - let reusedNames: string[] | undefined; + let reusedNames: StringLiteralLike[] | undefined; /** A transient placeholder used to mark predicted resolution in the result list. */ const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any; @@ -1581,21 +1596,22 @@ namespace ts { const moduleName = moduleNames[i]; // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions if (file === oldSourceFile && !hasInvalidatedResolutions(oldSourceFile.path)) { - const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, getModeForResolutionAtIndex(oldSourceFile, i)); + const mode = getModeForUsageLocation(file, moduleName); + const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName.text, mode); if (oldResolvedModule) { if (isTraceEnabled(options, host)) { trace(host, oldResolvedModule.packageId ? Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2, - moduleName, + moduleName.text, getNormalizedAbsolutePath(file.originalFileName, currentDirectory), oldResolvedModule.resolvedFileName, oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId) ); } - (result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule; - (reusedNames || (reusedNames = [])).push(moduleName); + (result ??= new Array(moduleNames.length))[i] = oldResolvedModule; + (reusedNames ??= []).push(moduleName); continue; } } @@ -1604,14 +1620,14 @@ namespace ts { // - resolved to an ambient module in the old program whose declaration is in an unmodified file // (so the same module declaration will land in the new program) let resolvesToAmbientModuleInNonModifiedFile = false; - if (contains(file.ambientModuleNames, moduleName)) { + if (contains(file.ambientModuleNames, moduleName.text)) { resolvesToAmbientModuleInNonModifiedFile = true; if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, getNormalizedAbsolutePath(file.originalFileName, currentDirectory)); + trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName.text, getNormalizedAbsolutePath(file.originalFileName, currentDirectory)); } } else { - resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, i); + resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName); } if (resolvesToAmbientModuleInNonModifiedFile) { @@ -1619,12 +1635,12 @@ namespace ts { } else { // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result. - (unknownModuleNames || (unknownModuleNames = [])).push(moduleName); + (unknownModuleNames ??= []).push(moduleName); } } const resolutions = unknownModuleNames && unknownModuleNames.length - ? resolveModuleNamesWorker(unknownModuleNames, file, reusedNames) + ? resolveModuleNamesWorker(unknownModuleNames, file, { names: unknownModuleNames, reusedNames }) : emptyArray; // Combine results of resolutions and predicted results @@ -1654,9 +1670,8 @@ namespace ts { // If we change our policy of rechecking failed lookups on each program create, // we should adjust the value returned here. - function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, index: number): boolean { - if (index >= length(oldSourceFile?.imports) + length(oldSourceFile?.moduleAugmentations)) return false; // mode index out of bounds, don't reuse resolution - const resolutionToFile = getResolvedModule(oldSourceFile, moduleName, oldSourceFile && getModeForResolutionAtIndex(oldSourceFile, index)); + function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: StringLiteralLike): boolean { + const resolutionToFile = getResolvedModule(oldSourceFile, moduleName.text, getModeForUsageLocation(file, moduleName)); const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); if (resolutionToFile && resolvedFile) { // In the old program, we resolved to an ambient module that was in the same @@ -1667,14 +1682,14 @@ namespace ts { } // at least one of declarations should come from non-modified source file - const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName); + const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName.text); if (!unmodifiedFile) { return false; } if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, unmodifiedFile); + trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName.text, unmodifiedFile); } return true; } @@ -1874,7 +1889,7 @@ namespace ts { const moduleNames = getModuleNames(newSourceFile); const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile); // ensure that module resolution results are still correct - const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, oldSourceFile, moduleResolutionIsEqualTo); + const resolutionsChanged = hasChangesInResolutions(moduleNames, newSourceFile, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); if (resolutionsChanged) { structureIsReused = StructureIsReused.SafeModules; newSourceFile.resolvedModules = zipToModeAwareCache(newSourceFile, moduleNames, resolutions); @@ -1885,7 +1900,7 @@ namespace ts { const typesReferenceDirectives = newSourceFile.typeReferenceDirectives; const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile); // ensure that types resolutions are still correct - const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, oldSourceFile, typeDirectiveIsEqualTo); + const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, newSourceFile, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); if (typeReferenceResolutionsChanged) { structureIsReused = StructureIsReused.SafeModules; newSourceFile.resolvedTypeReferenceDirectiveNames = zipToModeAwareCache(newSourceFile, typesReferenceDirectives, typeReferenceResolutions); @@ -3288,7 +3303,7 @@ namespace ts { const optionsForFile = (useSourceOfProjectReferenceRedirect ? getRedirectReferenceForResolution(file)?.commandLine.options : undefined) || options; for (let index = 0; index < moduleNames.length; index++) { const resolution = resolutions[index]; - setResolvedModule(file, moduleNames[index], resolution, getModeForResolutionAtIndex(file, index)); + setResolvedModule(file, moduleNames[index].text, resolution, getModeForUsageLocation(file, moduleNames[index])); if (!resolution) { continue; @@ -4379,11 +4394,11 @@ namespace ts { } } - function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] { - const res = imports.map(i => i.text); + function getModuleNames({ imports, moduleAugmentations }: SourceFile): StringLiteralLike[] { + const res = imports.map(i => i); for (const aug of moduleAugmentations) { if (aug.kind === SyntaxKind.StringLiteral) { - res.push(aug.text); + res.push(aug); } // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6f3cdf1749dc8..6953ebc845c86 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -5,7 +5,14 @@ namespace ts { startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[] | undefined; - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[]; + resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[] | undefined, + redirectedReference: ResolvedProjectReference | undefined, + containingSourceFile: SourceFile | undefined, + resolutionInfo: ModuleResolutionInfo | undefined + ): (ResolvedModuleFull | undefined)[]; getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined; resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[]; @@ -409,6 +416,7 @@ namespace ts { getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName; shouldRetryResolution: (t: T) => boolean; reusedNames?: readonly string[]; + resolutionInfo?: ModuleResolutionInfo; logChanges?: boolean; containingSourceFile?: SourceFile; containingSourceFileMode?: SourceFile["impliedNodeFormat"]; @@ -417,7 +425,7 @@ namespace ts { names, containingFile, redirectedReference, cache, perDirectoryCacheWithRedirects, loader, getResolutionWithResolvedFileName, - shouldRetryResolution, reusedNames, logChanges, containingSourceFile, containingSourceFileMode + shouldRetryResolution, reusedNames, resolutionInfo, logChanges, containingSourceFile, containingSourceFileMode }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { const path = resolutionHost.toPath(containingFile); const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!; @@ -441,15 +449,20 @@ namespace ts { const seenNamesInFile = createModeAwareCache(); let i = 0; - for (const entry of names) { - const name = isString(entry) ? entry : entry.fileName.toLowerCase(); + for (const entry of containingSourceFile && resolutionInfo ? resolutionInfo.names : names) { + const name = !isString(entry) ? getResolutionName(entry) : entry; // Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant // they require calculating the mode for a given import from it's position in the resolution table, since a given // import's syntax may override the file's default mode. // Type references instead supply a `containingSourceFileMode` and a non-string entry which contains // a default file mode override if applicable. - const mode = !isString(entry) ? getModeForFileReference(entry, containingSourceFileMode) : - containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined; + const mode = !isString(entry) ? + isStringLiteralLike(entry) ? + getModeForUsageLocation(containingSourceFile!, entry) : + getModeForFileReference(entry, containingSourceFileMode) : + containingSourceFile ? + getModeForResolutionAtIndex(containingSourceFile, i) : + undefined; i++; let resolution = resolutionsInFile.get(name, mode); // Resolution is valid if it is present and not invalidated @@ -533,13 +546,19 @@ namespace ts { resolvedModules.push(getResolutionWithResolvedFileName(resolution)); } - // Stop watching and remove the unused name - resolutionsInFile.forEach((resolution, name, mode) => { - if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { - stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); - resolutionsInFile.delete(name, mode); - } - }); + if (containingSourceFile && resolutionInfo) { + resolutionInfo.reusedNames?.forEach(literal => seenNamesInFile.set(literal.text, getModeForUsageLocation(containingSourceFile, literal), true)); + reusedNames = undefined; + } + if (resolutionsInFile.size() !== seenNamesInFile.size()) { + // Stop watching and remove the unused name + resolutionsInFile.forEach((resolution, name, mode) => { + if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { + stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); + resolutionsInFile.delete(name, mode); + } + }); + } return resolvedModules; @@ -576,7 +595,14 @@ namespace ts { }); } - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] { + function resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[] | undefined, + redirectedReference?: ResolvedProjectReference, + containingSourceFile?: SourceFile, + resolutionInfo?: ModuleResolutionInfo + ): (ResolvedModuleFull | undefined)[] { return resolveNamesWithLocalCache({ names: moduleNames, containingFile, @@ -587,6 +613,7 @@ namespace ts { getResolutionWithResolvedFileName: getResolvedModule, shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), reusedNames, + resolutionInfo, logChanges: logChangesWhenResolvingModule, containingSourceFile, }); diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index 6b1db6a046cbe..e41fdb2fd0a67 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -306,9 +306,10 @@ namespace ts { const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; const typeReferenceDirectiveResolutionCache = !compilerHost.resolveTypeReferenceDirectives ? createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()) : undefined; if (!compilerHost.resolveModuleNames) { - const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; - compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference, _options, containingSourceFile) => - loadWithModeAwareCache(Debug.checkEachDefined(moduleNames), Debug.checkDefined(containingSourceFile), containingFile, redirectedReference, loader); + const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => + resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule; + compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference, _options, containingSourceFile, resolutionInfo) => + loadWithModeAwareCache(Debug.checkEachDefined(moduleNames), Debug.checkDefined(containingSourceFile), containingFile, redirectedReference, resolutionInfo, loader); compilerHost.getModuleResolutionCache = () => moduleResolutionCache; } if (!compilerHost.resolveTypeReferenceDirectives) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 23862780d174f..9f6cd20edadc6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7193,6 +7193,11 @@ namespace ts { /* @internal */ export type HasChangedAutomaticTypeDirectiveNames = () => boolean; + export interface ModuleResolutionInfo { + names: readonly StringLiteralLike[]; + reusedNames: readonly StringLiteralLike[] | undefined; + } + export interface CompilerHost extends ModuleResolutionHost { getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined; getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined; @@ -7213,7 +7218,7 @@ namespace ts { * If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just * 'throw new Error("NotImplemented")' */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; /** * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0571a9c92df0d..bed7e1fafd806 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -211,19 +211,18 @@ namespace ts { } export function hasChangesInResolutions( - names: readonly string[] | readonly FileReference[], + names: readonly StringLiteralLike[] | readonly FileReference[], + newSourceFile: SourceFile, newResolutions: readonly T[], oldResolutions: ModeAwareCache | undefined, - oldSourceFile: SourceFile | undefined, comparer: (oldResolution: T, newResolution: T) => boolean): boolean { Debug.assert(names.length === newResolutions.length); for (let i = 0; i < names.length; i++) { const newResolution = newResolutions[i]; const entry = names[i]; - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const name = !isString(entry) ? entry.fileName.toLowerCase() : entry; - const mode = !isString(entry) ? getModeForFileReference(entry, oldSourceFile?.impliedNodeFormat) : oldSourceFile && getModeForResolutionAtIndex(oldSourceFile, i); + const name = getResolutionName(entry); + const mode = getResolutionMode(entry, newSourceFile); const oldResolution = oldResolutions && oldResolutions.get(name, mode); const changed = oldResolution diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index c6e083e9d7e1a..e29a548a9f022 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -2072,8 +2072,8 @@ namespace ts { return indentation === MAX_SMI_X86 ? undefined : indentation; } - export function isStringLiteralLike(node: Node): node is StringLiteralLike { - return node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; + export function isStringLiteralLike(node: Node | FileReference): node is StringLiteralLike { + return (node as Node).kind === SyntaxKind.StringLiteral || (node as Node).kind === SyntaxKind.NoSubstitutionTemplateLiteral; } export function isJSDocLinkLike(node: Node): node is JSDocLink | JSDocLinkCode | JSDocLinkPlain { diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 919cf9fd7c6ab..8ce7ccacedde7 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -109,7 +109,7 @@ namespace ts { getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; /** If provided along with custom resolveModuleNames or resolveTypeReferenceDirectives, used to determine if unchanged file path needs to re-resolve modules/type reference directives */ @@ -365,7 +365,7 @@ namespace ts { // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names compilerHost.resolveModuleNames = host.resolveModuleNames ? ((...args) => host.resolveModuleNames!(...args)) : - ((moduleNames, containingFile, reusedNames, redirectedReference, _options, sourceFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, sourceFile)); + ((moduleNames, containingFile, reusedNames, redirectedReference, _options, sourceFile, resolutionInfo) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, sourceFile, resolutionInfo)); compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? ((...args) => host.resolveTypeReferenceDirectives!(...args)) : ((typeDirectiveNames, containingFile, redirectedReference, _options, containingFileMode) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference, containingFileMode)); diff --git a/src/server/project.ts b/src/server/project.ts index 14c98583bd6a7..1c339d4202f53 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -506,8 +506,8 @@ namespace ts.server { return !this.isWatchedMissingFile(path) && this.directoryStructureHost.fileExists(file); } - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] { - return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, containingSourceFile); + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModuleFull | undefined)[] { + return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, containingSourceFile, resolutionInfo); } getModuleResolutionCache(): ModuleResolutionCache | undefined { diff --git a/src/services/types.ts b/src/services/types.ts index 3b63a68a5d30b..fdd692b5b2233 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -284,7 +284,7 @@ namespace ts { * * If this is implemented, `getResolvedModuleWithFailedLookupLocationsFromCache` should be too. */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; resolveTypeReferenceDirectives?(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; /* @internal */ hasInvalidatedResolutions?: HasInvalidatedResolutions; diff --git a/src/testRunner/unittests/tscWatch/moduleResolution.ts b/src/testRunner/unittests/tscWatch/moduleResolution.ts index 1d8d546e92466..8361da8532cbd 100644 --- a/src/testRunner/unittests/tscWatch/moduleResolution.ts +++ b/src/testRunner/unittests/tscWatch/moduleResolution.ts @@ -256,5 +256,75 @@ namespace ts.tscWatch { ], }); }); + + verifyTscWatch({ + scenario: "moduleResolution", + subScenario: "module resolutions from file are partially used", + sys: () => createWatchedSystem([ + { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { moduleResolution: "node16" }, + }) + }, + { + path: `${projectRoot}/index.ts`, + content: Utils.dedent` + import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }; + import type { RequireInterface } from "pkg1" assert { "resolution-mode": "require" }; + import {x} from "./a"; + ` + }, + { + path: `${projectRoot}/a.ts`, + content: Utils.dedent` + export const x = 10; + ` + }, + { + path: `${projectRoot}/node_modules/pkg/package.json`, + content: JSON.stringify({ + name: "pkg", + version: "0.0.1", + exports: { + import: "./import.js", + require: "./require.js" + } + }) + }, + { + path: `${projectRoot}/node_modules/pkg/import.d.ts`, + content: `export interface ImportInterface {}` + }, + { + path: `${projectRoot}/node_modules/pkg/require.d.ts`, + content: `export interface RequireInterface {}` + }, + { + path: `${projectRoot}/node_modules/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "0.0.1", + exports: { + import: "./import.js", + require: "./require.js" + } + }) + }, + { + path: `${projectRoot}/node_modules/pkg1/import.d.ts`, + content: `export interface ImportInterface {}` + }, + libFile + ], { currentDirectory: projectRoot }), + commandLineArgs: ["-w", "--traceResolution"], + changes: [ + { + caption: "modify aFile by adding import", + change: sys => sys.appendFile(`${projectRoot}/a.ts`, `import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }`), + timeouts: sys => sys.runQueuedTimeoutCallbacks(), + } + ] + }); }); } \ No newline at end of file diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ab080e9a81899..a0ffcb7706a95 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3301,6 +3301,10 @@ declare namespace ts { readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; readonly failedLookupLocations: string[]; } + export interface ModuleResolutionInfo { + names: readonly StringLiteralLike[]; + reusedNames: readonly StringLiteralLike[] | undefined; + } export interface CompilerHost extends ModuleResolutionHost { getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined; getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined; @@ -3313,7 +3317,7 @@ declare namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; /** * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it */ @@ -4567,7 +4571,7 @@ declare namespace ts { /** True if has initializer node attached to it. */ function hasOnlyExpressionInitializer(node: Node): node is HasExpressionInitializer; function isObjectLiteralElement(node: Node): node is ObjectLiteralElement; - function isStringLiteralLike(node: Node): node is StringLiteralLike; + function isStringLiteralLike(node: Node | FileReference): node is StringLiteralLike; function isJSDocLinkLike(node: Node): node is JSDocLink | JSDocLinkCode | JSDocLinkPlain; function hasRestParameter(s: SignatureDeclaration | JSDocSignature): boolean; function isRestParameter(node: ParameterDeclaration | JSDocParameterTag): boolean; @@ -5444,7 +5448,7 @@ declare namespace ts { /** If provided is used to get the environment variable */ getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; /** If provided along with custom resolveModuleNames or resolveTypeReferenceDirectives, used to determine if unchanged file path needs to re-resolve modules/type reference directives */ @@ -5851,7 +5855,7 @@ declare namespace ts { readFile(path: string, encoding?: string): string | undefined; fileExists(path: string): boolean; getTypeRootsVersion?(): number; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; resolveTypeReferenceDirectives?(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; getDirectories?(directoryName: string): string[]; @@ -10154,7 +10158,7 @@ declare namespace ts.server { readFile(fileName: string): string | undefined; writeFile(fileName: string, content: string): void; fileExists(file: string): boolean; - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[]; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModuleFull | undefined)[]; getModuleResolutionCache(): ModuleResolutionCache | undefined; getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; resolveTypeReferenceDirectives(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 282d0251bbf90..cc8a0528dd95e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3301,6 +3301,10 @@ declare namespace ts { readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; readonly failedLookupLocations: string[]; } + export interface ModuleResolutionInfo { + names: readonly StringLiteralLike[]; + reusedNames: readonly StringLiteralLike[] | undefined; + } export interface CompilerHost extends ModuleResolutionHost { getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined; getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined; @@ -3313,7 +3317,7 @@ declare namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; /** * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it */ @@ -4567,7 +4571,7 @@ declare namespace ts { /** True if has initializer node attached to it. */ function hasOnlyExpressionInitializer(node: Node): node is HasExpressionInitializer; function isObjectLiteralElement(node: Node): node is ObjectLiteralElement; - function isStringLiteralLike(node: Node): node is StringLiteralLike; + function isStringLiteralLike(node: Node | FileReference): node is StringLiteralLike; function isJSDocLinkLike(node: Node): node is JSDocLink | JSDocLinkCode | JSDocLinkPlain; function hasRestParameter(s: SignatureDeclaration | JSDocSignature): boolean; function isRestParameter(node: ParameterDeclaration | JSDocParameterTag): boolean; @@ -5444,7 +5448,7 @@ declare namespace ts { /** If provided is used to get the environment variable */ getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; /** If provided along with custom resolveModuleNames or resolveTypeReferenceDirectives, used to determine if unchanged file path needs to re-resolve modules/type reference directives */ @@ -5851,7 +5855,7 @@ declare namespace ts { readFile(path: string, encoding?: string): string | undefined; fileExists(path: string): boolean; getTypeRootsVersion?(): number; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; resolveTypeReferenceDirectives?(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; getDirectories?(directoryName: string): string[]; diff --git a/tests/baselines/reference/tscWatch/moduleResolution/module-resolutions-from-file-are-partially-used.js b/tests/baselines/reference/tscWatch/moduleResolution/module-resolutions-from-file-are-partially-used.js new file mode 100644 index 0000000000000..0a25238aa5262 --- /dev/null +++ b/tests/baselines/reference/tscWatch/moduleResolution/module-resolutions-from-file-are-partially-used.js @@ -0,0 +1,328 @@ +Input:: +//// [/user/username/projects/myproject/tsconfig.json] +{"compilerOptions":{"moduleResolution":"node16"}} + +//// [/user/username/projects/myproject/index.ts] +import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }; +import type { RequireInterface } from "pkg1" assert { "resolution-mode": "require" }; +import {x} from "./a"; + + +//// [/user/username/projects/myproject/a.ts] +export const x = 10; + + +//// [/user/username/projects/myproject/node_modules/pkg/package.json] +{"name":"pkg","version":"0.0.1","exports":{"import":"./import.js","require":"./require.js"}} + +//// [/user/username/projects/myproject/node_modules/pkg/import.d.ts] +export interface ImportInterface {} + +//// [/user/username/projects/myproject/node_modules/pkg/require.d.ts] +export interface RequireInterface {} + +//// [/user/username/projects/myproject/node_modules/pkg1/package.json] +{"name":"pkg1","version":"0.0.1","exports":{"import":"./import.js","require":"./require.js"}} + +//// [/user/username/projects/myproject/node_modules/pkg1/import.d.ts] +export interface ImportInterface {} + +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + + +/a/lib/tsc.js -w --traceResolution +Output:: +>> Screen clear +[12:00:39 AM] Starting compilation in watch mode... + +File '/user/username/projects/myproject/package.json' does not exist. +File '/user/username/projects/package.json' does not exist. +File '/user/username/package.json' does not exist. +File '/user/package.json' does not exist. +File '/package.json' does not exist. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +======== Resolving module 'pkg' from '/user/username/projects/myproject/index.ts'. ======== +Explicitly specified module resolution kind: 'Node16'. +Resolving in ESM mode with conditions 'node', 'import', 'types'. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +Loading module 'pkg' from 'node_modules' folder, target file type 'TypeScript'. +Found 'package.json' at '/user/username/projects/myproject/node_modules/pkg/package.json'. +'package.json' does not have a 'typesVersions' field. +Matched 'exports' condition 'import'. +Using 'exports' subpath '.' with target './import.js'. +File name '/user/username/projects/myproject/node_modules/pkg/import.js' has a '.js' extension - stripping it. +File '/user/username/projects/myproject/node_modules/pkg/import.ts' does not exist. +File '/user/username/projects/myproject/node_modules/pkg/import.tsx' does not exist. +File '/user/username/projects/myproject/node_modules/pkg/import.d.ts' exist - use it as a name resolution result. +Resolving real path for '/user/username/projects/myproject/node_modules/pkg/import.d.ts', result '/user/username/projects/myproject/node_modules/pkg/import.d.ts'. +======== Module name 'pkg' was successfully resolved to '/user/username/projects/myproject/node_modules/pkg/import.d.ts' with Package ID 'pkg/import.d.ts@0.0.1'. ======== +======== Resolving module 'pkg1' from '/user/username/projects/myproject/index.ts'. ======== +Explicitly specified module resolution kind: 'Node16'. +Resolving in CJS mode with conditions 'node', 'require', 'types'. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +Loading module 'pkg1' from 'node_modules' folder, target file type 'TypeScript'. +Found 'package.json' at '/user/username/projects/myproject/node_modules/pkg1/package.json'. +'package.json' does not have a 'typesVersions' field. +Saw non-matching condition 'import'. +Matched 'exports' condition 'require'. +Using 'exports' subpath '.' with target './require.js'. +File name '/user/username/projects/myproject/node_modules/pkg1/require.js' has a '.js' extension - stripping it. +File '/user/username/projects/myproject/node_modules/pkg1/require.ts' does not exist. +File '/user/username/projects/myproject/node_modules/pkg1/require.tsx' does not exist. +File '/user/username/projects/myproject/node_modules/pkg1/require.d.ts' does not exist. +Directory '/user/username/projects/myproject/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/user/username/projects/node_modules' does not exist, skipping all lookups in it. +Directory '/user/username/node_modules' does not exist, skipping all lookups in it. +Directory '/user/node_modules' does not exist, skipping all lookups in it. +Directory '/node_modules' does not exist, skipping all lookups in it. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +Loading module 'pkg1' from 'node_modules' folder, target file type 'JavaScript'. +File '/user/username/projects/myproject/node_modules/pkg1/package.json' exists according to earlier cached lookups. +Saw non-matching condition 'import'. +Matched 'exports' condition 'require'. +Using 'exports' subpath '.' with target './require.js'. +File name '/user/username/projects/myproject/node_modules/pkg1/require.js' has a '.js' extension - stripping it. +File '/user/username/projects/myproject/node_modules/pkg1/require.js' does not exist. +File '/user/username/projects/myproject/node_modules/pkg1/require.jsx' does not exist. +Directory '/user/username/projects/node_modules' does not exist, skipping all lookups in it. +Directory '/user/username/node_modules' does not exist, skipping all lookups in it. +Directory '/user/node_modules' does not exist, skipping all lookups in it. +Directory '/node_modules' does not exist, skipping all lookups in it. +======== Module name 'pkg1' was not resolved. ======== +======== Resolving module './a' from '/user/username/projects/myproject/index.ts'. ======== +Explicitly specified module resolution kind: 'Node16'. +Resolving in CJS mode with conditions 'node', 'require', 'types'. +Loading module as file / folder, candidate module location '/user/username/projects/myproject/a', target file type 'TypeScript'. +File '/user/username/projects/myproject/a.ts' exist - use it as a name resolution result. +======== Module name './a' was successfully resolved to '/user/username/projects/myproject/a.ts'. ======== +File '/user/username/projects/myproject/node_modules/pkg/package.json' exists according to earlier cached lookups. +File '/a/lib/package.json' does not exist. +File '/a/package.json' does not exist. +File '/package.json' does not exist according to earlier cached lookups. +index.ts:2:39 - error TS2307: Cannot find module 'pkg1' or its corresponding type declarations. + +2 import type { RequireInterface } from "pkg1" assert { "resolution-mode": "require" }; +   ~~~~~~ + +[12:00:44 AM] Found 1 error. Watching for file changes. + + + +Program root files: ["/user/username/projects/myproject/a.ts","/user/username/projects/myproject/index.ts"] +Program options: {"moduleResolution":3,"watch":true,"traceResolution":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/a.ts +/user/username/projects/myproject/node_modules/pkg/import.d.ts +/user/username/projects/myproject/index.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/a.ts +/user/username/projects/myproject/node_modules/pkg/import.d.ts +/user/username/projects/myproject/index.ts + +Shape signatures in builder refreshed for:: +/a/lib/lib.d.ts (used version) +/user/username/projects/myproject/a.ts (used version) +/user/username/projects/myproject/node_modules/pkg/import.d.ts (used version) +/user/username/projects/myproject/index.ts (used version) + +PolledWatches:: +/user/username/projects/myproject/package.json: + {"pollingInterval":2000} +/user/username/projects/package.json: + {"pollingInterval":2000} +/user/username/projects/myproject/node_modules/@types: + {"pollingInterval":500} + +FsWatches:: +/user/username/projects/myproject/tsconfig.json: + {} +/user/username/projects/myproject/a.ts: + {} +/user/username/projects/myproject/index.ts: + {} +/user/username/projects/myproject/node_modules/pkg/import.d.ts: + {} +/a/lib/lib.d.ts: + {} +/user/username/projects/myproject: + {} +/user/username/projects/myproject/node_modules/pkg/package.json: + {} +/user/username/projects/myproject/node_modules/pkg1/package.json: + {} + +FsWatchesRecursive:: +/user/username/projects/myproject/node_modules: + {} +/user/username/projects/myproject: + {} + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/a.js] +"use strict"; +exports.__esModule = true; +exports.x = void 0; +exports.x = 10; + + +//// [/user/username/projects/myproject/index.js] +"use strict"; +exports.__esModule = true; + + + +Change:: modify aFile by adding import + +Input:: +//// [/user/username/projects/myproject/a.ts] +export const x = 10; +import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" } + + +Output:: +>> Screen clear +[12:00:47 AM] File change detected. Starting incremental compilation... + +File '/a/lib/package.json' does not exist according to earlier cached lookups. +File '/a/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/myproject/node_modules/pkg/package.json' exists according to earlier cached lookups. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +======== Resolving module 'pkg' from '/user/username/projects/myproject/a.ts'. ======== +Explicitly specified module resolution kind: 'Node16'. +Resolving in ESM mode with conditions 'node', 'import', 'types'. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +Loading module 'pkg' from 'node_modules' folder, target file type 'TypeScript'. +File '/user/username/projects/myproject/node_modules/pkg/package.json' exists according to earlier cached lookups. +Matched 'exports' condition 'import'. +Using 'exports' subpath '.' with target './import.js'. +File name '/user/username/projects/myproject/node_modules/pkg/import.js' has a '.js' extension - stripping it. +File '/user/username/projects/myproject/node_modules/pkg/import.ts' does not exist. +File '/user/username/projects/myproject/node_modules/pkg/import.tsx' does not exist. +File '/user/username/projects/myproject/node_modules/pkg/import.d.ts' exist - use it as a name resolution result. +Resolving real path for '/user/username/projects/myproject/node_modules/pkg/import.d.ts', result '/user/username/projects/myproject/node_modules/pkg/import.d.ts'. +======== Module name 'pkg' was successfully resolved to '/user/username/projects/myproject/node_modules/pkg/import.d.ts' with Package ID 'pkg/import.d.ts@0.0.1'. ======== +File '/user/username/projects/myproject/node_modules/pkg/package.json' exists according to earlier cached lookups. +File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups. +File '/user/username/projects/package.json' does not exist according to earlier cached lookups. +File '/user/username/package.json' does not exist according to earlier cached lookups. +File '/user/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +Reusing resolution of module 'pkg' from '/user/username/projects/myproject/index.ts' of old program, it was successfully resolved to '/user/username/projects/myproject/node_modules/pkg/import.d.ts' with Package ID 'pkg/import.d.ts@0.0.1'. +Reusing resolution of module './a' from '/user/username/projects/myproject/index.ts' of old program, it was successfully resolved to '/user/username/projects/myproject/a.ts'. +Reusing resolution of module 'pkg1' from '/user/username/projects/myproject/index.ts' of old program, it was not resolved. +File '/a/lib/package.json' does not exist according to earlier cached lookups. +File '/a/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +index.ts:2:39 - error TS2307: Cannot find module 'pkg1' or its corresponding type declarations. + +2 import type { RequireInterface } from "pkg1" assert { "resolution-mode": "require" }; +   ~~~~~~ + +[12:00:54 AM] Found 1 error. Watching for file changes. + + + +Program root files: ["/user/username/projects/myproject/a.ts","/user/username/projects/myproject/index.ts"] +Program options: {"moduleResolution":3,"watch":true,"traceResolution":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: SafeModules +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/pkg/import.d.ts +/user/username/projects/myproject/a.ts +/user/username/projects/myproject/index.ts + +Semantic diagnostics in builder refreshed for:: +/user/username/projects/myproject/a.ts +/user/username/projects/myproject/index.ts + +Shape signatures in builder refreshed for:: +/user/username/projects/myproject/a.ts (computed .d.ts) +/user/username/projects/myproject/index.ts (computed .d.ts) + +PolledWatches:: +/user/username/projects/myproject/package.json: + {"pollingInterval":2000} +/user/username/projects/package.json: + {"pollingInterval":2000} +/user/username/projects/myproject/node_modules/@types: + {"pollingInterval":500} + +FsWatches:: +/user/username/projects/myproject/tsconfig.json: + {} +/user/username/projects/myproject/a.ts: + {} +/user/username/projects/myproject/index.ts: + {} +/user/username/projects/myproject/node_modules/pkg/import.d.ts: + {} +/a/lib/lib.d.ts: + {} +/user/username/projects/myproject: + {} +/user/username/projects/myproject/node_modules/pkg/package.json: + {} +/user/username/projects/myproject/node_modules/pkg1/package.json: + {} + +FsWatchesRecursive:: +/user/username/projects/myproject/node_modules: + {} +/user/username/projects/myproject: + {} + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/a.js] file written with same contents +//// [/user/username/projects/myproject/index.js] file written with same contents