Skip to content

Commit 83c7cc2

Browse files
shinichychase
authored andcommitted
Improve symlink resolution in module specifier generation
Extends the symlink support in GetEachFileNameOfModule to properly resolve module specifiers across symlinked packages and workspaces. Key changes: - Move knownsymlinks from compiler to dedicated symlinks package - Implement active resolution via ResolveModuleName to populate cache - Add dependency resolution from package.json to detect symlinks early - Improve ignored path handling (node_modules/., .git, .# emacs locks) - Add comprehensive test coverage for symlink resolution - Fix declaration emit to prefer original paths over symlink paths This aligns with upstream TypeScript's symlink resolution behavior, ensuring correct module specifiers in declaration files for monorepos and symlinked dependencies. Fixes baseline mismatches in: - declarationEmitReexportedSymlinkReference2/3 - symlinkedWorkspaceDependencies* tests - nodeModuleReexportFromDottedPath
1 parent 7a4a79f commit 83c7cc2

File tree

32 files changed

+1317
-225
lines changed

32 files changed

+1317
-225
lines changed

internal/checker/nodebuilderimpl.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,10 @@ func (b *nodeBuilderImpl) getSpecifierForModuleSymbol(symbol *ast.Symbol, overri
12151215
},
12161216
false, /*forAutoImports*/
12171217
)
1218+
if len(allSpecifiers) == 0 {
1219+
links.specifierCache[cacheKey] = ""
1220+
return ""
1221+
}
12181222
specifier := allSpecifiers[0]
12191223
links.specifierCache[cacheKey] = specifier
12201224
return specifier

internal/compiler/emitHost.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/microsoft/typescript-go/internal/modulespecifiers"
1010
"github.com/microsoft/typescript-go/internal/outputpaths"
1111
"github.com/microsoft/typescript-go/internal/printer"
12+
"github.com/microsoft/typescript-go/internal/symlinks"
1213
"github.com/microsoft/typescript-go/internal/transformers/declarations"
1314
"github.com/microsoft/typescript-go/internal/tsoptions"
1415
"github.com/microsoft/typescript-go/internal/tspath"
@@ -126,3 +127,12 @@ func (host *emitHost) GetEmitResolver() printer.EmitResolver {
126127
func (host *emitHost) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool {
127128
return host.program.IsSourceFileFromExternalLibrary(file)
128129
}
130+
131+
func (host *emitHost) GetSymlinkCache() *symlinks.KnownSymlinks {
132+
return host.program.GetSymlinkCache()
133+
}
134+
135+
func (host *emitHost) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
136+
resolved, _ := host.program.resolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil)
137+
return resolved
138+
}

internal/compiler/knownsymlinks.go

Lines changed: 0 additions & 53 deletions
This file was deleted.

internal/compiler/program.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/microsoft/typescript-go/internal/printer"
2424
"github.com/microsoft/typescript-go/internal/scanner"
2525
"github.com/microsoft/typescript-go/internal/sourcemap"
26+
"github.com/microsoft/typescript-go/internal/symlinks"
2627
"github.com/microsoft/typescript-go/internal/tsoptions"
2728
"github.com/microsoft/typescript-go/internal/tspath"
2829
)
@@ -66,6 +67,7 @@ type Program struct {
6667
// Cached unresolved imports for ATA
6768
unresolvedImportsOnce sync.Once
6869
unresolvedImports *collections.Set[string]
70+
knownSymlinks *symlinks.KnownSymlinks
6971
}
7072

7173
// FileExists implements checker.Program.
@@ -210,6 +212,10 @@ func NewProgram(opts ProgramOptions) *Program {
210212
p.initCheckerPool()
211213
p.processedFiles = processAllProgramFiles(p.opts, p.SingleThreaded())
212214
p.verifyCompilerOptions()
215+
p.knownSymlinks = symlinks.NewKnownSymlink(p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
216+
if len(p.resolvedModules) > 0 || len(p.typeResolutionsInFile) > 0 {
217+
p.knownSymlinks.SetSymlinksFromResolutions(p.ForEachResolvedModule, p.ForEachResolvedTypeReferenceDirective)
218+
}
213219
return p
214220
}
215221

@@ -240,6 +246,10 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos
240246
result.filesByPath = maps.Clone(result.filesByPath)
241247
result.filesByPath[newFile.Path()] = newFile
242248
updateFileIncludeProcessor(result)
249+
result.knownSymlinks = symlinks.NewKnownSymlink(result.GetCurrentDirectory(), result.UseCaseSensitiveFileNames())
250+
if len(result.resolvedModules) > 0 || len(result.typeResolutionsInFile) > 0 {
251+
result.knownSymlinks.SetSymlinksFromResolutions(result.ForEachResolvedModule, result.ForEachResolvedTypeReferenceDirective)
252+
}
243253
return result, true
244254
}
245255

@@ -1630,6 +1640,45 @@ func (p *Program) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmi
16301640
return sourceFileMayBeEmitted(sourceFile, p, forceDtsEmit)
16311641
}
16321642

1643+
func (p *Program) GetSymlinkCache() *symlinks.KnownSymlinks {
1644+
// if p.Host().GetSymlinkCache() != nil {
1645+
// return p.Host().GetSymlinkCache()
1646+
// }
1647+
if p.knownSymlinks == nil {
1648+
p.knownSymlinks = symlinks.NewKnownSymlink(p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
1649+
}
1650+
return p.knownSymlinks
1651+
}
1652+
1653+
func (p *Program) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
1654+
resolved, _ := p.resolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil)
1655+
return resolved
1656+
}
1657+
1658+
func (p *Program) ForEachResolvedModule(callback func(resolution *module.ResolvedModule, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
1659+
forEachResolution(p.resolvedModules, callback, file)
1660+
}
1661+
1662+
func (p *Program) ForEachResolvedTypeReferenceDirective(callback func(resolution *module.ResolvedTypeReferenceDirective, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
1663+
forEachResolution(p.typeResolutionsInFile, callback, file)
1664+
}
1665+
1666+
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) {
1667+
if file != nil {
1668+
if resolutions, ok := resolutionCache[file.Path()]; ok {
1669+
for key, resolution := range resolutions {
1670+
callback(resolution, key.Name, key.Mode, file.Path())
1671+
}
1672+
}
1673+
} else {
1674+
for filePath, resolutions := range resolutionCache {
1675+
for key, resolution := range resolutions {
1676+
callback(resolution, key.Name, key.Mode, filePath)
1677+
}
1678+
}
1679+
}
1680+
}
1681+
16331682
var plainJSErrors = collections.NewSetFromItems(
16341683
// binder errors
16351684
diagnostics.Cannot_redeclare_block_scoped_variable_0.Code(),

internal/compiler/projectreferencedtsfakinghost.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/microsoft/typescript-go/internal/collections"
88
"github.com/microsoft/typescript-go/internal/core"
99
"github.com/microsoft/typescript-go/internal/module"
10+
"github.com/microsoft/typescript-go/internal/symlinks"
1011
"github.com/microsoft/typescript-go/internal/tspath"
1112
"github.com/microsoft/typescript-go/internal/vfs"
1213
"github.com/microsoft/typescript-go/internal/vfs/cachedvfs"
@@ -26,7 +27,7 @@ func newProjectReferenceDtsFakingHost(loader *fileLoader) module.ResolutionHost
2627
fs: cachedvfs.From(&projectReferenceDtsFakingVfs{
2728
projectReferenceFileMapper: loader.projectReferenceFileMapper,
2829
dtsDirectories: loader.dtsDirectories,
29-
knownSymlinks: knownSymlinks{},
30+
knownSymlinks: symlinks.KnownSymlinks{},
3031
}),
3132
}
3233
return host
@@ -45,7 +46,7 @@ func (h *projectReferenceDtsFakingHost) GetCurrentDirectory() string {
4546
type projectReferenceDtsFakingVfs struct {
4647
projectReferenceFileMapper *projectReferenceFileMapper
4748
dtsDirectories collections.Set[tspath.Path]
48-
knownSymlinks knownSymlinks
49+
knownSymlinks symlinks.KnownSymlinks
4950
}
5051

5152
var _ vfs.FS = (*projectReferenceDtsFakingVfs)(nil)
@@ -150,7 +151,7 @@ func (fs *projectReferenceDtsFakingVfs) handleDirectoryCouldBeSymlink(directory
150151
// not symlinked
151152
return
152153
}
153-
fs.knownSymlinks.SetDirectory(directory, directoryPath, &knownDirectoryLink{
154+
fs.knownSymlinks.SetDirectory(directory, directoryPath, &symlinks.KnownDirectoryLink{
154155
Real: tspath.EnsureTrailingDirectorySeparator(realDirectory),
155156
RealPath: realPath,
156157
})
@@ -181,7 +182,7 @@ func (fs *projectReferenceDtsFakingVfs) fileOrDirectoryExistsUsingSource(fileOrD
181182

182183
// If it contains node_modules check if its one of the symlinked path we know of
183184
var exists bool
184-
knownDirectoryLinks.Range(func(directoryPath tspath.Path, knownDirectoryLink *knownDirectoryLink) bool {
185+
knownDirectoryLinks.Range(func(directoryPath tspath.Path, knownDirectoryLink *symlinks.KnownDirectoryLink) bool {
185186
relative, hasPrefix := strings.CutPrefix(string(fileOrDirectoryPath), string(directoryPath))
186187
if !hasPrefix {
187188
return true

internal/ls/autoimports.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ func (e *exportInfoMap) add(
164164
}
165165

166166
moduleName := stringutil.StripQuotes(moduleSymbol.Name)
167-
id := e.exportInfoId + 1
167+
id := e.exportInfoId
168+
e.exportInfoId += 1
168169
target := ch.SkipAlias(symbol)
169170

170171
if flagMatch != nil && !flagMatch(target.Flags) {

0 commit comments

Comments
 (0)