Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,10 @@ func (b *NodeBuilderImpl) getSpecifierForModuleSymbol(symbol *ast.Symbol, overri
},
false, /*forAutoImports*/
)
if len(allSpecifiers) == 0 {
links.specifierCache[cacheKey] = ""
return ""
}
specifier := allSpecifiers[0]
links.specifierCache[cacheKey] = specifier
return specifier
Expand Down
14 changes: 12 additions & 2 deletions internal/compiler/emitHost.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/symlinks"
"github.com/microsoft/typescript-go/internal/transformers/declarations"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
Expand Down Expand Up @@ -70,7 +71,7 @@ func (host *emitHost) GetNearestAncestorDirectoryWithPackageJson(dirname string)
return host.program.GetNearestAncestorDirectoryWithPackageJson(dirname)
}

func (host *emitHost) GetPackageJsonInfo(pkgJsonPath string) modulespecifiers.PackageJsonInfo {
func (host *emitHost) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
return host.program.GetPackageJsonInfo(pkgJsonPath)
}

Expand Down Expand Up @@ -126,3 +127,12 @@ func (host *emitHost) GetEmitResolver() printer.EmitResolver {
func (host *emitHost) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool {
return host.program.IsSourceFileFromExternalLibrary(file)
}

func (host *emitHost) GetSymlinkCache() *symlinks.KnownSymlinks {
return host.program.GetSymlinkCache()
}

func (host *emitHost) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
resolved, _ := host.program.resolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil)
return resolved
}
10 changes: 8 additions & 2 deletions internal/compiler/fileloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,20 @@ func (p *fileLoader) getDefaultLibFilePriority(a *ast.SourceFile) int {
}

func (p *fileLoader) loadSourceFileMetaData(fileName string) ast.SourceFileMetaData {
packageJsonScope := p.resolver.GetPackageJsonScopeIfApplicable(fileName)
packageJsonScope := p.resolver.GetPackageScopeForPath(fileName)
moduleResolutionKind := p.opts.Config.CompilerOptions().GetModuleResolutionKind()

var packageJsonType, packageJsonDirectory string
if packageJsonScope.Exists() {
packageJsonDirectory = packageJsonScope.PackageDirectory
if value, ok := packageJsonScope.Contents.Type.GetValue(); ok {
packageJsonType = value
if !tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMts, tspath.ExtensionCts, tspath.ExtensionMjs, tspath.ExtensionCjs}) &&
core.ModuleResolutionKindNode16 <= moduleResolutionKind && moduleResolutionKind <= core.ModuleResolutionKindNodeNext || strings.Contains(fileName, "/node_modules/") {
packageJsonType = value
}
}
}

impliedNodeFormat := ast.GetImpliedNodeFormatForFile(fileName, packageJsonType)
return ast.SourceFileMetaData{
PackageJsonType: packageJsonType,
Expand Down
53 changes: 0 additions & 53 deletions internal/compiler/knownsymlinks.go

This file was deleted.

92 changes: 90 additions & 2 deletions internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/parser"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/sourcemap"
"github.com/microsoft/typescript-go/internal/symlinks"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)
Expand Down Expand Up @@ -66,6 +67,8 @@ type Program struct {
// Cached unresolved imports for ATA
unresolvedImportsOnce sync.Once
unresolvedImports *collections.Set[string]
knownSymlinks *symlinks.KnownSymlinks
knownSymlinksOnce sync.Once
}

// FileExists implements checker.Program.
Expand Down Expand Up @@ -93,7 +96,7 @@ func (p *Program) GetNearestAncestorDirectoryWithPackageJson(dirname string) str
}

// GetPackageJsonInfo implements checker.Program.
func (p *Program) GetPackageJsonInfo(pkgJsonPath string) modulespecifiers.PackageJsonInfo {
func (p *Program) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
scoped := p.resolver.GetPackageScopeForPath(pkgJsonPath)
if scoped != nil && scoped.Exists() && scoped.PackageDirectory == tspath.GetDirectoryPath(pkgJsonPath) {
return scoped
Expand Down Expand Up @@ -232,6 +235,7 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos
programDiagnostics: p.programDiagnostics,
hasEmitBlockingDiagnostics: p.hasEmitBlockingDiagnostics,
unresolvedImports: p.unresolvedImports,
knownSymlinks: p.knownSymlinks,
}
result.initCheckerPool()
index := core.FindIndex(result.files, func(file *ast.SourceFile) bool { return file.Path() == newFile.Path() })
Expand All @@ -240,6 +244,10 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos
result.filesByPath = maps.Clone(result.filesByPath)
result.filesByPath[newFile.Path()] = newFile
updateFileIncludeProcessor(result)
result.knownSymlinks = symlinks.NewKnownSymlink(result.GetCurrentDirectory(), result.UseCaseSensitiveFileNames())
if len(result.resolvedModules) > 0 || len(result.typeResolutionsInFile) > 0 {
result.knownSymlinks.SetSymlinksFromResolutions(result.ForEachResolvedModule, result.ForEachResolvedTypeReferenceDirective)
}
return result, true
}

Expand Down Expand Up @@ -1635,6 +1643,86 @@ func (p *Program) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmi
return sourceFileMayBeEmitted(sourceFile, p, forceDtsEmit)
}

func (p *Program) GetSymlinkCache() *symlinks.KnownSymlinks {
p.knownSymlinksOnce.Do(func() {
if p.knownSymlinks == nil {
p.knownSymlinks = symlinks.NewKnownSymlink(p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())

// Resolved modules store realpath information when they're resolved inside node_modules
if len(p.resolvedModules) > 0 || len(p.typeResolutionsInFile) > 0 {
p.knownSymlinks.SetSymlinksFromResolutions(p.ForEachResolvedModule, p.ForEachResolvedTypeReferenceDirective)
}

// Check other dependencies for symlinks
var seenPackageJsons collections.Set[tspath.Path]
for filePath, meta := range p.sourceFileMetaDatas {
if meta.PackageJsonDirectory == "" ||
!p.SourceFileMayBeEmitted(p.GetSourceFileByPath(filePath), false) ||
!seenPackageJsons.AddIfAbsent(p.toPath(meta.PackageJsonDirectory)) {
continue
}
packageJsonName := tspath.CombinePaths(meta.PackageJsonDirectory, "package.json")
info := p.GetPackageJsonInfo(packageJsonName)
if info.GetContents() == nil {
continue
}

for dep := range info.GetContents().GetRuntimeDependencyNames().Keys() {
// Skip work in common case: we already saved a symlink for this package directory
// in the node_modules adjacent to this package.json
possibleDirectoryPath := p.toPath(tspath.CombinePaths(meta.PackageJsonDirectory, "node_modules", dep))
if p.knownSymlinks.HasDirectory(possibleDirectoryPath) {
continue
}
if !strings.HasPrefix(dep, "@types") {
possibleTypesDirectoryPath := p.toPath(tspath.CombinePaths(meta.PackageJsonDirectory, "node_modules", module.GetTypesPackageName(dep)))
if p.knownSymlinks.HasDirectory(possibleTypesDirectoryPath) {
continue
}
}

if packageResolution := p.resolver.ResolvePackageDirectory(dep, packageJsonName, core.ResolutionModeCommonJS, nil); packageResolution.IsResolved() {
p.knownSymlinks.ProcessResolution(
tspath.CombinePaths(packageResolution.OriginalPath, "package.json"),
tspath.CombinePaths(packageResolution.ResolvedFileName, "package.json"),
)
}
}
}
}
})
return p.knownSymlinks
}

func (p *Program) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
resolved, _ := p.resolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil)
return resolved
}

func (p *Program) ForEachResolvedModule(callback func(resolution *module.ResolvedModule, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
forEachResolution(p.resolvedModules, callback, file)
}

func (p *Program) ForEachResolvedTypeReferenceDirective(callback func(resolution *module.ResolvedTypeReferenceDirective, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
forEachResolution(p.typeResolutionsInFile, callback, file)
}

func forEachResolution[T any](resolutionCache map[tspath.Path]module.ModeAwareCache[T], callback func(resolution T, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
if file != nil {
if resolutions, ok := resolutionCache[file.Path()]; ok {
for key, resolution := range resolutions {
callback(resolution, key.Name, key.Mode, file.Path())
}
}
} else {
for filePath, resolutions := range resolutionCache {
for key, resolution := range resolutions {
callback(resolution, key.Name, key.Mode, filePath)
}
}
}
}

var plainJSErrors = collections.NewSetFromItems(
// binder errors
diagnostics.Cannot_redeclare_block_scoped_variable_0.Code(),
Expand Down
9 changes: 5 additions & 4 deletions internal/compiler/projectreferencedtsfakinghost.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/symlinks"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/microsoft/typescript-go/internal/vfs/cachedvfs"
Expand All @@ -26,7 +27,7 @@ func newProjectReferenceDtsFakingHost(loader *fileLoader) module.ResolutionHost
fs: cachedvfs.From(&projectReferenceDtsFakingVfs{
projectReferenceFileMapper: loader.projectReferenceFileMapper,
dtsDirectories: loader.dtsDirectories,
knownSymlinks: knownSymlinks{},
knownSymlinks: symlinks.KnownSymlinks{},
}),
}
return host
Expand All @@ -45,7 +46,7 @@ func (h *projectReferenceDtsFakingHost) GetCurrentDirectory() string {
type projectReferenceDtsFakingVfs struct {
projectReferenceFileMapper *projectReferenceFileMapper
dtsDirectories collections.Set[tspath.Path]
knownSymlinks knownSymlinks
knownSymlinks symlinks.KnownSymlinks
}

var _ vfs.FS = (*projectReferenceDtsFakingVfs)(nil)
Expand Down Expand Up @@ -150,7 +151,7 @@ func (fs *projectReferenceDtsFakingVfs) handleDirectoryCouldBeSymlink(directory
// not symlinked
return
}
fs.knownSymlinks.SetDirectory(directory, directoryPath, &knownDirectoryLink{
fs.knownSymlinks.SetDirectory(directory, directoryPath, &symlinks.KnownDirectoryLink{
Real: tspath.EnsureTrailingDirectorySeparator(realDirectory),
RealPath: realPath,
})
Expand Down Expand Up @@ -181,7 +182,7 @@ func (fs *projectReferenceDtsFakingVfs) fileOrDirectoryExistsUsingSource(fileOrD

// If it contains node_modules check if its one of the symlinked path we know of
var exists bool
knownDirectoryLinks.Range(func(directoryPath tspath.Path, knownDirectoryLink *knownDirectoryLink) bool {
knownDirectoryLinks.Range(func(directoryPath tspath.Path, knownDirectoryLink *symlinks.KnownDirectoryLink) bool {
relative, hasPrefix := strings.CutPrefix(string(fileOrDirectoryPath), string(directoryPath))
if !hasPrefix {
return true
Expand Down
51 changes: 30 additions & 21 deletions internal/module/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,15 @@ type resolutionState struct {
tracer *tracer

// request fields
name string
containingDirectory string
isConfigLookup bool
features NodeResolutionFeatures
esmMode bool
conditions []string
extensions extensions
compilerOptions *core.CompilerOptions
name string
containingDirectory string
isConfigLookup bool
features NodeResolutionFeatures
esmMode bool
conditions []string
extensions extensions
compilerOptions *core.CompilerOptions
resolvePackageDirectoryOnly bool

// state fields
candidateIsFromPackageJsonField bool
Expand Down Expand Up @@ -177,19 +178,6 @@ func (r *Resolver) GetPackageScopeForPath(directory string) *packagejson.InfoCac
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()))
Expand Down Expand Up @@ -266,6 +254,17 @@ func (r *Resolver) ResolveModuleName(moduleName string, containingFile string, r
return r.tryResolveFromTypingsLocation(moduleName, containingDirectory, result, traceBuilder), traceBuilder.getTraces()
}

func (r *Resolver) ResolvePackageDirectory(moduleName string, containingFile string, resolutionMode core.ResolutionMode, redirectedReference ResolvedProjectReference) *ResolvedModule {
compilerOptions := GetCompilerOptionsWithRedirect(r.compilerOptions, redirectedReference)
containingDirectory := tspath.GetDirectoryPath(containingFile)
state := newResolutionState(moduleName, containingDirectory, false /*isTypeReferenceDirective*/, resolutionMode, compilerOptions, redirectedReference, r, nil)
state.resolvePackageDirectoryOnly = true
if result := state.loadModuleFromNearestNodeModulesDirectory(false /*typesScopeOnly*/); result != nil && result.path != "" {
return state.createResolvedModuleHandlingSymlink(result)
}
return nil
}

func (r *Resolver) tryResolveFromTypingsLocation(moduleName string, containingDirectory string, originalResult *ResolvedModule, traceBuilder *tracer) *ResolvedModule {
if r.typingsLocation == "" ||
tspath.IsExternalModuleNameRelative(moduleName) ||
Expand Down Expand Up @@ -956,6 +955,16 @@ func (r *resolutionState) loadModuleFromSpecificNodeModulesDirectory(ext extensi
candidate := tspath.NormalizePath(tspath.CombinePaths(nodeModulesDirectory, moduleName))
packageName, rest := ParsePackageName(moduleName)
packageDirectory := tspath.CombinePaths(nodeModulesDirectory, packageName)
if packageName == "" {
packageDirectory = candidate
}

if r.resolvePackageDirectoryOnly {
if r.resolver.host.FS().DirectoryExists(packageDirectory) {
return &resolved{path: packageDirectory}
}
return continueSearching()
}

var rootPackageInfo *packagejson.InfoCacheEntry
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`
Expand Down
Loading