2025-10-15 10:12:44 +03:00

2011 lines
84 KiB
Go

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
}