Skip to content

Commit acba540

Browse files
authored
Typings Intaller initial work (#844)
1 parent dd11489 commit acba540

30 files changed

+3788
-227
lines changed

cmd/tsgo/lsp.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import (
44
"flag"
55
"fmt"
66
"os"
7+
"runtime"
78

89
"github.com/microsoft/typescript-go/internal/bundled"
910
"github.com/microsoft/typescript-go/internal/core"
1011
"github.com/microsoft/typescript-go/internal/lsp"
1112
"github.com/microsoft/typescript-go/internal/pprof"
13+
"github.com/microsoft/typescript-go/internal/tspath"
1214
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
1315
)
1416

@@ -37,6 +39,7 @@ func runLSP(args []string) int {
3739

3840
fs := bundled.WrapFS(osvfs.FS())
3941
defaultLibraryPath := bundled.LibPath()
42+
typingsLocation := getGlobalTypingsCacheLocation()
4043

4144
s := lsp.NewServer(&lsp.ServerOptions{
4245
In: os.Stdin,
@@ -45,10 +48,78 @@ func runLSP(args []string) int {
4548
Cwd: core.Must(os.Getwd()),
4649
FS: fs,
4750
DefaultLibraryPath: defaultLibraryPath,
51+
TypingsLocation: typingsLocation,
4852
})
4953

5054
if err := s.Run(); err != nil {
5155
return 1
5256
}
5357
return 0
5458
}
59+
60+
func getGlobalTypingsCacheLocation() string {
61+
switch runtime.GOOS {
62+
case "windows":
63+
return tspath.CombinePaths(tspath.CombinePaths(getWindowsCacheLocation(), "Microsoft/TypeScript"), core.VersionMajorMinor())
64+
case "openbsd", "freebsd", "netbsd", "darwin", "linux", "android":
65+
return tspath.CombinePaths(tspath.CombinePaths(getNonWindowsCacheLocation(), "typescript"), core.VersionMajorMinor())
66+
default:
67+
panic("unsupported platform: " + runtime.GOOS)
68+
}
69+
}
70+
71+
func getWindowsCacheLocation() string {
72+
basePath, err := os.UserCacheDir()
73+
if err != nil {
74+
if basePath, err = os.UserConfigDir(); err != nil {
75+
if basePath, err = os.UserHomeDir(); err != nil {
76+
if userProfile := os.Getenv("USERPROFILE"); userProfile != "" {
77+
basePath = userProfile
78+
} else if homeDrive, homePath := os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"); homeDrive != "" && homePath != "" {
79+
basePath = homeDrive + homePath
80+
} else {
81+
basePath = os.TempDir()
82+
}
83+
}
84+
}
85+
}
86+
return basePath
87+
}
88+
89+
func getNonWindowsCacheLocation() string {
90+
if xdgCacheHome := os.Getenv("XDG_CACHE_HOME"); xdgCacheHome != "" {
91+
return xdgCacheHome
92+
}
93+
const platformIsDarwin = runtime.GOOS == "darwin"
94+
var usersDir string
95+
if platformIsDarwin {
96+
usersDir = "Users"
97+
} else {
98+
usersDir = "home"
99+
}
100+
homePath, err := os.UserHomeDir()
101+
if err != nil {
102+
if home := os.Getenv("HOME"); home != "" {
103+
homePath = home
104+
} else {
105+
var userName string
106+
if logName := os.Getenv("LOGNAME"); logName != "" {
107+
userName = logName
108+
} else if user := os.Getenv("USER"); user != "" {
109+
userName = user
110+
}
111+
if userName != "" {
112+
homePath = "/" + usersDir + "/" + userName
113+
} else {
114+
homePath = os.TempDir()
115+
}
116+
}
117+
}
118+
var cacheFolder string
119+
if platformIsDarwin {
120+
cacheFolder = "Library/Caches"
121+
} else {
122+
cacheFolder = ".cache"
123+
}
124+
return tspath.CombinePaths(homePath, cacheFolder)
125+
}

internal/api/api.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ func (api *API) DefaultLibraryPath() string {
7373
return api.host.DefaultLibraryPath()
7474
}
7575

76+
// TypingsInstaller implements ProjectHost
77+
func (api *API) TypingsInstaller() *project.TypingsInstaller {
78+
return nil
79+
}
80+
7681
// DocumentRegistry implements ProjectHost.
7782
func (api *API) DocumentRegistry() *project.DocumentRegistry {
7883
return api.documentRegistry

internal/compiler/program.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type ProgramOptions struct {
2828
ProjectReference []core.ProjectReference
2929
ConfigFileParsingDiagnostics []*ast.Diagnostic
3030
CreateCheckerPool func(*Program) CheckerPool
31+
32+
TypingsLocation string
33+
ProjectName string
3134
}
3235

3336
type Program struct {
@@ -189,7 +192,7 @@ func NewProgram(options ProgramOptions) *Program {
189192
// tracing?.push(tracing.Phase.Program, "createProgram", { configFilePath: options.configFilePath, rootDir: options.rootDir }, /*separateBeginAndEnd*/ true);
190193
// performance.mark("beforeProgram");
191194

192-
p.resolver = module.NewResolver(p.host, p.compilerOptions)
195+
p.resolver = module.NewResolver(p.host, p.compilerOptions, p.programOptions.TypingsLocation, p.programOptions.ProjectName)
193196

194197
var libs []string
195198

internal/core/nodemodules.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package core
2+
3+
import (
4+
"maps"
5+
"sync"
6+
)
7+
8+
var UnprefixedNodeCoreModules = map[string]bool{
9+
"assert": true,
10+
"assert/strict": true,
11+
"async_hooks": true,
12+
"buffer": true,
13+
"child_process": true,
14+
"cluster": true,
15+
"console": true,
16+
"constants": true,
17+
"crypto": true,
18+
"dgram": true,
19+
"diagnostics_channel": true,
20+
"dns": true,
21+
"dns/promises": true,
22+
"domain": true,
23+
"events": true,
24+
"fs": true,
25+
"fs/promises": true,
26+
"http": true,
27+
"http2": true,
28+
"https": true,
29+
"inspector": true,
30+
"inspector/promises": true,
31+
"module": true,
32+
"net": true,
33+
"os": true,
34+
"path": true,
35+
"path/posix": true,
36+
"path/win32": true,
37+
"perf_hooks": true,
38+
"process": true,
39+
"punycode": true,
40+
"querystring": true,
41+
"readline": true,
42+
"readline/promises": true,
43+
"repl": true,
44+
"stream": true,
45+
"stream/consumers": true,
46+
"stream/promises": true,
47+
"stream/web": true,
48+
"string_decoder": true,
49+
"sys": true,
50+
"test/mock_loader": true,
51+
"timers": true,
52+
"timers/promises": true,
53+
"tls": true,
54+
"trace_events": true,
55+
"tty": true,
56+
"url": true,
57+
"util": true,
58+
"util/types": true,
59+
"v8": true,
60+
"vm": true,
61+
"wasi": true,
62+
"worker_threads": true,
63+
"zlib": true,
64+
}
65+
66+
var ExclusivelyPrefixedNodeCoreModules = map[string]bool{
67+
"node:sea": true,
68+
"node:sqlite": true,
69+
"node:test": true,
70+
"node:test/reporters": true,
71+
}
72+
73+
var nodeCoreModules = sync.OnceValue(func() map[string]bool {
74+
nodeCoreModules := make(map[string]bool, len(UnprefixedNodeCoreModules)*2+len(ExclusivelyPrefixedNodeCoreModules))
75+
for unprefixed := range UnprefixedNodeCoreModules {
76+
nodeCoreModules[unprefixed] = true
77+
nodeCoreModules["node:"+unprefixed] = true
78+
}
79+
maps.Copy(nodeCoreModules, ExclusivelyPrefixedNodeCoreModules)
80+
return nodeCoreModules
81+
})
82+
83+
func NonRelativeModuleNameForTypingCache(moduleName string) string {
84+
if nodeCoreModules()[moduleName] {
85+
return "node"
86+
}
87+
return moduleName
88+
}

internal/ls/completions_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2029,7 +2029,7 @@ func assertIncludesItem(t *testing.T, actual *lsproto.CompletionList, expected *
20292029
}
20302030

20312031
func createLanguageService(ctx context.Context, fileName string, files map[string]any) (*ls.LanguageService, func()) {
2032-
projectService, _ := projecttestutil.Setup(files)
2032+
projectService, _ := projecttestutil.Setup(files, nil)
20332033
projectService.OpenFile(fileName, files[fileName].(string), core.ScriptKindTS, "")
20342034
project := projectService.Projects()[0]
20352035
return project.GetLanguageServiceForRequest(ctx)

internal/lsp/server.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type ServerOptions struct {
3030
NewLine core.NewLineKind
3131
FS vfs.FS
3232
DefaultLibraryPath string
33+
TypingsLocation string
3334
}
3435

3536
func NewServer(opts *ServerOptions) *Server {
@@ -48,6 +49,7 @@ func NewServer(opts *ServerOptions) *Server {
4849
newLine: opts.NewLine,
4950
fs: opts.FS,
5051
defaultLibraryPath: opts.DefaultLibraryPath,
52+
typingsLocation: opts.TypingsLocation,
5153
}
5254
}
5355

@@ -79,6 +81,7 @@ type Server struct {
7981
newLine core.NewLineKind
8082
fs vfs.FS
8183
defaultLibraryPath string
84+
typingsLocation string
8285

8386
initializeParams *lsproto.InitializeParams
8487
positionEncoding lsproto.PositionEncodingKind
@@ -100,6 +103,11 @@ func (s *Server) DefaultLibraryPath() string {
100103
return s.defaultLibraryPath
101104
}
102105

106+
// TypingsLocation implements project.ServiceHost.
107+
func (s *Server) TypingsLocation() string {
108+
return s.typingsLocation
109+
}
110+
103111
// GetCurrentDirectory implements project.ServiceHost.
104112
func (s *Server) GetCurrentDirectory() string {
105113
return s.cwd
@@ -477,6 +485,10 @@ func (s *Server) handleInitialized(ctx context.Context, req *lsproto.RequestMess
477485
Logger: s.logger,
478486
WatchEnabled: s.watchEnabled,
479487
PositionEncoding: s.positionEncoding,
488+
TypingsInstallerOptions: project.TypingsInstallerOptions{
489+
ThrottleLimit: 5,
490+
NpmInstall: project.NpmInstall,
491+
},
480492
})
481493

482494
return nil

internal/module/resolver.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,23 @@ type Resolver struct {
109109
caches
110110
host ResolutionHost
111111
compilerOptions *core.CompilerOptions
112+
typingsLocation string
113+
projectName string
112114
// reportDiagnostic: DiagnosticReporter
113115
}
114116

115117
func NewResolver(
116118
host ResolutionHost,
117119
options *core.CompilerOptions,
120+
typingsLocation string,
121+
projectName string,
118122
) *Resolver {
119123
return &Resolver{
120124
host: host,
121125
caches: newCaches(host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames(), options),
122126
compilerOptions: options,
127+
typingsLocation: typingsLocation,
128+
projectName: projectName,
123129
}
124130
}
125131

@@ -229,6 +235,36 @@ func (r *Resolver) ResolveModuleName(moduleName string, containingFile string, r
229235
}
230236
}
231237

238+
return r.tryResolveFromTypingsLocation(moduleName, containingDirectory, result)
239+
}
240+
241+
func (r *Resolver) tryResolveFromTypingsLocation(moduleName string, containingDirectory string, originalResult *ResolvedModule) *ResolvedModule {
242+
if r.typingsLocation == "" ||
243+
tspath.IsExternalModuleNameRelative(moduleName) ||
244+
(originalResult.ResolvedFileName != "" && tspath.ExtensionIsOneOf(originalResult.Extension, tspath.SupportedTSExtensionsWithJsonFlat)) {
245+
return originalResult
246+
}
247+
248+
state := newResolutionState(
249+
moduleName,
250+
containingDirectory,
251+
false, /*isTypeReferenceDirective*/
252+
core.ModuleKindNone, // resolutionMode,
253+
r.compilerOptions,
254+
nil, // redirectedReference,
255+
r,
256+
)
257+
if r.traceEnabled() {
258+
r.host.Trace(diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2.Format(r.projectName, moduleName, r.typingsLocation))
259+
}
260+
globalResolved := state.loadModuleFromImmediateNodeModulesDirectory(extensionsDeclaration, r.typingsLocation, false)
261+
if globalResolved == nil {
262+
return originalResult
263+
}
264+
result := state.createResolvedModule(globalResolved, true)
265+
result.FailedLookupLocations = append(originalResult.FailedLookupLocations, result.FailedLookupLocations...)
266+
result.AffectingLocations = append(originalResult.AffectingLocations, result.AffectingLocations...)
267+
result.ResolutionDiagnostics = append(originalResult.ResolutionDiagnostics, result.ResolutionDiagnostics...)
232268
return result
233269
}
234270

@@ -1718,7 +1754,7 @@ func extensionIsOk(extensions extensions, extension string) bool {
17181754
}
17191755

17201756
func ResolveConfig(moduleName string, containingFile string, host ResolutionHost) *ResolvedModule {
1721-
resolver := NewResolver(host, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext})
1757+
resolver := NewResolver(host, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "")
17221758
return resolver.resolveConfig(moduleName, containingFile)
17231759
}
17241760

internal/module/resolver_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func runTraceBaseline(t *testing.T, test traceTestCase) {
280280
t.Parallel()
281281

282282
host := newVFSModuleResolutionHost(test.files, test.currentDirectory)
283-
resolver := module.NewResolver(host, test.compilerOptions)
283+
resolver := module.NewResolver(host, test.compilerOptions, "", "")
284284

285285
for _, call := range test.calls {
286286
doCall(t, resolver, call, false /*skipLocations*/)
@@ -291,7 +291,7 @@ func runTraceBaseline(t *testing.T, test traceTestCase) {
291291

292292
t.Run("concurrent", func(t *testing.T) {
293293
concurrentHost := newVFSModuleResolutionHost(test.files, test.currentDirectory)
294-
concurrentResolver := module.NewResolver(concurrentHost, test.compilerOptions)
294+
concurrentResolver := module.NewResolver(concurrentHost, test.compilerOptions, "", "")
295295

296296
var wg sync.WaitGroup
297297
for _, call := range test.calls {

internal/packagejson/packagejson.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type PathFields struct {
2222

2323
type DependencyFields struct {
2424
Dependencies Expected[map[string]string] `json:"dependencies"`
25+
DevDependencies Expected[map[string]string] `json:"devDependencies"`
2526
PeerDependencies Expected[map[string]string] `json:"peerDependencies"`
2627
OptionalDependencies Expected[map[string]string] `json:"optionalDependencies"`
2728
}

0 commit comments

Comments
 (0)