package module import ( "fmt" "slices" "strings" "sync" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/packagejson" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/semver" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" ) type resolved struct { path string extension string packageId PackageId originalPath string resolvedUsingTsExtension bool } func (r *resolved) shouldContinueSearching() bool { return r == nil } func (r *resolved) isResolved() bool { return r != nil && r.path != "" } func continueSearching() *resolved { return nil } func unresolved() *resolved { return &resolved{} } type resolutionKindSpecificLoader = func(extensions extensions, candidate string, onlyRecordFailures bool) *resolved type tracer struct { traces []string } func (t *tracer) write(msg string) { if t != nil { t.traces = append(t.traces, msg) } } func (t *tracer) getTraces() []string { if t != nil { return t.traces } return nil } type resolutionState struct { resolver *Resolver tracer *tracer // request fields name string containingDirectory string isConfigLookup bool features NodeResolutionFeatures esmMode bool conditions []string extensions extensions compilerOptions *core.CompilerOptions // state fields candidateIsFromPackageJsonField bool resolvedPackageDirectory bool failedLookupLocations []string affectingLocations []string diagnostics []*ast.Diagnostic // Similar to whats on resolver but only done if compilerOptions are for project reference redirect // Cached representation for `core.CompilerOptions.paths`. // Doesn't handle other path patterns like in `typesVersions`. parsedPatternsForPathsOnce sync.Once parsedPatternsForPaths *ParsedPatterns } func newResolutionState( name string, containingDirectory string, isTypeReferenceDirective bool, resolutionMode core.ResolutionMode, compilerOptions *core.CompilerOptions, redirectedReference ResolvedProjectReference, resolver *Resolver, traceBuilder *tracer, ) *resolutionState { state := &resolutionState{ name: name, containingDirectory: containingDirectory, compilerOptions: GetCompilerOptionsWithRedirect(compilerOptions, redirectedReference), resolver: resolver, tracer: traceBuilder, } if isTypeReferenceDirective { state.extensions = extensionsDeclaration } else if compilerOptions.NoDtsResolution == core.TSTrue { state.extensions = extensionsImplementationFiles } else { state.extensions = extensionsTypeScript | extensionsJavaScript | extensionsDeclaration } if !isTypeReferenceDirective && compilerOptions.GetResolveJsonModule() { state.extensions |= extensionsJson } switch compilerOptions.GetModuleResolutionKind() { case core.ModuleResolutionKindNode16: state.features = NodeResolutionFeaturesNode16Default state.esmMode = resolutionMode == core.ModuleKindESNext state.conditions = GetConditions(compilerOptions, resolutionMode) case core.ModuleResolutionKindNodeNext: state.features = NodeResolutionFeaturesNodeNextDefault state.esmMode = resolutionMode == core.ModuleKindESNext state.conditions = GetConditions(compilerOptions, resolutionMode) case core.ModuleResolutionKindBundler: state.features = getNodeResolutionFeatures(compilerOptions) state.conditions = GetConditions(compilerOptions, resolutionMode) } return state } func GetCompilerOptionsWithRedirect(compilerOptions *core.CompilerOptions, redirectedReference ResolvedProjectReference) *core.CompilerOptions { if redirectedReference == nil { return compilerOptions } if optionsFromRedirect := redirectedReference.CompilerOptions(); optionsFromRedirect != nil { return optionsFromRedirect } return compilerOptions } type Resolver struct { caches host ResolutionHost compilerOptions *core.CompilerOptions typingsLocation string projectName string // reportDiagnostic: DiagnosticReporter } func NewResolver( host ResolutionHost, options *core.CompilerOptions, typingsLocation string, projectName string, ) *Resolver { return &Resolver{ host: host, caches: newCaches(host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames(), options), compilerOptions: options, typingsLocation: typingsLocation, projectName: projectName, } } func (r *Resolver) newTraceBuilder() *tracer { if r.compilerOptions.TraceResolution == core.TSTrue { return &tracer{} } return nil } func (r *Resolver) GetPackageScopeForPath(directory string) *packagejson.InfoCacheEntry { return (&resolutionState{compilerOptions: r.compilerOptions, resolver: r}).getPackageScopeForPath(directory) } func (r *Resolver) GetPackageJsonScopeIfApplicable(path string) *packagejson.InfoCacheEntry { if tspath.FileExtensionIsOneOf(path, []string{tspath.ExtensionMts, tspath.ExtensionCts, tspath.ExtensionMjs, tspath.ExtensionCjs}) { return nil } moduleResolutionKind := r.compilerOptions.GetModuleResolutionKind() if core.ModuleResolutionKindNode16 <= moduleResolutionKind && moduleResolutionKind <= core.ModuleResolutionKindNodeNext || strings.Contains(path, "/node_modules/") { return r.GetPackageScopeForPath(tspath.GetDirectoryPath(path)) } return nil } func (r *tracer) traceResolutionUsingProjectReference(redirectedReference ResolvedProjectReference) { if redirectedReference != nil && redirectedReference.CompilerOptions() != nil { r.write(diagnostics.Using_compiler_options_of_project_reference_redirect_0.Format(redirectedReference.ConfigName())) } } func (r *Resolver) ResolveTypeReferenceDirective( typeReferenceDirectiveName string, containingFile string, resolutionMode core.ResolutionMode, redirectedReference ResolvedProjectReference, ) (*ResolvedTypeReferenceDirective, []string) { traceBuilder := r.newTraceBuilder() compilerOptions := GetCompilerOptionsWithRedirect(r.compilerOptions, redirectedReference) containingDirectory := tspath.GetDirectoryPath(containingFile) typeRoots, fromConfig := compilerOptions.GetEffectiveTypeRoots(r.host.GetCurrentDirectory()) if traceBuilder != nil { traceBuilder.write(diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2.Format(typeReferenceDirectiveName, containingFile, strings.Join(typeRoots, ","))) traceBuilder.traceResolutionUsingProjectReference(redirectedReference) } state := newResolutionState(typeReferenceDirectiveName, containingDirectory, true /*isTypeReferenceDirective*/, resolutionMode, compilerOptions, redirectedReference, r, traceBuilder) result := state.resolveTypeReferenceDirective(typeRoots, fromConfig, strings.HasSuffix(containingFile, InferredTypesContainingFile)) if traceBuilder != nil { traceBuilder.traceTypeReferenceDirectiveResult(typeReferenceDirectiveName, result) } return result, traceBuilder.getTraces() } func (r *Resolver) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode, redirectedReference ResolvedProjectReference) (*ResolvedModule, []string) { traceBuilder := r.newTraceBuilder() compilerOptions := GetCompilerOptionsWithRedirect(r.compilerOptions, redirectedReference) if traceBuilder != nil { traceBuilder.write(diagnostics.Resolving_module_0_from_1.Format(moduleName, containingFile)) traceBuilder.traceResolutionUsingProjectReference(redirectedReference) } containingDirectory := tspath.GetDirectoryPath(containingFile) moduleResolution := compilerOptions.ModuleResolution if moduleResolution == core.ModuleResolutionKindUnknown { moduleResolution = compilerOptions.GetModuleResolutionKind() if traceBuilder != nil { traceBuilder.write(diagnostics.Module_resolution_kind_is_not_specified_using_0.Format(moduleResolution.String())) } } else { if traceBuilder != nil { traceBuilder.write(diagnostics.Explicitly_specified_module_resolution_kind_Colon_0.Format(moduleResolution.String())) } } var result *ResolvedModule switch moduleResolution { case core.ModuleResolutionKindNode16, core.ModuleResolutionKindNodeNext, core.ModuleResolutionKindBundler: state := newResolutionState(moduleName, containingDirectory, false /*isTypeReferenceDirective*/, resolutionMode, compilerOptions, redirectedReference, r, traceBuilder) result = state.resolveNodeLike() default: panic(fmt.Sprintf("Unexpected moduleResolution: %d", moduleResolution)) } if traceBuilder != nil { if result.IsResolved() { if result.PackageId.Name != "" { traceBuilder.write(diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2.Format(moduleName, result.ResolvedFileName, result.PackageId.String())) } else { traceBuilder.write(diagnostics.Module_name_0_was_successfully_resolved_to_1.Format(moduleName, result.ResolvedFileName)) } } else { traceBuilder.write(diagnostics.Module_name_0_was_not_resolved.Format(moduleName)) } } return r.tryResolveFromTypingsLocation(moduleName, containingDirectory, result, traceBuilder), traceBuilder.getTraces() } func (r *Resolver) tryResolveFromTypingsLocation(moduleName string, containingDirectory string, originalResult *ResolvedModule, traceBuilder *tracer) *ResolvedModule { if r.typingsLocation == "" || tspath.IsExternalModuleNameRelative(moduleName) || (originalResult.ResolvedFileName != "" && tspath.ExtensionIsOneOf(originalResult.Extension, tspath.SupportedTSExtensionsWithJsonFlat)) { return originalResult } state := newResolutionState( moduleName, containingDirectory, false, /*isTypeReferenceDirective*/ core.ModuleKindNone, // resolutionMode, r.compilerOptions, nil, // redirectedReference, r, traceBuilder, ) if traceBuilder != nil { traceBuilder.write(diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2.Format(r.projectName, moduleName, r.typingsLocation)) } globalResolved := state.loadModuleFromImmediateNodeModulesDirectory(extensionsDeclaration, r.typingsLocation, false) if globalResolved == nil { return originalResult } result := state.createResolvedModule(globalResolved, true) result.FailedLookupLocations = append(originalResult.FailedLookupLocations, result.FailedLookupLocations...) result.AffectingLocations = append(originalResult.AffectingLocations, result.AffectingLocations...) result.ResolutionDiagnostics = append(originalResult.ResolutionDiagnostics, result.ResolutionDiagnostics...) return result } func (r *Resolver) resolveConfig(moduleName string, containingFile string) *ResolvedModule { containingDirectory := tspath.GetDirectoryPath(containingFile) state := newResolutionState(moduleName, containingDirectory, false /*isTypeReferenceDirective*/, core.ModuleKindCommonJS, r.compilerOptions, nil, r, nil) state.isConfigLookup = true state.extensions = extensionsJson return state.resolveNodeLike() } func (r *tracer) traceTypeReferenceDirectiveResult(typeReferenceDirectiveName string, result *ResolvedTypeReferenceDirective) { if !result.IsResolved() { r.write(diagnostics.Type_reference_directive_0_was_not_resolved.Format(typeReferenceDirectiveName)) } else if result.PackageId.Name != "" { r.write(diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3.Format( typeReferenceDirectiveName, result.ResolvedFileName, result.PackageId.String(), result.Primary, )) } else { r.write(diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2.Format( typeReferenceDirectiveName, result.ResolvedFileName, result.Primary, )) } } func (r *resolutionState) resolveTypeReferenceDirective(typeRoots []string, fromConfig bool, fromInferredTypesContainingFile bool) *ResolvedTypeReferenceDirective { // Primary lookup if len(typeRoots) > 0 { if r.tracer != nil { r.tracer.write(diagnostics.Resolving_with_primary_search_path_0.Format(strings.Join(typeRoots, ", "))) } for _, typeRoot := range typeRoots { candidate := r.getCandidateFromTypeRoot(typeRoot) directoryExists := r.resolver.host.FS().DirectoryExists(candidate) if !directoryExists && r.tracer != nil { r.tracer.write(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(typeRoot)) } if fromConfig { // Custom typeRoots resolve as file or directory just like we do modules if resolvedFromFile := r.loadModuleFromFile(extensionsDeclaration, candidate, !directoryExists); !resolvedFromFile.shouldContinueSearching() { packageDirectory := ParseNodeModuleFromPath(resolvedFromFile.path, false) if packageDirectory != "" { resolvedFromFile.packageId = r.getPackageId(resolvedFromFile.path, r.getPackageJsonInfo(packageDirectory, false /*onlyRecordFailures*/)) } return r.createResolvedTypeReferenceDirective(resolvedFromFile, true /*primary*/) } } if resolvedFromDirectory := r.loadNodeModuleFromDirectory(extensionsDeclaration, candidate, !directoryExists, true /*considerPackageJson*/); !resolvedFromDirectory.shouldContinueSearching() { return r.createResolvedTypeReferenceDirective(resolvedFromDirectory, true /*primary*/) } } } else if r.tracer != nil { r.tracer.write(diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths.Format()) } // Secondary lookup var resolved *resolved if !fromConfig || !fromInferredTypesContainingFile { if r.tracer != nil { r.tracer.write(diagnostics.Looking_up_in_node_modules_folder_initial_location_0.Format(r.containingDirectory)) } if !tspath.IsExternalModuleNameRelative(r.name) { resolved = r.loadModuleFromNearestNodeModulesDirectory(false /*typesScopeOnly*/) } else { candidate := normalizePathForCJSResolution(r.containingDirectory, r.name) resolved = r.nodeLoadModuleByRelativeName(extensionsDeclaration, candidate, false /*onlyRecordFailures*/, true /*considerPackageJson*/) } } else if r.tracer != nil { r.tracer.write(diagnostics.Resolving_type_reference_directive_for_program_that_specifies_custom_typeRoots_skipping_lookup_in_node_modules_folder.Format()) } return r.createResolvedTypeReferenceDirective(resolved, false /*primary*/) } func (r *resolutionState) getCandidateFromTypeRoot(typeRoot string) string { nameForLookup := r.name if strings.HasSuffix(typeRoot, "/node_modules/@types") || strings.HasSuffix(typeRoot, "/node_modules/@types/") { nameForLookup = r.mangleScopedPackageName(r.name) } return tspath.CombinePaths(typeRoot, nameForLookup) } func (r *resolutionState) mangleScopedPackageName(name string) string { mangled := MangleScopedPackageName(name) if r.tracer != nil && mangled != name { r.tracer.write(diagnostics.Scoped_package_detected_looking_in_0.Format(mangled)) } return mangled } func (r *resolutionState) getPackageScopeForPath(directory string) *packagejson.InfoCacheEntry { result, _ := tspath.ForEachAncestorDirectory( directory, func(directory string) (*packagejson.InfoCacheEntry, bool) { // !!! stop at global cache if result := r.getPackageJsonInfo(directory, false /*onlyRecordFailures*/); result != nil { return result, true } return nil, false }, ) return result } func (r *resolutionState) resolveNodeLike() *ResolvedModule { if r.tracer != nil { conditions := strings.Join(core.Map(r.conditions, func(c string) string { return `'` + c + `'` }), ", ") if r.esmMode { r.tracer.write(diagnostics.Resolving_in_0_mode_with_conditions_1.Format("ESM", conditions)) } else { r.tracer.write(diagnostics.Resolving_in_0_mode_with_conditions_1.Format("CJS", conditions)) } } result := r.resolveNodeLikeWorker() if r.resolvedPackageDirectory && !r.isConfigLookup && r.features&NodeResolutionFeaturesExports != 0 && r.extensions&(extensionsTypeScript|extensionsDeclaration) != 0 && !tspath.IsExternalModuleNameRelative(r.name) && result.IsResolved() && result.IsExternalLibraryImport && !extensionIsOk(extensionsTypeScript|extensionsDeclaration, result.Extension) && slices.Contains(r.conditions, "import") { if r.tracer != nil { r.tracer.write(diagnostics.Resolution_of_non_relative_name_failed_trying_with_modern_Node_resolution_features_disabled_to_see_if_npm_library_needs_configuration_update.Format()) } r.features = r.features & ^NodeResolutionFeaturesExports r.extensions = r.extensions & (extensionsTypeScript | extensionsDeclaration) diagnosticsCount := len(r.diagnostics) if diagnosticResult := r.resolveNodeLikeWorker(); diagnosticResult.IsResolved() && diagnosticResult.IsExternalLibraryImport { result.AlternateResult = diagnosticResult.ResolvedFileName } r.diagnostics = r.diagnostics[:diagnosticsCount] } return result } func (r *resolutionState) resolveNodeLikeWorker() *ResolvedModule { if resolved := r.tryLoadModuleUsingOptionalResolutionSettings(); !resolved.shouldContinueSearching() { return r.createResolvedModuleHandlingSymlink(resolved) } if !tspath.IsExternalModuleNameRelative(r.name) { if r.features&NodeResolutionFeaturesImports != 0 && strings.HasPrefix(r.name, "#") { if resolved := r.loadModuleFromImports(); !resolved.shouldContinueSearching() { return r.createResolvedModuleHandlingSymlink(resolved) } } if r.features&NodeResolutionFeaturesSelfName != 0 { if resolved := r.loadModuleFromSelfNameReference(); !resolved.shouldContinueSearching() { return r.createResolvedModuleHandlingSymlink(resolved) } } if strings.Contains(r.name, ":") { if r.tracer != nil { r.tracer.write(diagnostics.Skipping_module_0_that_looks_like_an_absolute_URI_target_file_types_Colon_1.Format(r.name, r.extensions.String())) } return r.createResolvedModule(nil, false) } if r.tracer != nil { r.tracer.write(diagnostics.Loading_module_0_from_node_modules_folder_target_file_types_Colon_1.Format(r.name, r.extensions.String())) } if resolved := r.loadModuleFromNearestNodeModulesDirectory(false /*typesScopeOnly*/); !resolved.shouldContinueSearching() { return r.createResolvedModuleHandlingSymlink(resolved) } if r.extensions&extensionsDeclaration != 0 { // !!! // if resolved := r.resolveFromTypeRoot(); !resolved.shouldContinueSearching() { // return r.createResolvedModuleHandlingSymlink(resolved) // } } } else { candidate := normalizePathForCJSResolution(r.containingDirectory, r.name) resolved := r.nodeLoadModuleByRelativeName(r.extensions, candidate, false, true) return r.createResolvedModule( resolved, resolved != nil && strings.Contains(resolved.path, "/node_modules/"), ) } return r.createResolvedModule(nil, false) } func (r *resolutionState) loadModuleFromSelfNameReference() *resolved { directoryPath := tspath.GetNormalizedAbsolutePath(r.containingDirectory, r.resolver.host.GetCurrentDirectory()) scope := r.getPackageScopeForPath(directoryPath) if !scope.Exists() || scope.Contents.Exports.IsFalsy() { // !!! falsy check seems wrong? return continueSearching() } name, ok := scope.Contents.Name.GetValue() if !ok { return continueSearching() } parts := tspath.GetPathComponents(r.name, "") nameParts := tspath.GetPathComponents(name, "") if len(parts) < len(nameParts) || !slices.Equal(nameParts, parts[:len(nameParts)]) { return continueSearching() } trailingParts := parts[len(nameParts):] var subpath string if len(trailingParts) > 0 { subpath = tspath.CombinePaths(".", trailingParts...) } else { subpath = "." } // Maybe TODO: splitting extensions into two priorities should be unnecessary, except // https://github.com/microsoft/TypeScript/issues/50762 makes the behavior different. // As long as that bug exists, we need to do two passes here in self-name loading // in order to be consistent with (non-self) library-name loading in // `loadModuleFromNearestNodeModulesDirectoryWorker`, which uses two passes in order // to prioritize `@types` packages higher up the directory tree over untyped // implementation packages. See the selfNameModuleAugmentation.ts test for why this // matters. // // However, there's an exception. If the user has `allowJs` and `declaration`, we need // to ensure that self-name imports of their own package can resolve back to their // input JS files via `tryLoadInputFileForPath` at a higher priority than their output // declaration files, so we need to do a single pass with all extensions for that case. if r.compilerOptions.GetAllowJS() && !strings.Contains(r.containingDirectory, "/node_modules/") { return r.loadModuleFromExports(scope, r.extensions, subpath) } priorityExtensions := r.extensions & (extensionsTypeScript | extensionsDeclaration) secondaryExtensions := r.extensions & ^(extensionsTypeScript | extensionsDeclaration) if resolved := r.loadModuleFromExports(scope, priorityExtensions, subpath); !resolved.shouldContinueSearching() { return resolved } return r.loadModuleFromExports(scope, secondaryExtensions, subpath) } func (r *resolutionState) loadModuleFromImports() *resolved { if r.name == "#" || strings.HasPrefix(r.name, "#/") { if r.tracer != nil { r.tracer.write(diagnostics.Invalid_import_specifier_0_has_no_possible_resolutions.Format(r.name)) } return continueSearching() } directoryPath := tspath.GetNormalizedAbsolutePath(r.containingDirectory, r.resolver.host.GetCurrentDirectory()) scope := r.getPackageScopeForPath(directoryPath) if !scope.Exists() { if r.tracer != nil { r.tracer.write(diagnostics.Directory_0_has_no_containing_package_json_scope_Imports_will_not_resolve.Format(directoryPath)) } return continueSearching() } if scope.Contents.Imports.Type != packagejson.JSONValueTypeObject { // !!! Old compiler only checks for undefined, but then assumes `imports` is an object if present. // Maybe should have a new diagnostic for imports of an invalid type. Also, array should be handled? if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_has_no_imports_defined.Format(scope.PackageDirectory)) } return continueSearching() } if result := r.loadModuleFromExportsOrImports(r.extensions, r.name, scope.Contents.Imports.AsObject(), scope /*isImports*/, true); !result.shouldContinueSearching() { return result } if r.tracer != nil { r.tracer.write(diagnostics.Import_specifier_0_does_not_exist_in_package_json_scope_at_path_1.Format(r.name, scope.PackageDirectory)) } return continueSearching() } func (r *resolutionState) loadModuleFromExports(packageInfo *packagejson.InfoCacheEntry, ext extensions, subpath string) *resolved { // !!! This is ported exactly, but the falsy check seems wrong if !packageInfo.Exists() || packageInfo.Contents.Exports.IsFalsy() { return continueSearching() } if subpath == "." { var mainExport packagejson.ExportsOrImports switch packageInfo.Contents.Exports.Type { case packagejson.JSONValueTypeString, packagejson.JSONValueTypeArray: mainExport = packageInfo.Contents.Exports case packagejson.JSONValueTypeObject: if packageInfo.Contents.Exports.IsConditions() { mainExport = packageInfo.Contents.Exports } else if dot, ok := packageInfo.Contents.Exports.AsObject().Get("."); ok { mainExport = dot } } if mainExport.Type != packagejson.JSONValueTypeNotPresent { return r.loadModuleFromTargetExportOrImport(ext, subpath, packageInfo, false /*isImports*/, mainExport, "", false /*isPattern*/, ".") } } else if packageInfo.Contents.Exports.Type == packagejson.JSONValueTypeObject && packageInfo.Contents.Exports.IsSubpaths() { if result := r.loadModuleFromExportsOrImports(ext, subpath, packageInfo.Contents.Exports.AsObject(), packageInfo, false /*isImports*/); !result.shouldContinueSearching() { return result } } if r.tracer != nil { r.tracer.write(diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1.Format(subpath, packageInfo.PackageDirectory)) } return continueSearching() } func (r *resolutionState) loadModuleFromExportsOrImports( extensions extensions, moduleName string, lookupTable *collections.OrderedMap[string, packagejson.ExportsOrImports], scope *packagejson.InfoCacheEntry, isImports bool, ) *resolved { if !strings.HasSuffix(moduleName, "/") && !strings.Contains(moduleName, "*") { if target, ok := lookupTable.Get(moduleName); ok { return r.loadModuleFromTargetExportOrImport(extensions, moduleName, scope, isImports, target, "", false /*isPattern*/, moduleName) } } expandingKeys := make([]string, 0, lookupTable.Size()) for key := range lookupTable.Keys() { if strings.Count(key, "*") == 1 || strings.HasSuffix(key, "/") { expandingKeys = append(expandingKeys, key) } } slices.SortFunc(expandingKeys, ComparePatternKeys) for _, potentialTarget := range expandingKeys { if r.features&NodeResolutionFeaturesExportsPatternTrailers != 0 && matchesPatternWithTrailer(potentialTarget, moduleName) { target, _ := lookupTable.Get(potentialTarget) starPos := strings.Index(potentialTarget, "*") subpath := moduleName[len(potentialTarget[:starPos]) : len(moduleName)-(len(potentialTarget)-1-starPos)] return r.loadModuleFromTargetExportOrImport(extensions, moduleName, scope, isImports, target, subpath, true, potentialTarget) } else if strings.HasSuffix(potentialTarget, "*") && strings.HasPrefix(moduleName, potentialTarget[:len(potentialTarget)-1]) { target, _ := lookupTable.Get(potentialTarget) subpath := moduleName[len(potentialTarget)-1:] return r.loadModuleFromTargetExportOrImport(extensions, moduleName, scope, isImports, target, subpath, true, potentialTarget) } else if strings.HasPrefix(moduleName, potentialTarget) { target, _ := lookupTable.Get(potentialTarget) subpath := moduleName[len(potentialTarget):] return r.loadModuleFromTargetExportOrImport(extensions, moduleName, scope, isImports, target, subpath, false, potentialTarget) } } return continueSearching() } func (r *resolutionState) loadModuleFromTargetExportOrImport(extensions extensions, moduleName string, scope *packagejson.InfoCacheEntry, isImports bool, target packagejson.ExportsOrImports, subpath string, isPattern bool, key string) *resolved { switch target.Type { case packagejson.JSONValueTypeString: targetString, _ := target.Value.(string) if !isPattern && len(subpath) > 0 && !strings.HasSuffix(targetString, "/") { if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_has_invalid_type_for_target_of_specifier_1.Format(scope.PackageDirectory, moduleName)) } return continueSearching() } if !strings.HasPrefix(targetString, "./") { if isImports && !strings.HasPrefix(targetString, "../") && !strings.HasPrefix(targetString, "/") && !tspath.IsRootedDiskPath(targetString) { combinedLookup := targetString + subpath if isPattern { combinedLookup = strings.ReplaceAll(targetString, "*", subpath) } if r.tracer != nil { r.tracer.write(diagnostics.Using_0_subpath_1_with_target_2.Format("imports", key, combinedLookup)) r.tracer.write(diagnostics.Resolving_module_0_from_1.Format(combinedLookup, scope.PackageDirectory+"/")) } name, containingDirectory := r.name, r.containingDirectory r.name, r.containingDirectory = combinedLookup, scope.PackageDirectory+"/" defer func() { r.name, r.containingDirectory = name, containingDirectory }() if result := r.resolveNodeLike(); result.IsResolved() { return &resolved{ path: result.ResolvedFileName, extension: result.Extension, packageId: result.PackageId, originalPath: result.OriginalPath, resolvedUsingTsExtension: result.ResolvedUsingTsExtension, } } return continueSearching() } if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_has_invalid_type_for_target_of_specifier_1.Format(scope.PackageDirectory, moduleName)) } return continueSearching() } var parts []string if tspath.PathIsRelative(targetString) { parts = tspath.GetPathComponents(targetString, "")[1:] } else { parts = tspath.GetPathComponents(targetString, "") } partsAfterFirst := parts[1:] if slices.Contains(partsAfterFirst, "..") || slices.Contains(partsAfterFirst, ".") || slices.Contains(partsAfterFirst, "node_modules") { if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_has_invalid_type_for_target_of_specifier_1.Format(scope.PackageDirectory, moduleName)) } return continueSearching() } resolvedTarget := tspath.CombinePaths(scope.PackageDirectory, targetString) // TODO: Assert that `resolvedTarget` is actually within the package directory? That's what the spec says.... but I'm not sure we need // to be in the business of validating everyone's import and export map correctness. subpathParts := tspath.GetPathComponents(subpath, "") if slices.Contains(subpathParts, "..") || slices.Contains(subpathParts, ".") || slices.Contains(subpathParts, "node_modules") { if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_has_invalid_type_for_target_of_specifier_1.Format(scope.PackageDirectory, moduleName)) } return continueSearching() } if r.tracer != nil { var messageTarget string if isPattern { messageTarget = strings.ReplaceAll(targetString, "*", subpath) } else { messageTarget = targetString + subpath } r.tracer.write(diagnostics.Using_0_subpath_1_with_target_2.Format(core.IfElse(isImports, "imports", "exports"), key, messageTarget)) } var finalPath string if isPattern { finalPath = tspath.GetNormalizedAbsolutePath(strings.ReplaceAll(resolvedTarget, "*", subpath), r.resolver.host.GetCurrentDirectory()) } else { finalPath = tspath.GetNormalizedAbsolutePath(resolvedTarget+subpath, r.resolver.host.GetCurrentDirectory()) } if inputLink := r.tryLoadInputFileForPath(finalPath, subpath, tspath.CombinePaths(scope.PackageDirectory, "package.json"), isImports); !inputLink.shouldContinueSearching() { return inputLink } if result := r.loadFileNameFromPackageJSONField(extensions, finalPath, targetString, false /*onlyRecordFailures*/); !result.shouldContinueSearching() { result.packageId = r.getPackageId(result.path, scope) return result } return continueSearching() case packagejson.JSONValueTypeObject: if r.tracer != nil { r.tracer.write(diagnostics.Entering_conditional_exports.Format()) } for condition := range target.AsObject().Keys() { if r.conditionMatches(condition) { if r.tracer != nil { r.tracer.write(diagnostics.Matched_0_condition_1.Format(core.IfElse(isImports, "imports", "exports"), condition)) } subTarget, _ := target.AsObject().Get(condition) if result := r.loadModuleFromTargetExportOrImport(extensions, moduleName, scope, isImports, subTarget, subpath, isPattern, key); !result.shouldContinueSearching() { if r.tracer != nil { r.tracer.write(diagnostics.Resolved_under_condition_0.Format(condition)) } if r.tracer != nil { r.tracer.write(diagnostics.Exiting_conditional_exports.Format()) } return result } else if r.tracer != nil { r.tracer.write(diagnostics.Failed_to_resolve_under_condition_0.Format(condition)) } } else { if r.tracer != nil { r.tracer.write(diagnostics.Saw_non_matching_condition_0.Format(condition)) } } } if r.tracer != nil { r.tracer.write(diagnostics.Exiting_conditional_exports.Format()) } return continueSearching() case packagejson.JSONValueTypeArray: if len(target.AsArray()) == 0 { if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_has_invalid_type_for_target_of_specifier_1.Format(scope.PackageDirectory, moduleName)) } return continueSearching() } for _, elem := range target.AsArray() { if result := r.loadModuleFromTargetExportOrImport(extensions, moduleName, scope, isImports, elem, subpath, isPattern, key); !result.shouldContinueSearching() { return result } } case packagejson.JSONValueTypeNull: if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_explicitly_maps_specifier_1_to_null.Format(scope.PackageDirectory, moduleName)) } return continueSearching() } if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_scope_0_has_invalid_type_for_target_of_specifier_1.Format(scope.PackageDirectory, moduleName)) } return continueSearching() } func (r *resolutionState) tryLoadInputFileForPath(finalPath string, entry string, packagePath string, isImports bool) *resolved { // Replace any references to outputs for files in the program with the input files to support package self-names used with outDir if !r.isConfigLookup && (r.compilerOptions.DeclarationDir != "" || r.compilerOptions.OutDir != "") && !strings.Contains(finalPath, "/node_modules/") && (r.compilerOptions.ConfigFilePath == "" || tspath.ContainsPath( tspath.GetDirectoryPath(packagePath), r.compilerOptions.ConfigFilePath, tspath.ComparePathsOptions{ UseCaseSensitiveFileNames: r.resolver.host.FS().UseCaseSensitiveFileNames(), CurrentDirectory: r.resolver.host.GetCurrentDirectory(), }, )) { // Note: this differs from Strada's tryLoadInputFileForPath in that it // does not attempt to perform "guesses", instead requring a clear root indicator. var rootDir string if r.compilerOptions.RootDir != "" { // A `rootDir` compiler option strongly indicates the root location rootDir = r.compilerOptions.RootDir } else if r.compilerOptions.Composite.IsTrue() && r.compilerOptions.ConfigFilePath != "" { // A `composite` project is using project references and has it's common src dir set to `.`, so it shouldn't need to check any other locations rootDir = r.compilerOptions.ConfigFilePath } else { diagnostic := ast.NewDiagnostic( nil, core.TextRange{}, core.IfElse(isImports, diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_import_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate, diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_export_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate, ), core.IfElse(entry == "", ".", entry), // replace empty string with `.` - the reverse of the operation done when entries are built - so main entrypoint errors don't look weird packagePath, ) r.diagnostics = append(r.diagnostics, diagnostic) return unresolved() } candidateDirectories := r.getOutputDirectoriesForBaseDirectory(rootDir) for _, candidateDir := range candidateDirectories { if tspath.ContainsPath(candidateDir, finalPath, tspath.ComparePathsOptions{ UseCaseSensitiveFileNames: r.resolver.host.FS().UseCaseSensitiveFileNames(), CurrentDirectory: r.resolver.host.GetCurrentDirectory(), }) { // The matched export is looking up something in either the out declaration or js dir, now map the written path back into the source dir and source extension pathFragment := finalPath[len(candidateDir)+1:] // +1 to also remove directory separator possibleInputBase := tspath.CombinePaths(rootDir, pathFragment) jsAndDtsExtensions := []string{tspath.ExtensionMjs, tspath.ExtensionCjs, tspath.ExtensionJs, tspath.ExtensionJson, tspath.ExtensionDmts, tspath.ExtensionDcts, tspath.ExtensionDts} for _, ext := range jsAndDtsExtensions { if tspath.FileExtensionIs(possibleInputBase, ext) { inputExts := r.getPossibleOriginalInputExtensionForExtension(possibleInputBase) for _, possibleExt := range inputExts { if !extensionIsOk(r.extensions, possibleExt) { continue } possibleInputWithInputExtension := tspath.ChangeExtension(possibleInputBase, possibleExt) if r.resolver.host.FS().FileExists(possibleInputWithInputExtension) { resolved := r.loadFileNameFromPackageJSONField(r.extensions, possibleInputWithInputExtension, "", false) if !resolved.shouldContinueSearching() { return resolved } } } } } } } } return continueSearching() } func (r *resolutionState) getOutputDirectoriesForBaseDirectory(commonSourceDirGuess string) []string { // Config file output paths are processed to be relative to the host's current directory, while // otherwise the paths are resolved relative to the common source dir the compiler puts together currentDir := core.IfElse(r.compilerOptions.ConfigFilePath != "", r.resolver.host.GetCurrentDirectory(), commonSourceDirGuess) var candidateDirectories []string if r.compilerOptions.DeclarationDir != "" { candidateDirectories = append(candidateDirectories, tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(currentDir, r.compilerOptions.DeclarationDir), r.resolver.host.GetCurrentDirectory())) } if r.compilerOptions.OutDir != "" && r.compilerOptions.OutDir != r.compilerOptions.DeclarationDir { candidateDirectories = append(candidateDirectories, tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(currentDir, r.compilerOptions.OutDir), r.resolver.host.GetCurrentDirectory())) } return candidateDirectories } func (r *resolutionState) getPossibleOriginalInputExtensionForExtension(path string) []string { if tspath.FileExtensionIsOneOf(path, []string{tspath.ExtensionDmts, tspath.ExtensionMjs, tspath.ExtensionMts}) { return []string{tspath.ExtensionMts, tspath.ExtensionMjs} } if tspath.FileExtensionIsOneOf(path, []string{tspath.ExtensionDcts, tspath.ExtensionCjs, tspath.ExtensionCts}) { return []string{tspath.ExtensionCts, tspath.ExtensionCjs} } if tspath.FileExtensionIs(path, ".d.json.ts") { return []string{tspath.ExtensionJson} } return []string{tspath.ExtensionTsx, tspath.ExtensionTs, tspath.ExtensionJsx, tspath.ExtensionJs} } func (r *resolutionState) loadModuleFromNearestNodeModulesDirectory(typesScopeOnly bool) *resolved { mode := core.ResolutionModeCommonJS if r.esmMode || r.conditionMatches("import") { mode = core.ResolutionModeESM } // Do (up to) two passes through node_modules: // 1. For each ancestor node_modules directory, try to find: // i. TS/DTS files in the implementation package // ii. DTS files in the @types package // 2. For each ancestor node_modules directory, try to find: // i. JS files in the implementation package priorityExtensions := r.extensions & (extensionsTypeScript | extensionsDeclaration) secondaryExtensions := r.extensions & ^(extensionsTypeScript | extensionsDeclaration) // (1) if priorityExtensions != 0 { if r.tracer != nil { r.tracer.write(diagnostics.Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0.Format(priorityExtensions.String())) } if result := r.loadModuleFromNearestNodeModulesDirectoryWorker(priorityExtensions, mode, typesScopeOnly); !result.shouldContinueSearching() { return result } } // (2) if secondaryExtensions != 0 && !typesScopeOnly { if r.tracer != nil { r.tracer.write(diagnostics.Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0.Format(secondaryExtensions.String())) } return r.loadModuleFromNearestNodeModulesDirectoryWorker(secondaryExtensions, mode, typesScopeOnly) } return continueSearching() } func (r *resolutionState) loadModuleFromNearestNodeModulesDirectoryWorker(ext extensions, mode core.ResolutionMode, typesScopeOnly bool) *resolved { result, _ := tspath.ForEachAncestorDirectory( r.containingDirectory, func(directory string) (result *resolved, stop bool) { // !!! stop at global cache if tspath.GetBaseFileName(directory) != "node_modules" { result := r.loadModuleFromImmediateNodeModulesDirectory(ext, directory, typesScopeOnly) return result, !result.shouldContinueSearching() } return continueSearching(), false }, ) return result } func (r *resolutionState) loadModuleFromImmediateNodeModulesDirectory(extensions extensions, directory string, typesScopeOnly bool) *resolved { nodeModulesFolder := tspath.CombinePaths(directory, "node_modules") nodeModulesFolderExists := r.resolver.host.FS().DirectoryExists(nodeModulesFolder) if !nodeModulesFolderExists && r.tracer != nil { r.tracer.write(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(nodeModulesFolder)) } if !typesScopeOnly { if packageResult := r.loadModuleFromSpecificNodeModulesDirectory(extensions, r.name, nodeModulesFolder, nodeModulesFolderExists); !packageResult.shouldContinueSearching() { return packageResult } } if extensions&extensionsDeclaration != 0 { nodeModulesAtTypes := tspath.CombinePaths(nodeModulesFolder, "@types") nodeModulesAtTypesExists := nodeModulesFolderExists && r.resolver.host.FS().DirectoryExists(nodeModulesAtTypes) if !nodeModulesAtTypesExists && r.tracer != nil { r.tracer.write(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(nodeModulesAtTypes)) } return r.loadModuleFromSpecificNodeModulesDirectory(extensionsDeclaration, r.mangleScopedPackageName(r.name), nodeModulesAtTypes, nodeModulesAtTypesExists) } return continueSearching() } func (r *resolutionState) loadModuleFromSpecificNodeModulesDirectory(ext extensions, moduleName string, nodeModulesDirectory string, nodeModulesDirectoryExists bool) *resolved { candidate := tspath.NormalizePath(tspath.CombinePaths(nodeModulesDirectory, moduleName)) packageName, rest := ParsePackageName(moduleName) packageDirectory := tspath.CombinePaths(nodeModulesDirectory, packageName) var rootPackageInfo *packagejson.InfoCacheEntry // First look for a nested package.json, as in `node_modules/foo/bar/package.json` packageInfo := r.getPackageJsonInfo(candidate, !nodeModulesDirectoryExists) // But only if we're not respecting export maps (if we are, we might redirect around this location) if rest != "" && packageInfo.Exists() { if r.features&NodeResolutionFeaturesExports != 0 { rootPackageInfo = r.getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists) } if !rootPackageInfo.Exists() || rootPackageInfo.Contents.Exports.Type == packagejson.JSONValueTypeNotPresent { if fromFile := r.loadModuleFromFile(ext, candidate, !nodeModulesDirectoryExists); !fromFile.shouldContinueSearching() { return fromFile } if fromDirectory := r.loadNodeModuleFromDirectoryWorker(ext, candidate, !nodeModulesDirectoryExists, packageInfo); !fromDirectory.shouldContinueSearching() { fromDirectory.packageId = r.getPackageId(packageDirectory, packageInfo) return fromDirectory } } } loader := func(extensions extensions, candidate string, onlyRecordFailures bool) *resolved { if rest != "" || !r.esmMode { if fromFile := r.loadModuleFromFile(extensions, candidate, onlyRecordFailures); !fromFile.shouldContinueSearching() { fromFile.packageId = r.getPackageId(packageDirectory, packageInfo) return fromFile } } if fromDirectory := r.loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, packageInfo); !fromDirectory.shouldContinueSearching() { fromDirectory.packageId = r.getPackageId(packageDirectory, packageInfo) return fromDirectory } // !!! this is ported exactly, but checking for null seems wrong? if rest == "" && packageInfo.Exists() && (packageInfo.Contents.Exports.Type == packagejson.JSONValueTypeNotPresent || packageInfo.Contents.Exports.Type == packagejson.JSONValueTypeNull) && r.esmMode { // EsmMode disables index lookup in `loadNodeModuleFromDirectoryWorker` generally, however non-relative package resolutions still assume // a default `index.js` entrypoint if no `main` or `exports` are present if indexResult := r.loadModuleFromFile(extensions, tspath.CombinePaths(candidate, "index.js"), onlyRecordFailures); !indexResult.shouldContinueSearching() { indexResult.packageId = r.getPackageId(packageDirectory, packageInfo) return indexResult } } return continueSearching() } if rest != "" { packageInfo = rootPackageInfo if packageInfo == nil { // Previous `packageInfo` may have been from a nested package.json; ensure we have the one from the package root now. packageInfo = r.getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists) } } if packageInfo != nil { r.resolvedPackageDirectory = true if r.features&NodeResolutionFeaturesExports != 0 && packageInfo.Exists() && packageInfo.Contents.Exports.Type != packagejson.JSONValueTypeNotPresent { // package exports are higher priority than file/directory/typesVersions lookups and (and, if there's exports present, blocks them) return r.loadModuleFromExports(packageInfo, ext, tspath.CombinePaths(".", rest)) } if rest != "" { versionPaths := packageInfo.Contents.GetVersionPaths(r.getTraceFunc()) if versionPaths.Exists() { if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2.Format(versionPaths.Version, core.Version(), rest)) } packageDirectoryExists := nodeModulesDirectoryExists && r.resolver.host.FS().DirectoryExists(packageDirectory) pathPatterns := TryParsePatterns(versionPaths.GetPaths()) if fromPaths := r.tryLoadModuleUsingPaths(ext, rest, packageDirectory, versionPaths.GetPaths(), pathPatterns, loader, !packageDirectoryExists); !fromPaths.shouldContinueSearching() { return fromPaths } } } } return loader(ext, candidate, !nodeModulesDirectoryExists) } func (r *resolutionState) createResolvedModuleHandlingSymlink(resolved *resolved) *ResolvedModule { isExternalLibraryImport := resolved != nil && strings.Contains(resolved.path, "/node_modules/") if r.compilerOptions.PreserveSymlinks != core.TSTrue && isExternalLibraryImport && resolved.originalPath == "" && !tspath.IsExternalModuleNameRelative(r.name) { originalPath, resolvedFileName := r.getOriginalAndResolvedFileName(resolved.path) if originalPath != "" { resolved.path = resolvedFileName resolved.originalPath = originalPath } } return r.createResolvedModule(resolved, isExternalLibraryImport) } func (r *resolutionState) createResolvedModule(resolved *resolved, isExternalLibraryImport bool) *ResolvedModule { var resolvedModule ResolvedModule resolvedModule.LookupLocations = LookupLocations{ FailedLookupLocations: r.failedLookupLocations, AffectingLocations: r.affectingLocations, ResolutionDiagnostics: r.diagnostics, } if resolved != nil { resolvedModule.ResolvedFileName = resolved.path resolvedModule.OriginalPath = resolved.originalPath resolvedModule.IsExternalLibraryImport = isExternalLibraryImport resolvedModule.ResolvedUsingTsExtension = resolved.resolvedUsingTsExtension resolvedModule.Extension = resolved.extension resolvedModule.PackageId = resolved.packageId } return &resolvedModule } func (r *resolutionState) createResolvedTypeReferenceDirective(resolved *resolved, primary bool) *ResolvedTypeReferenceDirective { var resolvedTypeReferenceDirective ResolvedTypeReferenceDirective resolvedTypeReferenceDirective.LookupLocations = LookupLocations{ FailedLookupLocations: r.failedLookupLocations, AffectingLocations: r.affectingLocations, ResolutionDiagnostics: r.diagnostics, } if resolved.isResolved() { if !tspath.ExtensionIsTs(resolved.extension) { panic("expected a TypeScript file extension") } resolvedTypeReferenceDirective.ResolvedFileName = resolved.path resolvedTypeReferenceDirective.Primary = primary resolvedTypeReferenceDirective.PackageId = resolved.packageId resolvedTypeReferenceDirective.IsExternalLibraryImport = strings.Contains(resolved.path, "/node_modules/") if r.compilerOptions.PreserveSymlinks != core.TSTrue { originalPath, resolvedFileName := r.getOriginalAndResolvedFileName(resolved.path) if originalPath != "" { resolvedTypeReferenceDirective.ResolvedFileName = resolvedFileName resolvedTypeReferenceDirective.OriginalPath = originalPath } } } return &resolvedTypeReferenceDirective } func (r *resolutionState) getOriginalAndResolvedFileName(fileName string) (string, string) { resolvedFileName := r.realPath(fileName) comparePathsOptions := tspath.ComparePathsOptions{ UseCaseSensitiveFileNames: r.resolver.host.FS().UseCaseSensitiveFileNames(), CurrentDirectory: r.resolver.host.GetCurrentDirectory(), } if tspath.ComparePaths(fileName, resolvedFileName, comparePathsOptions) == 0 { // If the fileName and realpath are differing only in casing, prefer fileName // so that we can issue correct errors for casing under forceConsistentCasingInFileNames return "", fileName } return fileName, resolvedFileName } func (r *resolutionState) tryLoadModuleUsingOptionalResolutionSettings() *resolved { if resolved := r.tryLoadModuleUsingPathsIfEligible(); !resolved.shouldContinueSearching() { return resolved } if !tspath.IsExternalModuleNameRelative(r.name) { // No more tryLoadModuleUsingBaseUrl. return continueSearching() } else { return r.tryLoadModuleUsingRootDirs() } } func (r *resolutionState) getParsedPatternsForPaths() *ParsedPatterns { if r.compilerOptions == r.resolver.compilerOptions { return r.resolver.getParsedPatternsForPaths() } r.parsedPatternsForPathsOnce.Do(func() { r.parsedPatternsForPaths = TryParsePatterns(r.compilerOptions.Paths) }) return r.parsedPatternsForPaths } func (r *resolutionState) tryLoadModuleUsingPathsIfEligible() *resolved { if r.compilerOptions.Paths.Size() > 0 && !tspath.PathIsRelative(r.name) { if r.tracer != nil { r.tracer.write(diagnostics.X_paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0.Format(r.name)) } } else { return continueSearching() } baseDirectory := r.compilerOptions.GetPathsBasePath(r.resolver.host.GetCurrentDirectory()) pathPatterns := r.getParsedPatternsForPaths() return r.tryLoadModuleUsingPaths( r.extensions, r.name, baseDirectory, r.compilerOptions.Paths, pathPatterns, func(extensions extensions, candidate string, onlyRecordFailures bool) *resolved { return r.nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, true /*considerPackageJson*/) }, false, /*onlyRecordFailures*/ ) } func (r *resolutionState) tryLoadModuleUsingPaths(extensions extensions, moduleName string, containingDirectory string, paths *collections.OrderedMap[string, []string], pathPatterns *ParsedPatterns, loader resolutionKindSpecificLoader, onlyRecordFailures bool) *resolved { if matchedPattern := MatchPatternOrExact(pathPatterns, moduleName); matchedPattern.IsValid() { matchedStar := matchedPattern.MatchedText(moduleName) if r.tracer != nil { r.tracer.write(diagnostics.Module_name_0_matched_pattern_1.Format(moduleName, matchedPattern.Text)) } for _, subst := range paths.GetOrZero(matchedPattern.Text) { path := strings.Replace(subst, "*", matchedStar, 1) candidate := tspath.NormalizePath(tspath.CombinePaths(containingDirectory, path)) if r.tracer != nil { r.tracer.write(diagnostics.Trying_substitution_0_candidate_module_location_Colon_1.Format(subst, path)) } // A path mapping may have an extension if extension := tspath.TryGetExtensionFromPath(subst); extension != "" { if path, ok := r.tryFile(candidate, onlyRecordFailures /*onlyRecordFailures*/); ok { return &resolved{ path: path, extension: extension, } } } if resolved := loader(extensions, candidate, onlyRecordFailures || !r.resolver.host.FS().DirectoryExists(tspath.GetDirectoryPath(candidate))); !resolved.shouldContinueSearching() { return resolved } } } return continueSearching() } func (r *resolutionState) tryLoadModuleUsingRootDirs() *resolved { if len(r.compilerOptions.RootDirs) == 0 { return continueSearching() } if r.tracer != nil { r.tracer.write(diagnostics.X_rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0.Format(r.name)) } candidate := tspath.NormalizePath(tspath.CombinePaths(r.containingDirectory, r.name)) var matchedRootDir string var matchedNormalizedPrefix string for _, rootDir := range r.compilerOptions.RootDirs { // rootDirs are expected to be absolute // in case of tsconfig.json this will happen automatically - compiler will expand relative names // using location of tsconfig.json as base location normalizedRoot := tspath.NormalizePath(rootDir) if !strings.HasSuffix(normalizedRoot, "/") { normalizedRoot += "/" } isLongestMatchingPrefix := strings.HasPrefix(candidate, normalizedRoot) && (matchedNormalizedPrefix == "" || len(matchedNormalizedPrefix) < len(normalizedRoot)) if r.tracer != nil { r.tracer.write(diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2.Format(normalizedRoot, candidate, isLongestMatchingPrefix)) } if isLongestMatchingPrefix { matchedNormalizedPrefix = normalizedRoot matchedRootDir = rootDir } } if matchedNormalizedPrefix != "" { if r.tracer != nil { r.tracer.write(diagnostics.Longest_matching_prefix_for_0_is_1.Format(candidate, matchedNormalizedPrefix)) } suffix := candidate[len(matchedNormalizedPrefix):] // first - try to load from a initial location if r.tracer != nil { r.tracer.write(diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2.Format(suffix, matchedNormalizedPrefix, candidate)) } loader := func(extensions extensions, candidate string, onlyRecordFailures bool) *resolved { return r.nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, true /*considerPackageJson*/) } if resolvedFileName := loader(r.extensions, candidate, !r.resolver.host.FS().DirectoryExists(r.containingDirectory)); !resolvedFileName.shouldContinueSearching() { return resolvedFileName } if r.tracer != nil { r.tracer.write(diagnostics.Trying_other_entries_in_rootDirs.Format()) } // then try to resolve using remaining entries in rootDirs for _, rootDir := range r.compilerOptions.RootDirs { if rootDir == matchedRootDir { // skip the initially matched entry continue } candidate := tspath.CombinePaths(tspath.NormalizePath(rootDir), suffix) if r.tracer != nil { r.tracer.write(diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2.Format(suffix, rootDir, candidate)) } baseDirectory := tspath.GetDirectoryPath(candidate) if resolvedFileName := loader(r.extensions, candidate, !r.resolver.host.FS().DirectoryExists(baseDirectory)); !resolvedFileName.shouldContinueSearching() { return resolvedFileName } } if r.tracer != nil { r.tracer.write(diagnostics.Module_resolution_using_rootDirs_has_failed.Format()) } } return continueSearching() } func (r *resolutionState) nodeLoadModuleByRelativeName(extensions extensions, candidate string, onlyRecordFailures bool, considerPackageJson bool) *resolved { if r.tracer != nil { r.tracer.write(diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_types_Colon_1.Format(candidate, extensions.String())) } if !tspath.HasTrailingDirectorySeparator(candidate) { if !onlyRecordFailures { parentOfCandidate := tspath.GetDirectoryPath(candidate) if !r.resolver.host.FS().DirectoryExists(parentOfCandidate) { if r.tracer != nil { r.tracer.write(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(parentOfCandidate)) } onlyRecordFailures = true } } resolvedFromFile := r.loadModuleFromFile(extensions, candidate, onlyRecordFailures) if resolvedFromFile != nil { if considerPackageJson { if packageDirectory := ParseNodeModuleFromPath(resolvedFromFile.path /*isFolder*/, false); packageDirectory != "" { resolvedFromFile.packageId = r.getPackageId(resolvedFromFile.path, r.getPackageJsonInfo(packageDirectory /*onlyRecordFailures*/, false)) } } return resolvedFromFile } } if !onlyRecordFailures { candidateExists := r.resolver.host.FS().DirectoryExists(candidate) if !candidateExists { if r.tracer != nil { r.tracer.write(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(candidate)) } onlyRecordFailures = true } } // esm mode relative imports shouldn't do any directory lookups (either inside `package.json` // files or implicit `index.js`es). This is a notable departure from cjs norms, where `./foo/pkg` // could have been redirected by `./foo/pkg/package.json` to an arbitrary location! if !r.esmMode { return r.loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, considerPackageJson) } return continueSearching() } func (r *resolutionState) loadModuleFromFile(extensions extensions, candidate string, onlyRecordFailures bool) *resolved { // ./foo.js -> ./foo.ts resolvedByReplacingExtension := r.loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures) if resolvedByReplacingExtension != nil { return resolvedByReplacingExtension } // ./foo -> ./foo.ts if !r.esmMode { return r.tryAddingExtensions(candidate, extensions, "", onlyRecordFailures) } return continueSearching() } func (r *resolutionState) loadModuleFromFileNoImplicitExtensions(extensions extensions, candidate string, onlyRecordFailures bool) *resolved { base := tspath.GetBaseFileName(candidate) if !strings.Contains(base, ".") { return continueSearching() // extensionless import, no lookups performed, since we don't support extensionless files } extensionless := tspath.RemoveFileExtension(candidate) if extensionless == candidate { // Once TS native extensions are handled, handle arbitrary extensions for declaration file mapping extensionless = candidate[:strings.LastIndex(candidate, ".")] } extension := candidate[len(extensionless):] if r.tracer != nil { r.tracer.write(diagnostics.File_name_0_has_a_1_extension_stripping_it.Format(candidate, extension)) } return r.tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures) } func (r *resolutionState) tryAddingExtensions(extensionless string, extensions extensions, originalExtension string, onlyRecordFailures bool) *resolved { if !onlyRecordFailures { directory := tspath.GetDirectoryPath(extensionless) onlyRecordFailures = directory != "" && !r.resolver.host.FS().DirectoryExists(directory) } switch originalExtension { case tspath.ExtensionMjs, tspath.ExtensionMts, tspath.ExtensionDmts: if extensions&extensionsTypeScript != 0 { if resolved := r.tryExtension(tspath.ExtensionMts, extensionless, originalExtension == tspath.ExtensionMts || originalExtension == tspath.ExtensionDmts, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsDeclaration != 0 { if resolved := r.tryExtension(tspath.ExtensionDmts, extensionless, originalExtension == tspath.ExtensionMts || originalExtension == tspath.ExtensionDmts, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsJavaScript != 0 { if resolved := r.tryExtension(tspath.ExtensionMjs, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } return continueSearching() case tspath.ExtensionCjs, tspath.ExtensionCts, tspath.ExtensionDcts: if extensions&extensionsTypeScript != 0 { if resolved := r.tryExtension(tspath.ExtensionCts, extensionless, originalExtension == tspath.ExtensionCts || originalExtension == tspath.ExtensionDcts, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsDeclaration != 0 { if resolved := r.tryExtension(tspath.ExtensionDcts, extensionless, originalExtension == tspath.ExtensionCts || originalExtension == tspath.ExtensionDcts, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsJavaScript != 0 { if resolved := r.tryExtension(tspath.ExtensionCjs, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } return continueSearching() case tspath.ExtensionJson: if extensions&extensionsDeclaration != 0 { if resolved := r.tryExtension(".d.json.ts", extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsJson != 0 { if resolved := r.tryExtension(tspath.ExtensionJson, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } return continueSearching() case tspath.ExtensionTsx, tspath.ExtensionJsx: // basically idendical to the ts/js case below, but prefers matching tsx and jsx files exactly before falling back to the ts or js file path // (historically, we disallow having both a a.ts and a.tsx file in the same compilation, since their outputs clash) // TODO: We should probably error if `"./a.tsx"` resolved to `"./a.ts"`, right? if extensions&extensionsTypeScript != 0 { if resolved := r.tryExtension(tspath.ExtensionTsx, extensionless, originalExtension == tspath.ExtensionTsx, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } if resolved := r.tryExtension(tspath.ExtensionTs, extensionless, originalExtension == tspath.ExtensionTsx, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsDeclaration != 0 { if resolved := r.tryExtension(tspath.ExtensionDts, extensionless, originalExtension == tspath.ExtensionTsx, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsJavaScript != 0 { if resolved := r.tryExtension(tspath.ExtensionJsx, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } if resolved := r.tryExtension(tspath.ExtensionJs, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } return continueSearching() case tspath.ExtensionTs, tspath.ExtensionDts, tspath.ExtensionJs, "": if extensions&extensionsTypeScript != 0 { if resolved := r.tryExtension(tspath.ExtensionTs, extensionless, originalExtension == tspath.ExtensionTs || originalExtension == tspath.ExtensionDts, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } if resolved := r.tryExtension(tspath.ExtensionTsx, extensionless, originalExtension == tspath.ExtensionTs || originalExtension == tspath.ExtensionDts, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsDeclaration != 0 { if resolved := r.tryExtension(tspath.ExtensionDts, extensionless, originalExtension == tspath.ExtensionTs || originalExtension == tspath.ExtensionDts, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if extensions&extensionsJavaScript != 0 { if resolved := r.tryExtension(tspath.ExtensionJs, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } if resolved := r.tryExtension(tspath.ExtensionJsx, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } if r.isConfigLookup { if resolved := r.tryExtension(tspath.ExtensionJson, extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } return continueSearching() default: if extensions&extensionsDeclaration != 0 && !tspath.IsDeclarationFileName(extensionless+originalExtension) { if resolved := r.tryExtension(".d"+originalExtension+".ts", extensionless, false, onlyRecordFailures); !resolved.shouldContinueSearching() { return resolved } } return continueSearching() } } func (r *resolutionState) tryExtension(extension string, extensionless string, resolvedUsingTsExtension bool, onlyRecordFailures bool) *resolved { fileName := extensionless + extension if path, ok := r.tryFile(fileName, onlyRecordFailures); ok { return &resolved{ path: path, extension: extension, resolvedUsingTsExtension: !r.candidateIsFromPackageJsonField && resolvedUsingTsExtension, } } return continueSearching() } func (r *resolutionState) tryFile(fileName string, onlyRecordFailures bool) (string, bool) { if len(r.compilerOptions.ModuleSuffixes) == 0 { return fileName, r.tryFileLookup(fileName, onlyRecordFailures) } ext := tspath.TryGetExtensionFromPath(fileName) fileNameNoExtension := tspath.RemoveExtension(fileName, ext) for _, suffix := range r.compilerOptions.ModuleSuffixes { path := fileNameNoExtension + suffix + ext if r.tryFileLookup(path, onlyRecordFailures) { return path, true } } return fileName, false } func (r *resolutionState) tryFileLookup(fileName string, onlyRecordFailures bool) bool { if !onlyRecordFailures { if r.resolver.host.FS().FileExists(fileName) { if r.tracer != nil { r.tracer.write(diagnostics.File_0_exists_use_it_as_a_name_resolution_result.Format(fileName)) } return true } else if r.tracer != nil { r.tracer.write(diagnostics.File_0_does_not_exist.Format(fileName)) } } r.failedLookupLocations = append(r.failedLookupLocations, fileName) return false } func (r *resolutionState) loadNodeModuleFromDirectory(extensions extensions, candidate string, onlyRecordFailures bool, considerPackageJson bool) *resolved { var packageInfo *packagejson.InfoCacheEntry if considerPackageJson { packageInfo = r.getPackageJsonInfo(candidate, onlyRecordFailures) } return r.loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, packageInfo) } func (r *resolutionState) loadNodeModuleFromDirectoryWorker(ext extensions, candidate string, onlyRecordFailures bool, packageInfo *packagejson.InfoCacheEntry) *resolved { var ( packageFile string onlyRecordFailuresForPackageFile bool versionPaths packagejson.VersionPaths ) if packageInfo.Exists() { versionPaths = packageInfo.Contents.GetVersionPaths(r.getTraceFunc()) if tspath.ComparePaths(candidate, packageInfo.PackageDirectory, tspath.ComparePathsOptions{UseCaseSensitiveFileNames: r.resolver.host.FS().UseCaseSensitiveFileNames()}) == 0 { if file, ok := r.getPackageFile(ext, packageInfo); ok { packageFile = file onlyRecordFailuresForPackageFile = !r.resolver.host.FS().DirectoryExists(tspath.GetDirectoryPath(file)) } } } loader := func(extensions extensions, candidate string, onlyRecordFailures bool) *resolved { if fromFile := r.loadFileNameFromPackageJSONField(extensions, candidate, packageFile, onlyRecordFailures); !fromFile.shouldContinueSearching() { return fromFile } // Even if `extensions == extensionsDeclaration`, we can still look up a .ts file as a result of package.json "types" // !!! should we not set this before the filename lookup above? expandedExtensions := extensions if extensions == extensionsDeclaration { expandedExtensions = extensionsTypeScript | extensionsDeclaration } // Disable `esmMode` for the resolution of the package path for CJS-mode packages (so the `main` field can omit extensions) saveESMMode := r.esmMode saveCandidateIsFromPackageJsonField := r.candidateIsFromPackageJsonField r.candidateIsFromPackageJsonField = true if packageInfo.Exists() && packageInfo.Contents.Type.Value != "module" { r.esmMode = false } result := r.nodeLoadModuleByRelativeName(expandedExtensions, candidate, onlyRecordFailures, false /*considerPackageJson*/) r.esmMode = saveESMMode r.candidateIsFromPackageJsonField = saveCandidateIsFromPackageJsonField return result } var indexPath string if r.isConfigLookup { indexPath = tspath.CombinePaths(candidate, "tsconfig") } else { indexPath = tspath.CombinePaths(candidate, "index") } if versionPaths.Exists() && (packageFile == "" || tspath.ContainsPath(candidate, packageFile, tspath.ComparePathsOptions{})) { var moduleName string if packageFile != "" { moduleName = tspath.GetRelativePathFromDirectory(candidate, packageFile, tspath.ComparePathsOptions{}) } else { moduleName = tspath.GetRelativePathFromDirectory(candidate, indexPath, tspath.ComparePathsOptions{}) } if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2.Format(versionPaths.Version, core.Version(), moduleName)) } pathPatterns := TryParsePatterns(versionPaths.GetPaths()) if result := r.tryLoadModuleUsingPaths(ext, moduleName, candidate, versionPaths.GetPaths(), pathPatterns, loader, onlyRecordFailuresForPackageFile); !result.shouldContinueSearching() { if result.packageId.Name != "" { // !!! are these asserts really necessary? panic("expected packageId to be empty") } return result } } if packageFile != "" { if packageFileResult := loader(ext, packageFile, onlyRecordFailuresForPackageFile); !packageFileResult.shouldContinueSearching() { if packageFileResult.packageId.Name != "" { // !!! are these asserts really necessary? panic("expected packageId to be empty") } return packageFileResult } } // ESM mode resolutions don't do package 'index' lookups if !r.esmMode { return r.loadModuleFromFile(ext, indexPath, onlyRecordFailures || !r.resolver.host.FS().DirectoryExists(candidate)) } return continueSearching() } // This function is only ever called with paths written in package.json files - never // module specifiers written in source files - and so it always allows the // candidate to end with a TS extension (but will also try substituting a JS extension for a TS extension). func (r *resolutionState) loadFileNameFromPackageJSONField(extensions extensions, candidate string, packageJSONValue string, onlyRecordFailures bool) *resolved { if extensions&extensionsTypeScript != 0 && tspath.HasImplementationTSFileExtension(candidate) || extensions&extensionsDeclaration != 0 && tspath.IsDeclarationFileName(candidate) { if path, ok := r.tryFile(candidate, onlyRecordFailures); ok { extension := tspath.TryExtractTSExtension(path) return &resolved{ path: path, extension: extension, resolvedUsingTsExtension: packageJSONValue != "" && !strings.HasSuffix(packageJSONValue, extension), } } return continueSearching() } if r.isConfigLookup && extensions&extensionsJson != 0 && tspath.FileExtensionIs(candidate, tspath.ExtensionJson) { if path, ok := r.tryFile(candidate, onlyRecordFailures); ok { return &resolved{ path: path, extension: tspath.ExtensionJson, } } } return r.loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures) } func (r *resolutionState) getPackageFile(extensions extensions, packageInfo *packagejson.InfoCacheEntry) (string, bool) { if !packageInfo.Exists() { return "", false } if r.isConfigLookup { return r.getPackageJSONPathField("tsconfig", &packageInfo.Contents.TSConfig, packageInfo.PackageDirectory) } if extensions&extensionsDeclaration != 0 { if packageFile, ok := r.getPackageJSONPathField("typings", &packageInfo.Contents.Typings, packageInfo.PackageDirectory); ok { return packageFile, ok } if packageFile, ok := r.getPackageJSONPathField("types", &packageInfo.Contents.Types, packageInfo.PackageDirectory); ok { return packageFile, ok } } if extensions&(extensionsImplementationFiles|extensionsDeclaration) != 0 { return r.getPackageJSONPathField("main", &packageInfo.Contents.Main, packageInfo.PackageDirectory) } return "", false } func (r *resolutionState) getPackageJsonInfo(packageDirectory string, onlyRecordFailures bool) *packagejson.InfoCacheEntry { packageJsonPath := tspath.CombinePaths(packageDirectory, "package.json") if onlyRecordFailures { r.failedLookupLocations = append(r.failedLookupLocations, packageJsonPath) return nil } if existing := r.resolver.packageJsonInfoCache.Get(packageJsonPath); existing != nil { if existing.Contents != nil { if r.tracer != nil { r.tracer.write(diagnostics.File_0_exists_according_to_earlier_cached_lookups.Format(packageJsonPath)) } r.affectingLocations = append(r.affectingLocations, packageJsonPath) if existing.PackageDirectory == packageDirectory { return existing } // https://github.com/microsoft/TypeScript/pull/50740 return &packagejson.InfoCacheEntry{ PackageDirectory: packageDirectory, DirectoryExists: true, Contents: existing.Contents, } } else { if existing.DirectoryExists && r.tracer != nil { r.tracer.write(diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups.Format(packageJsonPath)) } r.failedLookupLocations = append(r.failedLookupLocations, packageJsonPath) return nil } } directoryExists := r.resolver.host.FS().DirectoryExists(packageDirectory) if directoryExists && r.resolver.host.FS().FileExists(packageJsonPath) { // Ignore error contents, _ := r.resolver.host.FS().ReadFile(packageJsonPath) packageJsonContent, _ := packagejson.Parse([]byte(contents)) if r.tracer != nil { r.tracer.write(diagnostics.Found_package_json_at_0.Format(packageJsonPath)) } result := &packagejson.InfoCacheEntry{ PackageDirectory: packageDirectory, DirectoryExists: true, Contents: &packagejson.PackageJson{ Fields: packageJsonContent, }, } result = r.resolver.packageJsonInfoCache.Set(packageJsonPath, result) r.affectingLocations = append(r.affectingLocations, packageJsonPath) return result } else { if directoryExists && r.tracer != nil { r.tracer.write(diagnostics.File_0_does_not_exist.Format(packageJsonPath)) } _ = r.resolver.packageJsonInfoCache.Set(packageJsonPath, &packagejson.InfoCacheEntry{ PackageDirectory: packageDirectory, DirectoryExists: directoryExists, }) r.failedLookupLocations = append(r.failedLookupLocations, packageJsonPath) } return nil } func (r *resolutionState) getPackageId(resolvedFileName string, packageInfo *packagejson.InfoCacheEntry) PackageId { if packageInfo.Exists() { packageJsonContent := packageInfo.Contents if name, ok := packageJsonContent.Name.GetValue(); ok { if version, ok := packageJsonContent.Version.GetValue(); ok { var subModuleName string if len(resolvedFileName) > len(packageInfo.PackageDirectory) { subModuleName = resolvedFileName[len(packageInfo.PackageDirectory)+1:] } return PackageId{ Name: name, Version: version, SubModuleName: subModuleName, PeerDependencies: r.readPackageJsonPeerDependencies(packageInfo), } } } } return PackageId{} } func (r *resolutionState) readPackageJsonPeerDependencies(packageJsonInfo *packagejson.InfoCacheEntry) string { peerDependencies := packageJsonInfo.Contents.PeerDependencies ok := r.validatePackageJSONField("peerDependencies", &peerDependencies) if !ok || len(peerDependencies.Value) == 0 { return "" } if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_has_a_peerDependencies_field.Message()) } packageDirectory := r.realPath(packageJsonInfo.PackageDirectory) nodeModulesIndex := strings.LastIndex(packageDirectory, "/node_modules") if nodeModulesIndex == -1 { return "" } nodeModules := packageDirectory[:nodeModulesIndex+len("/node_modules")] + "/" builder := strings.Builder{} for name := range peerDependencies.Value { peerPackageJson := r.getPackageJsonInfo(nodeModules+name /*onlyRecordFailures*/, false) if peerPackageJson != nil { version := peerPackageJson.Contents.Version.Value builder.WriteString("+") builder.WriteString(name) builder.WriteString("@") builder.WriteString(version) if r.tracer != nil { r.tracer.write(diagnostics.Found_peerDependency_0_with_1_version.Format(name, version)) } } else if r.tracer != nil { r.tracer.write(diagnostics.Failed_to_find_peerDependency_0.Format(name)) } } return builder.String() } func (r *resolutionState) realPath(path string) string { rp := tspath.NormalizePath(r.resolver.host.FS().Realpath(path)) if r.tracer != nil { r.tracer.write(diagnostics.Resolving_real_path_for_0_result_1.Format(path, rp)) } return rp } func (r *resolutionState) validatePackageJSONField(fieldName string, field packagejson.TypeValidatedField) bool { if field.IsPresent() { if field.IsValid() { return true } if r.tracer != nil { r.tracer.write(diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2.Format(fieldName, field.ExpectedJSONType(), field.ActualJSONType())) } } if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_does_not_have_a_0_field.Format(fieldName)) } return false } func (r *resolutionState) getPackageJSONPathField(fieldName string, field *packagejson.Expected[string], directory string) (string, bool) { if !r.validatePackageJSONField(fieldName, field) { return "", false } if field.Value == "" { if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_had_a_falsy_0_field.Format(fieldName)) } return "", false } path := tspath.NormalizePath(tspath.CombinePaths(directory, field.Value)) if r.tracer != nil { r.tracer.write(diagnostics.X_package_json_has_0_field_1_that_references_2.Format(fieldName, field.Value, path)) } return path, true } func (r *resolutionState) conditionMatches(condition string) bool { if condition == "default" || slices.Contains(r.conditions, condition) { return true } if !slices.Contains(r.conditions, "types") { return false // only apply versioned types conditions if the types condition is applied } if !strings.HasPrefix(condition, "types@") { return false } if versionRange, ok := semver.TryParseVersionRange(condition[len("types@"):]); ok { return versionRange.Test(&typeScriptVersion) } return false } func (r *resolutionState) getTraceFunc() func(string) { if r.tracer != nil { return r.tracer.write } return nil } func GetConditions(options *core.CompilerOptions, resolutionMode core.ResolutionMode) []string { moduleResolution := options.GetModuleResolutionKind() if resolutionMode == core.ModuleKindNone && moduleResolution == core.ModuleResolutionKindBundler { resolutionMode = core.ModuleKindESNext } conditions := make([]string, 0, 3+len(options.CustomConditions)) if resolutionMode == core.ModuleKindESNext { conditions = append(conditions, "import") } else { conditions = append(conditions, "require") } if options.NoDtsResolution != core.TSTrue { conditions = append(conditions, "types") } if moduleResolution != core.ModuleResolutionKindBundler { conditions = append(conditions, "node") } conditions = core.Concatenate(conditions, options.CustomConditions) return conditions } func getNodeResolutionFeatures(options *core.CompilerOptions) NodeResolutionFeatures { features := NodeResolutionFeaturesNone switch options.GetModuleResolutionKind() { case core.ModuleResolutionKindNode16: features = NodeResolutionFeaturesNode16Default case core.ModuleResolutionKindNodeNext: features = NodeResolutionFeaturesNodeNextDefault case core.ModuleResolutionKindBundler: features = NodeResolutionFeaturesBundlerDefault } if options.ResolvePackageJsonExports == core.TSTrue { features |= NodeResolutionFeaturesExports } else if options.ResolvePackageJsonExports == core.TSFalse { features &^= NodeResolutionFeaturesExports } if options.ResolvePackageJsonImports == core.TSTrue { features |= NodeResolutionFeaturesImports } else if options.ResolvePackageJsonImports == core.TSFalse { features &^= NodeResolutionFeaturesImports } return features } func moveToNextDirectorySeparatorIfAvailable(path string, prevSeparatorIndex int, isFolder bool) int { offset := prevSeparatorIndex + 1 nextSeparatorIndex := strings.Index(path[offset:], "/") if nextSeparatorIndex == -1 { if isFolder { return len(path) } return prevSeparatorIndex } return nextSeparatorIndex + offset } type ParsedPatterns struct { matchableStringSet collections.Set[string] patterns []core.Pattern } func (r *Resolver) getParsedPatternsForPaths() *ParsedPatterns { r.parsedPatternsForPathsOnce.Do(func() { r.parsedPatternsForPaths = TryParsePatterns(r.compilerOptions.Paths) }) return r.parsedPatternsForPaths } func TryParsePatterns(pathMappings *collections.OrderedMap[string, []string]) *ParsedPatterns { paths := pathMappings.Keys() numPatterns := 0 for path := range paths { if pattern := core.TryParsePattern(path); pattern.IsValid() && pattern.StarIndex == -1 { numPatterns++ } } numMatchables := pathMappings.Size() - numPatterns var patterns []core.Pattern var matchableStringSet collections.Set[string] if numPatterns != 0 { patterns = make([]core.Pattern, 0, numPatterns) } if numMatchables != 0 { matchableStringSet = *collections.NewSetWithSizeHint[string](numMatchables) } for path := range paths { if pattern := core.TryParsePattern(path); pattern.IsValid() { if pattern.StarIndex == -1 { matchableStringSet.Add(path) } else { patterns = append(patterns, pattern) } } } return &ParsedPatterns{ matchableStringSet: matchableStringSet, patterns: patterns, } } func MatchPatternOrExact(patterns *ParsedPatterns, candidate string) core.Pattern { if patterns.matchableStringSet.Has(candidate) { return core.Pattern{ Text: candidate, StarIndex: -1, } } if len(patterns.patterns) == 0 { return core.Pattern{} } return core.FindBestPatternMatch(patterns.patterns, core.Identity, candidate) } // If you import from "." inside a containing directory "/foo", the result of `tspath.NormalizePath` // would be "/foo", but this loses the information that `foo` is a directory and we intended // to look inside of it. The Node CommonJS resolution algorithm doesn't call this out // (https://nodejs.org/api/modules.html#all-together), but it seems that module paths ending // in `.` are actually normalized to `./` before proceeding with the resolution algorithm. func normalizePathForCJSResolution(containingDirectory string, moduleName string) string { combined := tspath.CombinePaths(containingDirectory, moduleName) parts := tspath.GetPathComponents(combined, "") lastPart := parts[len(parts)-1] if lastPart == "." || lastPart == ".." { return tspath.EnsureTrailingDirectorySeparator(tspath.NormalizePath(combined)) } return tspath.NormalizePath(combined) } func matchesPatternWithTrailer(target string, name string) bool { if strings.HasSuffix(target, "*") { return false } starPos := strings.Index(target, "*") if starPos == -1 { return false } return strings.HasPrefix(name, target[:starPos]) && strings.HasSuffix(name, target[starPos+1:]) } /** True if `extension` is one of the supported `extensions`. */ func extensionIsOk(extensions extensions, extension string) bool { return (extensions&extensionsJavaScript != 0 && (extension == tspath.ExtensionJs || extension == tspath.ExtensionJsx || extension == tspath.ExtensionMjs || extension == tspath.ExtensionCjs) || (extensions&extensionsTypeScript != 0 && (extension == tspath.ExtensionTs || extension == tspath.ExtensionTsx || extension == tspath.ExtensionMts || extension == tspath.ExtensionCts)) || (extensions&extensionsDeclaration != 0 && (extension == tspath.ExtensionDts || extension == tspath.ExtensionDmts || extension == tspath.ExtensionDcts)) || (extensions&extensionsJson != 0 && extension == tspath.ExtensionJson)) } func ResolveConfig(moduleName string, containingFile string, host ResolutionHost) *ResolvedModule { resolver := NewResolver(host, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "") return resolver.resolveConfig(moduleName, containingFile) } func GetAutomaticTypeDirectiveNames(options *core.CompilerOptions, host ResolutionHost) []string { if options.Types != nil { return options.Types } var result []string typeRoots, _ := options.GetEffectiveTypeRoots(host.GetCurrentDirectory()) for _, root := range typeRoots { if host.FS().DirectoryExists(root) { for _, typeDirectivePath := range host.FS().GetAccessibleEntries(root).Directories { normalized := tspath.NormalizePath(typeDirectivePath) packageJsonPath := tspath.CombinePaths(root, normalized, "package.json") isNotNeededPackage := false if host.FS().FileExists(packageJsonPath) { contents, _ := host.FS().ReadFile(packageJsonPath) packageJsonContent, _ := packagejson.Parse([]byte(contents)) // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. // See `createNotNeededPackageJSON` in the types-publisher` repo. isNotNeededPackage = packageJsonContent.Typings.Null } if !isNotNeededPackage { baseFileName := tspath.GetBaseFileName(normalized) if !strings.HasPrefix(baseFileName, ".") { result = append(result, baseFileName) } } } } } return result }