vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,79 @@
package module
import (
"sync"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/packagejson"
)
type ModeAwareCache[T any] map[ModeAwareCacheKey]T
type moduleResolutionCacheKey struct {
containingDirectory string
moduleName string
resolutionMode core.ResolutionMode
redirectConfigName string
}
type moduleResolutionCache struct {
cache collections.SyncMap[moduleResolutionCacheKey, *ResolvedModule]
}
func (c *moduleResolutionCache) Get(key moduleResolutionCacheKey) (*ResolvedModule, bool) {
return c.cache.Load(key)
}
func (c *moduleResolutionCache) Set(key moduleResolutionCacheKey, value *ResolvedModule) {
c.cache.LoadOrStore(key, value)
}
type typeRefDirectiveResolutionCacheKey struct {
containingDirectory string
typeReferenceName string
resolutionMode core.ResolutionMode
redirectConfigName string
fromInferredTypesContainingFile bool
}
type typeRefDirectiveResolutionCache struct {
cache collections.SyncMap[typeRefDirectiveResolutionCacheKey, *ResolvedTypeReferenceDirective]
}
func (c *typeRefDirectiveResolutionCache) Get(key typeRefDirectiveResolutionCacheKey) (*ResolvedTypeReferenceDirective, bool) {
return c.cache.Load(key)
}
func (c *typeRefDirectiveResolutionCache) Set(key typeRefDirectiveResolutionCacheKey, value *ResolvedTypeReferenceDirective) {
c.cache.Store(key, value)
}
type caches struct {
packageJsonInfoCache *packagejson.InfoCache
moduleResolutionCache moduleResolutionCache
typeRefDirectiveResolutionCache typeRefDirectiveResolutionCache
// Cached representation for `core.CompilerOptions.paths`.
// Doesn't handle other path patterns like in `typesVersions`.
parsedPatternsForPathsOnce sync.Once
parsedPatternsForPaths *ParsedPatterns
}
func newCaches(
currentDirectory string,
useCaseSensitiveFileNames bool,
options *core.CompilerOptions,
) caches {
return caches{
packageJsonInfoCache: packagejson.NewInfoCache(currentDirectory, useCaseSensitiveFileNames),
}
}
func getRedirectConfigName(redirect ResolvedProjectReference) string {
if redirect == nil {
return ""
}
return redirect.ConfigName()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,416 @@
package module_test
import (
"strings"
"sync"
"sync/atomic"
"testing"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
)
type resolutionHostStub struct {
fs vfs.FS
cwd string
}
func (h *resolutionHostStub) FS() vfs.FS { return h.fs }
func (h *resolutionHostStub) GetCurrentDirectory() string { return h.cwd }
// Regression test for https://github.com/microsoft/typescript-go/issues/3526.
//
// Resolving a node_modules import with a trailing slash (e.g. `pkg/`) must
// produce the same result as without one.
func TestResolveModuleNameTrailingSlash(t *testing.T) {
t.Parallel()
fs := vfstest.FromMap(map[string]string{
"/repo/node_modules/pkg/package.json": `{"name":"pkg","main":"main.js","types":"main.d.ts"}`,
"/repo/node_modules/pkg/main.d.ts": "export const x: number;",
"/repo/node_modules/pkg/main.js": "exports.x = 1;",
"/repo/src/file.ts": "",
}, true)
host := &resolutionHostStub{fs: fs, cwd: "/repo"}
opts := &core.CompilerOptions{
ModuleResolution: core.ModuleResolutionKindBundler,
Module: core.ModuleKindESNext,
Target: core.ScriptTargetESNext,
}
resolver := module.NewResolver(host, opts, "", "")
for _, name := range []string{"pkg", "pkg/"} {
r, _ := resolver.ResolveModuleName(name, "/repo/src/file.ts", core.ModuleKindESNext, nil)
if !r.IsResolved() {
t.Errorf("%q failed to resolve", name)
}
}
}
// blockingFS wraps a vfs.FS and forces FileExists calls for `targetPath` to
// block on `gate` until released. Each caller sends on `arrived` when it
// reaches the gate. This is used to deterministically reproduce the
// `package.json` info-cache insert race described in
// https://github.com/microsoft/typescript-go/issues/3526.
type blockingFS struct {
vfs.FS
targetPath string
gate chan struct{}
arrived chan struct{} // each blocked goroutine sends one value
}
// waitForSignal waits for a synchronization point in these race regression
// tests and converts deadlocks into deterministic test failures.
func waitForSignal(t *testing.T, ch <-chan struct{}, description string) {
t.Helper()
select {
case <-ch:
return
case <-t.Context().Done():
t.Fatalf("timed out waiting for %s", description)
}
}
func (f *blockingFS) FileExists(path string) bool {
if path == f.targetPath {
f.arrived <- struct{}{}
<-f.gate
}
return f.FS.FileExists(path)
}
// flipFileExistsFS wraps a vfs.FS and returns false for the first
// FileExists call to `targetPath`, then true for the second. Both calls
// signal arrival via channel then block until released via their respective
// gate channels. ReadFile for the target path also signals arrival then
// blocks, so the "file doesn't exist" Set completes before the "file exists"
// Set (reproducing the LoadOrStore race).
type flipFileExistsFS struct {
vfs.FS
targetPath string
callCount atomic.Int32
firstArrived chan struct{} // closed when the first FileExists caller arrives
secondArrived chan struct{} // closed when the second FileExists caller arrives
firstGate chan struct{}
secondGate chan struct{}
readArrived chan struct{} // closed when ReadFile caller arrives
readGate chan struct{}
}
func (f *flipFileExistsFS) FileExists(path string) bool {
if path == f.targetPath {
n := f.callCount.Add(1)
if n == 1 {
close(f.firstArrived)
<-f.firstGate
return false // first caller: simulate "file not yet visible"
}
if n == 2 {
close(f.secondArrived)
<-f.secondGate
return f.FS.FileExists(path) // second caller: file is visible
}
}
return f.FS.FileExists(path)
}
func (f *flipFileExistsFS) ReadFile(path string) (string, bool) {
if path == f.targetPath {
close(f.readArrived)
<-f.readGate
}
return f.FS.ReadFile(path)
}
// Regression test for https://github.com/microsoft/typescript-go/issues/3526.
//
// Two goroutines resolve the same package via specifiers that differ only by
// a trailing slash (`pkg` and `pkg/`). A blocking FS holds both at the
// `FileExists` check for `package.json` — *after* each has confirmed a
// `package.json` info-cache miss but *before* either has called `Set`. When
// released, both proceed to `LoadOrStore` and one of them loses. Without the
// fix, the loser receives the winner's `InfoCacheEntry` whose
// `PackageDirectory` doesn't match its own `candidate` (because one spelling
// has a trailing slash and the other doesn't), and
// `loadNodeModuleFromDirectoryWorker`'s `ComparePaths` check skips loading
// the package's `main`/`types`. With no `index.*` present, resolution falls
// through to "unresolved" — the phantom TS2307 the issue describes. This
// test deterministically fails when the fix is reverted.
func TestResolveModuleNameTrailingSlashRace(t *testing.T) {
t.Parallel()
const pkgJSONPath = "/repo/node_modules/pkg/package.json"
files := map[string]string{
// `types` points at a file that is not discoverable through any
// fallback path: there is no `index.*` and no `main`. The only way
// to resolve `pkg` (or `pkg/`) is via the package.json `types` field
// inside `loadNodeModuleFromDirectoryWorker`, which is exactly the
// step that the bug skips when `candidate` and
// `packageInfo.PackageDirectory` mismatch.
pkgJSONPath: `{"name":"pkg","types":"./typings/index.d.ts"}`,
"/repo/node_modules/pkg/typings/index.d.ts": "export const x: number;",
// Distinct containing files so each `ResolveModuleName` call has a
// unique module-resolution-cache key.
"/repo/src/a/file.ts": "",
"/repo/src/b/file.ts": "",
}
fs := &blockingFS{
FS: vfstest.FromMap(files, true),
targetPath: pkgJSONPath,
gate: make(chan struct{}),
arrived: make(chan struct{}, 2),
}
host := &resolutionHostStub{fs: fs, cwd: "/repo"}
opts := &core.CompilerOptions{
ModuleResolution: core.ModuleResolutionKindBundler,
Module: core.ModuleKindESNext,
Target: core.ScriptTargetESNext,
}
resolver := module.NewResolver(host, opts, "", "")
type resolutionResult struct {
name string
resolved bool
}
results := make(chan resolutionResult, 2)
var wg sync.WaitGroup
for _, name := range []string{"pkg", "pkg/"} {
containingFile := "/repo/src/a/file.ts"
if strings.HasSuffix(name, "/") {
containingFile = "/repo/src/b/file.ts"
}
wg.Go(func() {
r, _ := resolver.ResolveModuleName(name, containingFile, core.ModuleKindESNext, nil)
results <- resolutionResult{name, r.IsResolved()}
})
}
// Wait for both goroutines to reach the FileExists gate, guaranteeing
// both have observed a package.json info-cache miss.
waitForSignal(t, fs.arrived, "first FileExists gate arrival")
waitForSignal(t, fs.arrived, "second FileExists gate arrival")
close(fs.gate)
wg.Wait()
close(results)
for r := range results {
if !r.resolved {
t.Errorf("%q failed to resolve", r.name)
}
}
}
// Regression test for https://github.com/microsoft/typescript-go/issues/1290.
//
// Two goroutines resolve `pkg/sub` concurrently. Both miss the package.json
// info-cache for the root package directory. A `flipFileExistsFS` forces the
// first goroutine's `FileExists` to return false (simulating the file not yet
// being visible), so it stores a nil-Contents cache entry. The second
// goroutine's `FileExists` returns true, but its `Set` call (`LoadOrStore`)
// returns the first goroutine's nil-Contents entry. Without the `Exists()`
// guard on the `typesVersions` lookup, `packageInfo.Contents.GetVersionPaths`
// dereferences nil and panics. With the guard the nil-Contents entry is safely
// skipped.
func TestResolveSubpathNilContentsRace(t *testing.T) {
t.Parallel()
const rootPkgJSON = "/repo/node_modules/pkg/package.json"
files := map[string]string{
rootPkgJSON: `{"name":"pkg","version":"1.0.0"}`,
"/repo/node_modules/pkg/sub/index.d.ts": "export declare const sub: number;",
"/repo/node_modules/pkg/sub/index.js": "exports.sub = 1;",
"/repo/src/a/file.ts": "",
"/repo/src/b/file.ts": "",
}
fs := &flipFileExistsFS{
FS: vfstest.FromMap(files, true),
targetPath: rootPkgJSON,
firstArrived: make(chan struct{}),
secondArrived: make(chan struct{}),
firstGate: make(chan struct{}),
secondGate: make(chan struct{}),
readArrived: make(chan struct{}),
readGate: make(chan struct{}),
}
host := &resolutionHostStub{fs: fs, cwd: "/repo"}
opts := &core.CompilerOptions{
ModuleResolution: core.ModuleResolutionKindBundler,
Module: core.ModuleKindESNext,
Target: core.ScriptTargetESNext,
}
resolver := module.NewResolver(host, opts, "", "")
var panicked atomic.Bool
type resolutionResult struct {
containingFile string
resolved bool
}
results := make(chan resolutionResult, 2)
var wg sync.WaitGroup
// Two goroutines both resolve "pkg/sub". Each calls getPackageJsonInfo
// for the root package directory, reaching FileExists for rootPkgJSON.
for _, containingFile := range []string{"/repo/src/a/file.ts", "/repo/src/b/file.ts"} {
wg.Go(func() {
resolved := false
defer func() {
if r := recover(); r != nil {
panicked.Store(true)
}
results <- resolutionResult{containingFile: containingFile, resolved: resolved}
}()
r, _ := resolver.ResolveModuleName("pkg/sub", containingFile, core.ModuleKindESNext, nil)
resolved = r.IsResolved()
})
}
// Phase 1: Wait for both goroutines to reach FileExists for the root
// package.json, guaranteeing both have observed a cache miss.
waitForSignal(t, fs.firstArrived, "first root package.json FileExists arrival")
waitForSignal(t, fs.secondArrived, "second root package.json FileExists arrival")
// Phase 2: Release the first FileExists caller (returns false).
// It enters the "file not found" branch and stores a nil-Contents entry
// via Set — this is nearly instant (no ReadFile).
close(fs.firstGate)
// Phase 3: Release the second FileExists caller (returns true).
// It proceeds to ReadFile, which we gate separately to ensure the first
// goroutine's nil-Contents Set has completed.
close(fs.secondGate)
// Phase 4: Wait for the second goroutine to reach ReadFile, then release.
// By this point the first goroutine has stored its nil-Contents entry.
// The second goroutine's Set (LoadOrStore) will return that stale entry.
waitForSignal(t, fs.readArrived, "root package.json ReadFile arrival")
close(fs.readGate)
wg.Wait()
close(results)
if panicked.Load() {
t.Fatal("resolver panicked due to nil Contents dereference in loadModuleFromSpecificNodeModulesDirectory")
}
for r := range results {
if !r.resolved {
t.Fatalf("%q failed to resolve pkg/sub", r.containingFile)
}
}
}
func TestParseNodeModuleFromPath(t *testing.T) {
t.Parallel()
tests := []struct {
name string
path string
isFolder bool
want string
}{
{"file in package", "/a/node_modules/b/lib/index.d.ts", false, "/a/node_modules/b"},
{"file in scoped package", "/a/node_modules/@scope/b/lib/index.d.ts", false, "/a/node_modules/@scope/b"},
{"folder subpath", "/a/node_modules/b/lib/File", true, "/a/node_modules/b"},
{"folder subpath scoped", "/a/node_modules/@scope/b/lib/File", true, "/a/node_modules/@scope/b"},
{"package root folder", "/a/node_modules/b", true, "/a/node_modules/b"},
{"scoped package root folder", "/a/node_modules/@scope/b", true, "/a/node_modules/@scope/b"},
// A bare scope directory has no package name; must not panic (https://github.com/microsoft/typescript-go/issues/4373).
{"scope-only folder", "/a/node_modules/@scope", true, "/a/node_modules/@scope"},
{"types scope-only folder", "/a/node_modules/@types", true, "/a/node_modules/@types"},
{"not in node_modules", "/a/src/index.ts", false, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := module.ParseNodeModuleFromPath(tt.path, tt.isFolder); got != tt.want {
t.Errorf("ParseNodeModuleFromPath(%q, %v) = %q, want %q", tt.path, tt.isFolder, got, tt.want)
}
})
}
}
// Regression test for https://github.com/microsoft/typescript-go/issues/4478.
//
// While resolving a package with peerDependencies, two goroutines look up the
// peer package's package.json concurrently. A `flipFileExistsFS` forces the
// first lookup to cache a nil-Contents entry and the second lookup to receive
// that stale entry from `Set`. The resolver must not dereference the peer
// package.json contents unless the entry actually exists.
func TestResolvePeerDependencyNilContentsRace(t *testing.T) {
t.Parallel()
const peerPkgJSON = "/repo/node_modules/peer/package.json"
files := map[string]string{
"/repo/node_modules/pkg/package.json": `{"name":"pkg","version":"1.0.0","types":"index.d.ts","peerDependencies":{"peer":"*"}}`,
"/repo/node_modules/pkg/index.d.ts": "export declare const x: number;",
peerPkgJSON: `{"name":"peer","version":"2.0.0"}`,
"/repo/src/a/file.ts": "",
"/repo/src/b/file.ts": "",
}
fs := &flipFileExistsFS{
FS: vfstest.FromMap(files, true),
targetPath: peerPkgJSON,
firstArrived: make(chan struct{}),
secondArrived: make(chan struct{}),
firstGate: make(chan struct{}),
secondGate: make(chan struct{}),
readArrived: make(chan struct{}),
readGate: make(chan struct{}),
}
host := &resolutionHostStub{fs: fs, cwd: "/repo"}
opts := &core.CompilerOptions{
ModuleResolution: core.ModuleResolutionKindBundler,
Module: core.ModuleKindESNext,
Target: core.ScriptTargetESNext,
}
resolver := module.NewResolver(host, opts, "", "")
var panicked atomic.Bool
type resolutionResult struct {
containingFile string
resolved bool
}
results := make(chan resolutionResult, 2)
var wg sync.WaitGroup
for _, containingFile := range []string{"/repo/src/a/file.ts", "/repo/src/b/file.ts"} {
wg.Go(func() {
resolved := false
defer func() {
if r := recover(); r != nil {
panicked.Store(true)
}
results <- resolutionResult{containingFile: containingFile, resolved: resolved}
}()
r, _ := resolver.ResolveModuleName("pkg", containingFile, core.ModuleKindESNext, nil)
resolved = r.IsResolved()
})
}
waitForSignal(t, fs.firstArrived, "first peer package.json FileExists arrival")
waitForSignal(t, fs.secondArrived, "second peer package.json FileExists arrival")
close(fs.firstGate)
var firstResult resolutionResult
select {
case result := <-results:
firstResult = result
case <-t.Context().Done():
t.Fatal("timed out waiting for first peer package.json lookup to finish")
}
close(fs.secondGate)
waitForSignal(t, fs.readArrived, "peer package.json ReadFile arrival")
close(fs.readGate)
wg.Wait()
close(results)
if panicked.Load() {
t.Fatal("resolver panicked due to nil Contents dereference in readPackageJsonPeerDependencies")
}
if !firstResult.resolved {
t.Fatalf("%q failed to resolve pkg", firstResult.containingFile)
}
for r := range results {
if !r.resolved {
t.Fatalf("%q failed to resolve pkg", r.containingFile)
}
}
}

View File

@@ -0,0 +1,136 @@
package module
import (
"fmt"
"math/bits"
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
)
type ResolutionHost interface {
FS() vfs.FS
GetCurrentDirectory() string
}
type ModeAwareCacheKey struct {
Name string
Mode core.ResolutionMode
}
type ResolvedProjectReference interface {
ConfigName() string
CompilerOptions() *core.CompilerOptions
}
type NodeResolutionFeatures int32
const (
NodeResolutionFeaturesImports NodeResolutionFeatures = 1 << iota
NodeResolutionFeaturesSelfName
NodeResolutionFeaturesExports
NodeResolutionFeaturesExportsPatternTrailers
// allowing `#/` root imports in package.json imports field
// not supported until mass adoption - https://github.com/nodejs/node/pull/60864
NodeResolutionFeaturesImportsPatternRoot
NodeResolutionFeaturesNone NodeResolutionFeatures = 0
NodeResolutionFeaturesAll = NodeResolutionFeaturesImports | NodeResolutionFeaturesSelfName | NodeResolutionFeaturesExports | NodeResolutionFeaturesExportsPatternTrailers | NodeResolutionFeaturesImportsPatternRoot
NodeResolutionFeaturesNode16Default = NodeResolutionFeaturesImports | NodeResolutionFeaturesSelfName | NodeResolutionFeaturesExports | NodeResolutionFeaturesExportsPatternTrailers
NodeResolutionFeaturesNodeNextDefault = NodeResolutionFeaturesAll
NodeResolutionFeaturesBundlerDefault = NodeResolutionFeaturesImports | NodeResolutionFeaturesSelfName | NodeResolutionFeaturesExports | NodeResolutionFeaturesExportsPatternTrailers | NodeResolutionFeaturesImportsPatternRoot
)
type PackageId struct {
Name string
SubModuleName string
Version string
PeerDependencies string
}
func (p *PackageId) String() string {
return fmt.Sprintf("%s@%s%s", p.PackageName(), p.Version, p.PeerDependencies)
}
func (p *PackageId) PackageName() string {
if p.SubModuleName != "" {
return p.Name + "/" + p.SubModuleName
}
return p.Name
}
type ResolvedModule struct {
ResolutionDiagnostics []*ast.Diagnostic
ResolvedFileName string
OriginalPath string
Extension string
ResolvedUsingTsExtension bool
PackageId PackageId
IsExternalLibraryImport bool
AlternateResult string
}
func (r *ResolvedModule) IsResolved() bool {
return r != nil && r.ResolvedFileName != ""
}
type ResolvedTypeReferenceDirective struct {
ResolutionDiagnostics []*ast.Diagnostic
Primary bool
ResolvedFileName string
OriginalPath string
PackageId PackageId
IsExternalLibraryImport bool
}
func (r *ResolvedTypeReferenceDirective) IsResolved() bool {
return r.ResolvedFileName != ""
}
type extensions int32
const (
extensionsTypeScript extensions = 1 << iota
extensionsJavaScript
extensionsDeclaration
extensionsJson
extensionsImplementationFiles = extensionsTypeScript | extensionsJavaScript
)
func (e extensions) String() string {
result := make([]string, 0, bits.OnesCount(uint(e)))
if e&extensionsTypeScript != 0 {
result = append(result, "TypeScript")
}
if e&extensionsJavaScript != 0 {
result = append(result, "JavaScript")
}
if e&extensionsDeclaration != 0 {
result = append(result, "Declaration")
}
if e&extensionsJson != 0 {
result = append(result, "JSON")
}
return strings.Join(result, ", ")
}
func (e extensions) Array() []string {
result := []string{}
if e&extensionsTypeScript != 0 {
result = append(result, tspath.SupportedTSImplementationExtensions...)
}
if e&extensionsJavaScript != 0 {
result = append(result, tspath.SupportedJSExtensionsFlat...)
}
if e&extensionsDeclaration != 0 {
result = append(result, tspath.SupportedDeclarationExtensions...)
}
if e&extensionsJson != 0 {
result = append(result, tspath.ExtensionJson)
}
return result
}

View File

@@ -0,0 +1,197 @@
package module
import (
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/semver"
"github.com/microsoft/typescript-go/internal/tspath"
)
var typeScriptVersion = semver.MustParse(core.Version())
const InferredTypesContainingFile = "__inferred type names__.ts"
func IsApplicableVersionedTypesKey(key string) bool {
if !strings.HasPrefix(key, "types@") {
return false
}
range_, ok := semver.TryParseVersionRange(key[len("types@"):])
if !ok {
return false
}
return range_.Test(&typeScriptVersion)
}
func ParseNodeModuleFromPath(resolved string, isFolder bool) string {
path := tspath.NormalizePath(resolved)
idx := strings.LastIndex(path, "/node_modules/")
if idx == -1 {
return ""
}
indexAfterNodeModules := idx + len("/node_modules/")
indexAfterPackageName := moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules, isFolder)
if path[indexAfterNodeModules] == '@' {
indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName, isFolder)
}
return path[:indexAfterPackageName]
}
func ParsePackageName(moduleName string) (packageName, rest string) {
idx := strings.Index(moduleName, "/")
if len(moduleName) > 0 && moduleName[0] == '@' {
offset := idx + 1
idx = strings.Index(moduleName[offset:], "/")
if idx != -1 {
idx += offset
}
}
if idx == -1 {
return moduleName, ""
}
return moduleName[:idx], moduleName[idx+1:]
}
func MangleScopedPackageName(packageName string) string {
if len(packageName) > 0 && packageName[0] == '@' {
idx := strings.Index(packageName, "/")
if idx == -1 {
return packageName
}
return packageName[1:idx] + "__" + packageName[idx+1:]
}
return packageName
}
func UnmangleScopedPackageName(packageName string) string {
before, after, ok := strings.Cut(packageName, "__")
if ok {
return "@" + before + "/" + after
}
return packageName
}
func GetTypesPackageName(packageName string) string {
return "@types/" + MangleScopedPackageName(packageName)
}
func GetPackageNameFromTypesPackageName(mangledName string) string {
withoutAtTypePrefix := strings.TrimPrefix(mangledName, "@types/")
if withoutAtTypePrefix != mangledName {
return UnmangleScopedPackageName(withoutAtTypePrefix)
}
return mangledName
}
func ComparePatternKeys(a, b string) int {
aPatternIndex := strings.Index(a, "*")
bPatternIndex := strings.Index(b, "*")
baseLenA := len(a)
if aPatternIndex != -1 {
baseLenA = aPatternIndex + 1
}
baseLenB := len(b)
if bPatternIndex != -1 {
baseLenB = bPatternIndex + 1
}
if baseLenA > baseLenB {
return -1
}
if baseLenB > baseLenA {
return 1
}
if aPatternIndex == -1 {
return 1
}
if bPatternIndex == -1 {
return -1
}
if len(a) > len(b) {
return -1
}
if len(b) > len(a) {
return 1
}
return 0
}
// Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
// The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to.
// This returns a diagnostic even if the module will be an untyped module.
func GetResolutionDiagnostic(options *core.CompilerOptions, resolvedModule *ResolvedModule, file *ast.SourceFile) *diagnostics.Message {
needJsx := func() *diagnostics.Message {
if options.Jsx != core.JsxEmitNone {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set
}
needAllowJs := func() *diagnostics.Message {
if options.GetAllowJS() || !options.NoImplicitAny.DefaultIfUnknown(options.Strict).IsTrue() {
return nil
}
return diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type
}
needResolveJsonModule := func() *diagnostics.Message {
if options.GetResolveJsonModule() {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used
}
needAllowArbitraryExtensions := func() *diagnostics.Message {
if file.IsDeclarationFile || options.AllowArbitraryExtensions.IsTrue() {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_allowArbitraryExtensions_is_not_set
}
switch resolvedModule.Extension {
case tspath.ExtensionTs, tspath.ExtensionDts,
tspath.ExtensionMts, tspath.ExtensionDmts,
tspath.ExtensionCts, tspath.ExtensionDcts:
// These are always allowed.
return nil
case tspath.ExtensionTsx:
return needJsx()
case tspath.ExtensionJsx:
if message := needJsx(); message != nil {
return message
}
return needAllowJs()
case tspath.ExtensionJs, tspath.ExtensionMjs, tspath.ExtensionCjs:
return needAllowJs()
case tspath.ExtensionJson:
return needResolveJsonModule()
default:
return needAllowArbitraryExtensions()
}
}
// TryGetJSExtensionForFile maps TS/JS/DTS extensions to the output JS-side extension.
// Returns an empty string if the extension is unsupported.
func TryGetJSExtensionForFile(fileName string, options *core.CompilerOptions) string {
ext := tspath.TryGetExtensionFromPath(fileName)
switch ext {
case tspath.ExtensionTs, tspath.ExtensionDts:
return tspath.ExtensionJs
case tspath.ExtensionTsx:
if options.Jsx == core.JsxEmitPreserve {
return tspath.ExtensionJsx
}
return tspath.ExtensionJs
case tspath.ExtensionJs, tspath.ExtensionJsx, tspath.ExtensionJson:
return ext
case tspath.ExtensionDmts, tspath.ExtensionMts, tspath.ExtensionMjs:
return tspath.ExtensionMjs
case tspath.ExtensionDcts, tspath.ExtensionCts, tspath.ExtensionCjs:
return tspath.ExtensionCjs
default:
return ""
}
}