|
| 1 | +package modulespecifiers |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + |
| 6 | + "github.com/microsoft/TypeScript/tsc/internal/collections" |
| 7 | + "github.com/microsoft/TypeScript/tsc/internal/core" |
| 8 | + "github.com/microsoft/TypeScript/tsc/internal/module" |
| 9 | + "github.com/microsoft/TypeScript/tsc/internal/packagejson" |
| 10 | + "github.com/microsoft/TypeScript/tsc/internal/tspath" |
| 11 | +) |
| 12 | + |
| 13 | +// GenerationCache holds results that a host reuses across module specifier generation. Finding the |
| 14 | +// subpath that a package.json "exports" map assigns to a file, and resolving the patterns of a "paths" |
| 15 | +// or "typesVersions" table, each scan a whole table, and the same table is consulted once per file that |
| 16 | +// needs a specifier, so hosts that generate many specifiers keep one of these. Entries are keyed by the |
| 17 | +// table they were derived from and the compiler options, so neither a re-read package.json nor a |
| 18 | +// different set of options is served stale results. The zero value is ready to use and safe for |
| 19 | +// concurrent use. |
| 20 | +type GenerationCache struct { |
| 21 | + exports collections.SyncMap[packageExportsSpecifierKey, tspath.ModuleSpecifier] |
| 22 | + pathPatterns collections.SyncMap[pathPatternsKey, []pathPattern] |
| 23 | +} |
| 24 | + |
| 25 | +type packageExportsSpecifierKey struct { |
| 26 | + options *core.CompilerOptions // conditions, custom conditions, and output extensions |
| 27 | + packageJson *packagejson.PackageJson |
| 28 | + packageDirectory tspath.RootedDirectoryPath |
| 29 | + packageName string |
| 30 | + targetFileName tspath.RootedFilePath |
| 31 | + mode core.ResolutionMode |
| 32 | +} |
| 33 | + |
| 34 | +// getModuleNameFromExports returns the specifier under which packageJson's "exports" map publishes |
| 35 | +// targetFileName, or "" when it is not exported, consulting the host's cache when it has one. |
| 36 | +func getModuleNameFromExports( |
| 37 | + options *core.CompilerOptions, |
| 38 | + host ModuleSpecifierGenerationHost, |
| 39 | + targetFileName tspath.RootedFilePath, |
| 40 | + packageDirectory tspath.RootedDirectoryPath, |
| 41 | + packageName string, |
| 42 | + packageJson *packagejson.PackageJson, |
| 43 | + mode core.ResolutionMode, |
| 44 | +) tspath.ModuleSpecifier { |
| 45 | + cache := host.ModuleSpecifierGenerationCache() |
| 46 | + if cache == nil { |
| 47 | + return tryGetModuleNameFromExports(options, host, targetFileName, packageDirectory, packageName, packageJson.Fields.Exports, module.GetConditions(options, mode)) |
| 48 | + } |
| 49 | + key := packageExportsSpecifierKey{ |
| 50 | + options: options, |
| 51 | + packageJson: packageJson, |
| 52 | + packageDirectory: packageDirectory, |
| 53 | + packageName: packageName, |
| 54 | + targetFileName: targetFileName, |
| 55 | + mode: mode, |
| 56 | + } |
| 57 | + if specifier, ok := cache.exports.Load(key); ok { |
| 58 | + return specifier |
| 59 | + } |
| 60 | + specifier := tryGetModuleNameFromExports(options, host, targetFileName, packageDirectory, packageName, packageJson.Fields.Exports, module.GetConditions(options, mode)) |
| 61 | + cache.exports.Store(key, specifier) |
| 62 | + return specifier |
| 63 | +} |
| 64 | + |
| 65 | +type pathPatternsKey struct { |
| 66 | + paths *collections.OrderedMap[string, []string] |
| 67 | + baseDirectory tspath.RootedDirectoryPath |
| 68 | +} |
| 69 | + |
| 70 | +// pathPattern is one pattern of a "paths" or "typesVersions" table, resolved against the table's base |
| 71 | +// directory and split at its wildcard. |
| 72 | +type pathPattern struct { |
| 73 | + key string // the table key the pattern belongs to |
| 74 | + pattern string |
| 75 | + prefix string // the pattern before its "*", or the whole pattern when it has none |
| 76 | + suffix string // the pattern after its "*" |
| 77 | + hasWildcard bool |
| 78 | + hasExtension bool |
| 79 | +} |
| 80 | + |
| 81 | +// getPathPatterns returns the patterns of paths in table order, resolved against baseDirectory. They |
| 82 | +// depend only on the table, so they are resolved once per host when the host keeps a cache. The cache |
| 83 | +// is keyed by the table's identity, so paths must be a table that lives as long as its source: the |
| 84 | +// compiler options' "paths", or the "typesVersions" table of a parsed package.json. |
| 85 | +func getPathPatterns(host ModuleSpecifierGenerationHost, paths *collections.OrderedMap[string, []string], baseDirectory tspath.RootedDirectoryPath) []pathPattern { |
| 86 | + cache := host.ModuleSpecifierGenerationCache() |
| 87 | + if cache == nil { |
| 88 | + return resolvePathPatterns(paths, baseDirectory, host.CaseSensitivity()) |
| 89 | + } |
| 90 | + key := pathPatternsKey{paths: paths, baseDirectory: baseDirectory} |
| 91 | + if patterns, ok := cache.pathPatterns.Load(key); ok { |
| 92 | + return patterns |
| 93 | + } |
| 94 | + patterns, _ := cache.pathPatterns.LoadOrStore(key, resolvePathPatterns(paths, baseDirectory, host.CaseSensitivity())) |
| 95 | + return patterns |
| 96 | +} |
| 97 | + |
| 98 | +func resolvePathPatterns(paths *collections.OrderedMap[string, []string], baseDirectory tspath.RootedDirectoryPath, caseSensitivity tspath.CaseSensitivity) []pathPattern { |
| 99 | + patterns := make([]pathPattern, 0, paths.Size()) |
| 100 | + for key, values := range paths.Entries() { |
| 101 | + for _, patternText := range values { |
| 102 | + normalized := tspath.NormalizePath(patternText) |
| 103 | + pattern := resolvePathPatternIfInSameVolume(normalized, baseDirectory, caseSensitivity) |
| 104 | + if len(pattern) == 0 { |
| 105 | + pattern = normalized |
| 106 | + } |
| 107 | + prefix, suffix, hasWildcard := strings.Cut(pattern, "*") |
| 108 | + patterns = append(patterns, pathPattern{ |
| 109 | + key: key, |
| 110 | + pattern: pattern, |
| 111 | + prefix: prefix, |
| 112 | + suffix: suffix, |
| 113 | + hasWildcard: hasWildcard, |
| 114 | + hasExtension: len(tspath.TryGetExtensionFromPath(pattern)) > 0, |
| 115 | + }) |
| 116 | + } |
| 117 | + } |
| 118 | + return patterns |
| 119 | +} |
0 commit comments