From a626163ff040797be5d7dc89fbc78bff517c0d1d Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:39:03 -0700 Subject: [PATCH 1/5] Forbid non-tspath path library usage --- .golangci.yml | 14 ++++++++++++++ internal/tspath/extension.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index afb62f07e9..8d6f17e260 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,6 +22,7 @@ linters: - errname - errorlint - fatcontext + - forbidigo - gocheckcompilerdirectives - goprintffuncname - govet @@ -70,11 +71,24 @@ linters: - pkg: 'encoding/json$' desc: 'Use "github.com/go-json-experiment/json" instead.' + forbidigo: + analyze-types: true + forbid: + - pattern: '.*' + msg: tspath should likely be used instead + pkg: ^(path|path/filepath)$ + exclusions: rules: - path: internal/fourslash/tests/gen/ linters: - misspell + - path: 'internal/(repo|testutil|testrunner|vfs|pprof|execute/tsctests|bundled)' + text: tspath should likely be used instead + - path: '(.+)_test\.go$' + text: tspath should likely be used instead + - path: '_tools' + text: tspath should likely be used instead presets: - comments diff --git a/internal/tspath/extension.go b/internal/tspath/extension.go index cc30d48a66..1b4422e791 100644 --- a/internal/tspath/extension.go +++ b/internal/tspath/extension.go @@ -51,7 +51,7 @@ func RemoveFileExtension(path string) string { } } // Otherwise just remove single dot extension, if any - return path[:len(path)-len(filepath.Ext(path))] + return path[:len(path)-len(filepath.Ext(path))] //nolint:forbidigo } func TryGetExtensionFromPath(p string) string { From 1ee8162a8f9187518728640787cbc91143a30a95 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:45:09 -0700 Subject: [PATCH 2/5] Also ban os --- .golangci.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 8d6f17e260..3461d59961 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -77,18 +77,21 @@ linters: - pattern: '.*' msg: tspath should likely be used instead pkg: ^(path|path/filepath)$ + - pattern: '.*' + msg: a host implementation should likely be used instead + pkg: ^os$ exclusions: rules: - path: internal/fourslash/tests/gen/ linters: - misspell - - path: 'internal/(repo|testutil|testrunner|vfs|pprof|execute/tsctests|bundled)' - text: tspath should likely be used instead + - path: 'internal/(repo|testutil|testrunner|vfs|pprof|execute/tsctests|bundled)|cmd/tsgo' + text: should likely be used instead - path: '(.+)_test\.go$' - text: tspath should likely be used instead + text: should likely be used instead - path: '_tools' - text: tspath should likely be used instead + text: should likely be used instead presets: - comments From e5c1da0eecb6e2bbc76a0c38ff4aea9b182d9934 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:49:11 -0700 Subject: [PATCH 3/5] Move LSP context --- cmd/tsgo/lsp.go | 8 +++++++- internal/fourslash/fourslash.go | 3 ++- internal/lsp/server.go | 8 +------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cmd/tsgo/lsp.go b/cmd/tsgo/lsp.go index 35c973a3a0..dc24e90e73 100644 --- a/cmd/tsgo/lsp.go +++ b/cmd/tsgo/lsp.go @@ -1,10 +1,13 @@ package main import ( + "context" "flag" "fmt" "os" + "os/signal" "runtime" + "syscall" "github.com/microsoft/typescript-go/internal/bundled" "github.com/microsoft/typescript-go/internal/core" @@ -51,7 +54,10 @@ func runLSP(args []string) int { TypingsLocation: typingsLocation, }) - if err := s.Run(); err != nil { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + if err := s.Run(ctx); err != nil { return 1 } return 0 diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 8c64ea2f90..ddac550413 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -1,6 +1,7 @@ package fourslash import ( + "context" "fmt" "io" "maps" @@ -164,7 +165,7 @@ func NewFourslash(t *testing.T, capabilities *lsproto.ClientCapabilities, conten defer func() { outputWriter.Close() }() - err := server.Run() + err := server.Run(context.TODO()) if err != nil { t.Error("server error:", err) } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 54f78f2e16..5f6b36f6a2 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -5,14 +5,11 @@ import ( "errors" "fmt" "io" - "os" "os/exec" - "os/signal" "runtime/debug" "slices" "sync" "sync/atomic" - "syscall" "time" "github.com/go-json-experiment/json" @@ -218,10 +215,7 @@ func (s *Server) RefreshDiagnostics(ctx context.Context) error { return nil } -func (s *Server) Run() error { - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() - +func (s *Server) Run(ctx context.Context) error { g, ctx := errgroup.WithContext(ctx) g.Go(func() error { return s.dispatchLoop(ctx) }) g.Go(func() error { return s.writeLoop(ctx) }) From f4261e71c25d0cc873d87b252e4fec2cea909792 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:52:51 -0700 Subject: [PATCH 4/5] Also GOOS --- .golangci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 3461d59961..1c54b58137 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -80,6 +80,9 @@ linters: - pattern: '.*' msg: a host implementation should likely be used instead pkg: ^os$ + - pattern: 'GOOS' + msg: a host implementation should likely be used instead + pkg: ^runtime$ exclusions: rules: From af2940d828d9f9a5cf047d4e09d97ce59d587c36 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:57:45 -0700 Subject: [PATCH 5/5] Also exec --- .golangci.yml | 2 +- cmd/tsgo/lsp.go | 6 ++++++ internal/lsp/server.go | 9 +++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1c54b58137..5212cb09a7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -79,7 +79,7 @@ linters: pkg: ^(path|path/filepath)$ - pattern: '.*' msg: a host implementation should likely be used instead - pkg: ^os$ + pkg: ^os/ - pattern: 'GOOS' msg: a host implementation should likely be used instead pkg: ^runtime$ diff --git a/cmd/tsgo/lsp.go b/cmd/tsgo/lsp.go index dc24e90e73..0dd04c57fb 100644 --- a/cmd/tsgo/lsp.go +++ b/cmd/tsgo/lsp.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "os" + "os/exec" "os/signal" "runtime" "syscall" @@ -52,6 +53,11 @@ func runLSP(args []string) int { FS: fs, DefaultLibraryPath: defaultLibraryPath, TypingsLocation: typingsLocation, + NpmInstall: func(cwd string, args []string) ([]byte, error) { + cmd := exec.Command("npm", args...) + cmd.Dir = cwd + return cmd.Output() + }, }) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 5f6b36f6a2..2265ebb344 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "os/exec" "runtime/debug" "slices" "sync" @@ -36,6 +35,7 @@ type ServerOptions struct { DefaultLibraryPath string TypingsLocation string ParseCache *project.ParseCache + NpmInstall func(cwd string, args []string) ([]byte, error) } func NewServer(opts *ServerOptions) *Server { @@ -56,6 +56,7 @@ func NewServer(opts *ServerOptions) *Server { defaultLibraryPath: opts.DefaultLibraryPath, typingsLocation: opts.TypingsLocation, parseCache: opts.ParseCache, + npmInstall: opts.NpmInstall, } } @@ -154,6 +155,8 @@ type Server struct { compilerOptionsForInferredProjects *core.CompilerOptions // parseCache can be passed in so separate tests can share ASTs parseCache *project.ParseCache + + npmInstall func(cwd string, args []string) ([]byte, error) } // WatchFiles implements project.Client. @@ -871,9 +874,7 @@ func (s *Server) SetCompilerOptionsForInferredProjects(ctx context.Context, opti // NpmInstall implements ata.NpmExecutor func (s *Server) NpmInstall(cwd string, args []string) ([]byte, error) { - cmd := exec.Command("npm", args...) - cmd.Dir = cwd - return cmd.Output() + return s.npmInstall(cwd, args) } func isBlockingMethod(method lsproto.Method) bool {