Skip to content

Commit 8beb55c

Browse files
chaseiisaduanandrewbranch
authored
Fix #1034: Improve symlink resolution in module specifier generation (#1902)
Co-authored-by: Isabel Duan <[email protected]> Co-authored-by: Andrew Branch <[email protected]>
1 parent 7451b04 commit 8beb55c

File tree

43 files changed

+1603
-279
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1603
-279
lines changed

internal/checker/nodebuilderimpl.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,10 @@ func (b *NodeBuilderImpl) getSpecifierForModuleSymbol(symbol *ast.Symbol, overri
12061206
},
12071207
false, /*forAutoImports*/
12081208
)
1209+
if len(allSpecifiers) == 0 {
1210+
links.specifierCache[cacheKey] = ""
1211+
return ""
1212+
}
12091213
specifier := allSpecifiers[0]
12101214
links.specifierCache[cacheKey] = specifier
12111215
return specifier

internal/compiler/emitHost.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import (
66
"github.com/microsoft/typescript-go/internal/ast"
77
"github.com/microsoft/typescript-go/internal/core"
88
"github.com/microsoft/typescript-go/internal/module"
9-
"github.com/microsoft/typescript-go/internal/modulespecifiers"
109
"github.com/microsoft/typescript-go/internal/outputpaths"
10+
"github.com/microsoft/typescript-go/internal/packagejson"
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"
@@ -70,7 +71,7 @@ func (host *emitHost) GetNearestAncestorDirectoryWithPackageJson(dirname string)
7071
return host.program.GetNearestAncestorDirectoryWithPackageJson(dirname)
7172
}
7273

73-
func (host *emitHost) GetPackageJsonInfo(pkgJsonPath string) modulespecifiers.PackageJsonInfo {
74+
func (host *emitHost) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
7475
return host.program.GetPackageJsonInfo(pkgJsonPath)
7576
}
7677

@@ -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/fileloader.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,14 +278,20 @@ func (p *fileLoader) getDefaultLibFilePriority(a *ast.SourceFile) int {
278278
}
279279

280280
func (p *fileLoader) loadSourceFileMetaData(fileName string) ast.SourceFileMetaData {
281-
packageJsonScope := p.resolver.GetPackageJsonScopeIfApplicable(fileName)
281+
packageJsonScope := p.resolver.GetPackageScopeForPath(fileName)
282+
moduleResolutionKind := p.opts.Config.CompilerOptions().GetModuleResolutionKind()
283+
282284
var packageJsonType, packageJsonDirectory string
283285
if packageJsonScope.Exists() {
284286
packageJsonDirectory = packageJsonScope.PackageDirectory
285287
if value, ok := packageJsonScope.Contents.Type.GetValue(); ok {
286-
packageJsonType = value
288+
if !tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMts, tspath.ExtensionCts, tspath.ExtensionMjs, tspath.ExtensionCjs}) &&
289+
core.ModuleResolutionKindNode16 <= moduleResolutionKind && moduleResolutionKind <= core.ModuleResolutionKindNodeNext || strings.Contains(fileName, "/node_modules/") {
290+
packageJsonType = value
291+
}
287292
}
288293
}
294+
289295
impliedNodeFormat := ast.GetImpliedNodeFormatForFile(fileName, packageJsonType)
290296
return ast.SourceFileMetaData{
291297
PackageJsonType: packageJsonType,

internal/compiler/knownsymlinks.go

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

internal/compiler/program.go

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ import (
1919
"github.com/microsoft/typescript-go/internal/diagnostics"
2020
"github.com/microsoft/typescript-go/internal/locale"
2121
"github.com/microsoft/typescript-go/internal/module"
22-
"github.com/microsoft/typescript-go/internal/modulespecifiers"
2322
"github.com/microsoft/typescript-go/internal/outputpaths"
23+
"github.com/microsoft/typescript-go/internal/packagejson"
2424
"github.com/microsoft/typescript-go/internal/parser"
2525
"github.com/microsoft/typescript-go/internal/printer"
2626
"github.com/microsoft/typescript-go/internal/scanner"
2727
"github.com/microsoft/typescript-go/internal/sourcemap"
28+
"github.com/microsoft/typescript-go/internal/symlinks"
2829
"github.com/microsoft/typescript-go/internal/tsoptions"
2930
"github.com/microsoft/typescript-go/internal/tspath"
3031
)
@@ -68,6 +69,8 @@ type Program struct {
6869
// Cached unresolved imports for ATA
6970
unresolvedImportsOnce sync.Once
7071
unresolvedImports *collections.Set[string]
72+
knownSymlinks *symlinks.KnownSymlinks
73+
knownSymlinksOnce sync.Once
7174

7275
// Used by workspace/symbol
7376
hasTSFileOnce sync.Once
@@ -99,7 +102,7 @@ func (p *Program) GetNearestAncestorDirectoryWithPackageJson(dirname string) str
99102
}
100103

101104
// GetPackageJsonInfo implements checker.Program.
102-
func (p *Program) GetPackageJsonInfo(pkgJsonPath string) modulespecifiers.PackageJsonInfo {
105+
func (p *Program) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
103106
scoped := p.resolver.GetPackageScopeForPath(pkgJsonPath)
104107
if scoped != nil && scoped.Exists() && scoped.PackageDirectory == tspath.GetDirectoryPath(pkgJsonPath) {
105108
return scoped
@@ -247,6 +250,7 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos
247250
programDiagnostics: p.programDiagnostics,
248251
hasEmitBlockingDiagnostics: p.hasEmitBlockingDiagnostics,
249252
unresolvedImports: p.unresolvedImports,
253+
knownSymlinks: p.knownSymlinks,
250254
}
251255
result.initCheckerPool()
252256
index := core.FindIndex(result.files, func(file *ast.SourceFile) bool { return file.Path() == newFile.Path() })
@@ -255,6 +259,10 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos
255259
result.filesByPath = maps.Clone(result.filesByPath)
256260
result.filesByPath[newFile.Path()] = newFile
257261
updateFileIncludeProcessor(result)
262+
result.knownSymlinks = symlinks.NewKnownSymlink(result.GetCurrentDirectory(), result.UseCaseSensitiveFileNames())
263+
if len(result.resolvedModules) > 0 || len(result.typeResolutionsInFile) > 0 {
264+
result.knownSymlinks.SetSymlinksFromResolutions(result.ForEachResolvedModule, result.ForEachResolvedTypeReferenceDirective)
265+
}
258266
return result, true
259267
}
260268

@@ -1597,6 +1605,86 @@ func (p *Program) HasTSFile() bool {
15971605
return p.hasTSFile
15981606
}
15991607

1608+
func (p *Program) GetSymlinkCache() *symlinks.KnownSymlinks {
1609+
p.knownSymlinksOnce.Do(func() {
1610+
if p.knownSymlinks == nil {
1611+
p.knownSymlinks = symlinks.NewKnownSymlink(p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
1612+
1613+
// Resolved modules store realpath information when they're resolved inside node_modules
1614+
if len(p.resolvedModules) > 0 || len(p.typeResolutionsInFile) > 0 {
1615+
p.knownSymlinks.SetSymlinksFromResolutions(p.ForEachResolvedModule, p.ForEachResolvedTypeReferenceDirective)
1616+
}
1617+
1618+
// Check other dependencies for symlinks
1619+
var seenPackageJsons collections.Set[tspath.Path]
1620+
for filePath, meta := range p.sourceFileMetaDatas {
1621+
if meta.PackageJsonDirectory == "" ||
1622+
!p.SourceFileMayBeEmitted(p.GetSourceFileByPath(filePath), false) ||
1623+
!seenPackageJsons.AddIfAbsent(p.toPath(meta.PackageJsonDirectory)) {
1624+
continue
1625+
}
1626+
packageJsonName := tspath.CombinePaths(meta.PackageJsonDirectory, "package.json")
1627+
info := p.GetPackageJsonInfo(packageJsonName)
1628+
if info.GetContents() == nil {
1629+
continue
1630+
}
1631+
1632+
for dep := range info.GetContents().GetRuntimeDependencyNames().Keys() {
1633+
// Skip work in common case: we already saved a symlink for this package directory
1634+
// in the node_modules adjacent to this package.json
1635+
possibleDirectoryPath := p.toPath(tspath.CombinePaths(meta.PackageJsonDirectory, "node_modules", dep))
1636+
if p.knownSymlinks.HasDirectory(possibleDirectoryPath) {
1637+
continue
1638+
}
1639+
if !strings.HasPrefix(dep, "@types") {
1640+
possibleTypesDirectoryPath := p.toPath(tspath.CombinePaths(meta.PackageJsonDirectory, "node_modules", module.GetTypesPackageName(dep)))
1641+
if p.knownSymlinks.HasDirectory(possibleTypesDirectoryPath) {
1642+
continue
1643+
}
1644+
}
1645+
1646+
if packageResolution := p.resolver.ResolvePackageDirectory(dep, packageJsonName, core.ResolutionModeCommonJS, nil); packageResolution.IsResolved() {
1647+
p.knownSymlinks.ProcessResolution(
1648+
tspath.CombinePaths(packageResolution.OriginalPath, "package.json"),
1649+
tspath.CombinePaths(packageResolution.ResolvedFileName, "package.json"),
1650+
)
1651+
}
1652+
}
1653+
}
1654+
}
1655+
})
1656+
return p.knownSymlinks
1657+
}
1658+
1659+
func (p *Program) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
1660+
resolved, _ := p.resolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil)
1661+
return resolved
1662+
}
1663+
1664+
func (p *Program) ForEachResolvedModule(callback func(resolution *module.ResolvedModule, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
1665+
forEachResolution(p.resolvedModules, callback, file)
1666+
}
1667+
1668+
func (p *Program) ForEachResolvedTypeReferenceDirective(callback func(resolution *module.ResolvedTypeReferenceDirective, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
1669+
forEachResolution(p.typeResolutionsInFile, callback, file)
1670+
}
1671+
1672+
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) {
1673+
if file != nil {
1674+
if resolutions, ok := resolutionCache[file.Path()]; ok {
1675+
for key, resolution := range resolutions {
1676+
callback(resolution, key.Name, key.Mode, file.Path())
1677+
}
1678+
}
1679+
} else {
1680+
for filePath, resolutions := range resolutionCache {
1681+
for key, resolution := range resolutions {
1682+
callback(resolution, key.Name, key.Mode, filePath)
1683+
}
1684+
}
1685+
}
1686+
}
1687+
16001688
var plainJSErrors = collections.NewSetFromItems(
16011689
// binder errors
16021690
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/fourslash/_scripts/failingTests.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ TestAutoImportJsDocImport1
1616
TestAutoImportModuleNone1
1717
TestAutoImportNodeNextJSRequire
1818
TestAutoImportPathsAliasesAndBarrels
19-
TestAutoImportPnpm
2019
TestAutoImportProvider_exportMap1
2120
TestAutoImportProvider_exportMap2
2221
TestAutoImportProvider_exportMap3
@@ -34,7 +33,6 @@ TestAutoImportProvider_wildcardExports2
3433
TestAutoImportProvider_wildcardExports3
3534
TestAutoImportProvider4
3635
TestAutoImportSortCaseSensitivity1
37-
TestAutoImportSymlinkCaseSensitive
3836
TestAutoImportTypeImport1
3937
TestAutoImportTypeImport2
4038
TestAutoImportTypeImport3
@@ -296,7 +294,6 @@ TestImportNameCodeFix_importType8
296294
TestImportNameCodeFix_jsx1
297295
TestImportNameCodeFix_order
298296
TestImportNameCodeFix_order2
299-
TestImportNameCodeFix_pnpm1
300297
TestImportNameCodeFix_preferBaseUrl
301298
TestImportNameCodeFix_reExportDefault
302299
TestImportNameCodeFix_symlink

internal/fourslash/tests/gen/autoImportPnpm_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func TestAutoImportPnpm(t *testing.T) {
1111
t.Parallel()
12-
t.Skip()
12+
1313
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
1414
const content = `// @Filename: /tsconfig.json
1515
{ "compilerOptions": { "module": "commonjs" } }

internal/fourslash/tests/gen/autoImportSymlinkCaseSensitive_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func TestAutoImportSymlinkCaseSensitive(t *testing.T) {
1111
t.Parallel()
12-
t.Skip()
12+
1313
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
1414
const content = `// @Filename: /tsconfig.json
1515
{ "compilerOptions": { "module": "commonjs" } }

internal/fourslash/tests/gen/importNameCodeFix_pnpm1_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func TestImportNameCodeFix_pnpm1(t *testing.T) {
1111
t.Parallel()
12-
t.Skip()
12+
1313
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
1414
const content = `// @Filename: /home/src/workspaces/project/tsconfig.json
1515
{ "compilerOptions": { "module": "commonjs" } }

0 commit comments

Comments
 (0)