diff --git a/src/ulsp/config/base.yaml b/src/ulsp/config/base.yaml index ba54392..9e93693 100644 --- a/src/ulsp/config/base.yaml +++ b/src/ulsp/config/base.yaml @@ -45,6 +45,11 @@ monorepos: _default: scip: loadFromDirectories: true + # Uncomment below as needed to enable language-specific language features for this repository + # languages: + # - go + # - java + # - scala directories: - '.scip' diff --git a/src/ulsp/controller/indexer/BUILD.bazel b/src/ulsp/controller/indexer/BUILD.bazel index bae2997..8d744db 100644 --- a/src/ulsp/controller/indexer/BUILD.bazel +++ b/src/ulsp/controller/indexer/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "@com_github_gofrs_uuid//:go_default_library", "@com_github_uber_go_tally//:go_default_library", "@dev_lsp_go_protocol//:go_default_library", + "@org_uber_go_config//:go_default_library", "@org_uber_go_fx//:go_default_library", "@org_uber_go_zap//:go_default_library", ], @@ -46,7 +47,9 @@ go_test( "//src/ulsp/factory:go_default_library", "//src/ulsp/gateway/ide-client:go_ideclient_gomock_library", "//src/ulsp/internal/executor:go_loader_gomock_library", + "//src/ulsp/internal/fs:go_default_library", "//src/ulsp/internal/fs:go_fs_gomock_library", + "//src/ulsp/internal/fs/fsmock/helpers:go_default_library", "//src/ulsp/internal/logfilewriter:go_default_library", "//src/ulsp/internal/serverinfofile:go_serverinfofile_gomock_library", "//src/ulsp/repository/session:go_repository_gomock_library", @@ -55,6 +58,7 @@ go_test( "@com_github_stretchr_testify//require:go_default_library", "@com_github_uber_go_tally//:go_default_library", "@dev_lsp_go_protocol//:go_default_library", + "@org_uber_go_config//:go_default_library", "@org_uber_go_fx//fxtest:go_default_library", "@org_uber_go_mock//gomock:go_default_library", "@org_uber_go_zap//:go_default_library", diff --git a/src/ulsp/controller/indexer/indexer.go b/src/ulsp/controller/indexer/indexer.go index 3f821f3..e67d6f4 100644 --- a/src/ulsp/controller/indexer/indexer.go +++ b/src/ulsp/controller/indexer/indexer.go @@ -7,6 +7,8 @@ import ( "path/filepath" "strings" + "go.uber.org/config" + "github.com/gofrs/uuid" tally "github.com/uber-go/tally" docsync "github.com/uber/scip-lsp/src/ulsp/controller/doc-sync" @@ -25,8 +27,8 @@ import ( ) const ( + _javaLang = "java" _nameKey = "indexer" - _fievel = "fievel" _syncMessage = "Syncing index for file: %s" ) @@ -50,6 +52,7 @@ type Params struct { Sessions session.Repository Logger *zap.SugaredLogger + Config config.Provider Documents docsync.Controller Guidance userguidance.Controller Stats tally.Scope @@ -68,6 +71,7 @@ type Controller interface { type controller struct { sessions session.Repository logger *zap.SugaredLogger + config entity.MonorepoConfigs documents docsync.Controller executor executor.Executor ideGateway ideclient.Gateway @@ -81,9 +85,15 @@ type controller struct { // New controller for indexer func New(p Params) Controller { + configs := entity.MonorepoConfigs{} + if err := p.Config.Get(entity.MonorepoConfigKey).Populate(&configs); err != nil { + panic(fmt.Sprintf("getting configuration for %q: %v", entity.MonorepoConfigKey, err)) + } + c := &controller{ sessions: p.Sessions, logger: p.Logger.With("plugin", _nameKey), + config: configs, documents: p.Documents, executor: p.Executor, ideGateway: p.IdeGateway, @@ -129,12 +139,10 @@ func (c *controller) StartupInfo(ctx context.Context) (ulspplugin.PluginInfo, er } return ulspplugin.PluginInfo{ - Priorities: priorities, - Methods: methods, - NameKey: _nameKey, - RelevantRepos: map[entity.MonorepoName]struct{}{ - entity.MonorepoNameJava: {}, - }, + Priorities: priorities, + Methods: methods, + NameKey: _nameKey, + RelevantRepos: c.config.RelevantJavaRepos(), }, nil } @@ -148,8 +156,8 @@ func (c *controller) initialize(ctx context.Context, params *protocol.Initialize c.indexer = make(map[uuid.UUID]Indexer) } - // only support fievel indexer for java monorepo now - if strings.HasSuffix(s.WorkspaceRoot, _fievel) { + // Indexer is currently only supported in Java monorepositories + if c.config[s.Monorepo].EnableJavaSupport() { if c.indexerOutputWriter == nil { var err error c.indexerOutputWriter, err = logfilewriter.SetupOutputWriter(c.outputWriterParams, _logFileKey) @@ -157,7 +165,7 @@ func (c *controller) initialize(ctx context.Context, params *protocol.Initialize return fmt.Errorf("setting up log file: %w", err) } } - c.indexer[s.UUID] = NewJavaIndexer(s, c.indexerOutputWriter) + c.indexer[s.UUID] = NewJavaIndexer(c.outputWriterParams.FS, s, c.indexerOutputWriter) } return nil } diff --git a/src/ulsp/controller/indexer/indexer_test.go b/src/ulsp/controller/indexer/indexer_test.go index b113e5c..4b77a33 100644 --- a/src/ulsp/controller/indexer/indexer_test.go +++ b/src/ulsp/controller/indexer/indexer_test.go @@ -6,6 +6,8 @@ import ( "os" "testing" + "go.uber.org/config" + "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,10 +27,12 @@ import ( ) func TestNew(t *testing.T) { + mockConfig, _ := config.NewStaticProvider(map[string]interface{}{}) assert.NotPanics(t, func() { New(Params{ Logger: zap.NewNop().Sugar(), Stats: tally.NewTestScope("testing", make(map[string]string, 0)), + Config: mockConfig, }) }) } @@ -65,8 +69,15 @@ func TestInitialize(t *testing.T) { fs.EXPECT().MkdirAll(gomock.Any()).Return(nil) fs.EXPECT().TempFile(gomock.Any(), gomock.Any()).Return(tempFile, nil) + monorepoConfig := entity.MonorepoConfigs{ + "": { + Languages: []string{"java"}, + }, + } + c := controller{ sessions: sessionRepository, + config: monorepoConfig, outputWriterParams: logfilewriter.Params{ Lifecycle: fxtest.NewLifecycle(t), ServerInfoFile: infoFile, diff --git a/src/ulsp/controller/indexer/java_indexer.go b/src/ulsp/controller/indexer/java_indexer.go index 8079a75..f827715 100644 --- a/src/ulsp/controller/indexer/java_indexer.go +++ b/src/ulsp/controller/indexer/java_indexer.go @@ -7,6 +7,8 @@ import ( "os/exec" "path" + "github.com/uber/scip-lsp/src/ulsp/internal/fs" + "github.com/uber/scip-lsp/src/ulsp/entity" ideclient "github.com/uber/scip-lsp/src/ulsp/gateway/ide-client" "github.com/uber/scip-lsp/src/ulsp/internal/executor" @@ -26,16 +28,18 @@ type javaIndexer struct { path string session *entity.Session outputWriter io.Writer + fs fs.UlspFS } // NewJavaIndexer return a new java indexer -func NewJavaIndexer(s *entity.Session, outputWriter io.Writer) Indexer { +func NewJavaIndexer(fs fs.UlspFS, s *entity.Session, outputWriter io.Writer) Indexer { indexerPath := path.Join(s.WorkspaceRoot, _javaIndexerRelativePath) return &javaIndexer{ session: s, path: indexerPath, outputWriter: outputWriter, + fs: fs, } } @@ -73,7 +77,7 @@ func (j *javaIndexer) IsRelevantDocument(document protocol.TextDocumentItem) boo // GetUniqueIndexKey returns a unique key for the document, // this will be used to determine if additional relevant indexing is in progress for this document func (j *javaIndexer) GetUniqueIndexKey(document protocol.TextDocumentItem) (string, error) { - target, err := javautils.GetJavaTarget(j.session.WorkspaceRoot, document.URI) + target, err := javautils.GetJavaTarget(j.fs, j.session.WorkspaceRoot, document.URI) if err != nil { return "", err } diff --git a/src/ulsp/controller/indexer/java_indexer_test.go b/src/ulsp/controller/indexer/java_indexer_test.go index 2754bce..9625759 100644 --- a/src/ulsp/controller/indexer/java_indexer_test.go +++ b/src/ulsp/controller/indexer/java_indexer_test.go @@ -4,8 +4,13 @@ import ( "context" "fmt" "io" + "os" "testing" + ulspfs "github.com/uber/scip-lsp/src/ulsp/internal/fs" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock/helpers" + "github.com/stretchr/testify/assert" "github.com/uber/scip-lsp/src/ulsp/entity" "github.com/uber/scip-lsp/src/ulsp/factory" @@ -26,7 +31,7 @@ func TestJavaIndexerSyncIndex(t *testing.T) { doc := protocol.TextDocumentItem{ URI: protocol.DocumentURI("file:///home/fievel/sample.java"), } - indexer := NewJavaIndexer(s, io.Discard) + indexer := NewJavaIndexer(ulspfs.New(), s, io.Discard) t.Run("success", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) @@ -53,7 +58,7 @@ func TestJavaIndexerNewJavaIndexer(t *testing.T) { UUID: factory.UUID(), } s.WorkspaceRoot = "/home/fievel" - indexer := NewJavaIndexer(s, io.Discard) + indexer := NewJavaIndexer(ulspfs.New(), s, io.Discard) assert.NotNil(t, indexer) } @@ -62,7 +67,7 @@ func TestJavaIndexerIsRelevantDocument(t *testing.T) { UUID: factory.UUID(), } s.WorkspaceRoot = "/home/fievel" - indexer := NewJavaIndexer(s, io.Discard) + indexer := NewJavaIndexer(ulspfs.New(), s, io.Discard) t.Run("success java", func(t *testing.T) { doc := protocol.TextDocumentItem{ @@ -95,9 +100,17 @@ func TestJavaIndexerGetUniqueIndexKey(t *testing.T) { } s.WorkspaceRoot = "/home/user/fievel" s.UUID = factory.UUID() - indexer := NewJavaIndexer(s, io.Discard) t.Run("success", func(t *testing.T) { + ctrl := gomock.NewController(t) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/tooling/intellij").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(4).Return([]os.DirEntry{}, nil) + + indexer := NewJavaIndexer(fs, s, io.Discard) + validDoc := protocol.TextDocumentItem{ URI: "file:///home/user/fievel/tooling/intellij/src/intellij/bazel/BazelSyncListener.java", } @@ -108,6 +121,11 @@ func TestJavaIndexerGetUniqueIndexKey(t *testing.T) { }) t.Run("failure", func(t *testing.T) { + ctrl := gomock.NewController(t) + fs := fsmock.NewMockUlspFS(ctrl) + + indexer := NewJavaIndexer(fs, s, io.Discard) + invalidDoc := protocol.TextDocumentItem{ URI: "file:///home/user/BazelSyncListener.java", } diff --git a/src/ulsp/controller/quick-actions/BUILD.bazel b/src/ulsp/controller/quick-actions/BUILD.bazel index 5b9e8a0..90503aa 100644 --- a/src/ulsp/controller/quick-actions/BUILD.bazel +++ b/src/ulsp/controller/quick-actions/BUILD.bazel @@ -13,15 +13,18 @@ go_library( "//src/ulsp/controller/doc-sync:go_default_library", "//src/ulsp/controller/quick-actions/action:go_default_library", "//src/ulsp/controller/quick-actions/actions-java:go_default_library", + "//src/ulsp/entity:go_default_library", "//src/ulsp/entity/ulsp-plugin:go_default_library", "//src/ulsp/factory:go_default_library", "//src/ulsp/gateway/ide-client:go_default_library", "//src/ulsp/internal/errors:go_default_library", "//src/ulsp/internal/executor:go_default_library", + "//src/ulsp/internal/fs:go_default_library", "//src/ulsp/mapper:go_default_library", "//src/ulsp/repository/session:go_default_library", "@com_github_gofrs_uuid//:go_default_library", "@dev_lsp_go_protocol//:go_default_library", + "@org_uber_go_config//:go_default_library", "@org_uber_go_fx//:go_default_library", "@org_uber_go_zap//:go_default_library", ], @@ -48,6 +51,7 @@ go_test( "@com_github_gofrs_uuid//:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@dev_lsp_go_protocol//:go_default_library", + "@org_uber_go_config//:go_default_library", "@org_uber_go_mock//gomock:go_default_library", "@org_uber_go_zap//:go_default_library", ], diff --git a/src/ulsp/controller/quick-actions/action/BUILD.bazel b/src/ulsp/controller/quick-actions/action/BUILD.bazel index 768f23b..5778b77 100644 --- a/src/ulsp/controller/quick-actions/action/BUILD.bazel +++ b/src/ulsp/controller/quick-actions/action/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "//src/ulsp/entity:go_default_library", "//src/ulsp/gateway/ide-client:go_default_library", "//src/ulsp/internal/executor:go_default_library", + "//src/ulsp/internal/fs:go_default_library", "//src/ulsp/repository/session:go_default_library", "@dev_lsp_go_protocol//:go_default_library", ], diff --git a/src/ulsp/controller/quick-actions/action/action.go b/src/ulsp/controller/quick-actions/action/action.go index ed04761..8ad065b 100644 --- a/src/ulsp/controller/quick-actions/action/action.go +++ b/src/ulsp/controller/quick-actions/action/action.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" + "github.com/uber/scip-lsp/src/ulsp/internal/fs" + "github.com/uber/scip-lsp/src/ulsp/entity" ideclient "github.com/uber/scip-lsp/src/ulsp/gateway/ide-client" "github.com/uber/scip-lsp/src/ulsp/internal/executor" @@ -25,6 +27,7 @@ type ExecuteParams struct { IdeGateway ideclient.Gateway Executor executor.Executor ProgressToken *protocol.ProgressToken + FileSystem fs.UlspFS } // ProgressInfoParams provides parameters for display during progress of action in status bar @@ -39,7 +42,7 @@ type ProgressInfoParams struct { type Action interface { // These methods should be implemented using simple conditionals, should not be expensive or depend on outside calls, and cannot return an error. // ShouldEnable determines if the action should be enabled for the given session. - ShouldEnable(s *entity.Session) bool + ShouldEnable(s *entity.Session, monorepo entity.MonorepoConfigEntry) bool // CommandName returns the name of the command which will be executed when clicked. CommandName() string // IsRelevantDocument determines if the action is relevant to the given document. diff --git a/src/ulsp/controller/quick-actions/actions-java/BUILD.bazel b/src/ulsp/controller/quick-actions/actions-java/BUILD.bazel index 50577be..2e7446c 100644 --- a/src/ulsp/controller/quick-actions/actions-java/BUILD.bazel +++ b/src/ulsp/controller/quick-actions/actions-java/BUILD.bazel @@ -38,6 +38,8 @@ go_test( "//src/ulsp/factory:go_default_library", "//src/ulsp/gateway/ide-client:go_ideclient_gomock_library", "//src/ulsp/internal/executor:go_loader_gomock_library", + "//src/ulsp/internal/fs:go_fs_gomock_library", + "//src/ulsp/internal/fs/fsmock/helpers:go_default_library", "//src/ulsp/mapper:go_default_library", "//src/ulsp/repository/session:go_repository_gomock_library", "@com_github_stretchr_testify//assert:go_default_library", diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_build.go b/src/ulsp/controller/quick-actions/actions-java/action_java_build.go index e1b1ce8..c6f4466 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_build.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_build.go @@ -62,7 +62,7 @@ func (a *ActionJavaBuild) Execute(ctx context.Context, params *action.ExecutePar if err != nil { return fmt.Errorf("getting writer: %w", err) } - target, err := javautils.GetJavaTarget(s.WorkspaceRoot, resultArgs.Document.URI) + target, err := javautils.GetJavaTarget(params.FileSystem, s.WorkspaceRoot, resultArgs.Document.URI) if err != nil { return err } @@ -117,8 +117,8 @@ func (a *ActionJavaBuild) CommandName() string { } // ShouldEnable returns true if the action should be enabled for the given session. -func (a *ActionJavaBuild) ShouldEnable(s *entity.Session) bool { - if s.Monorepo == entity.MonorepoNameJava { +func (a *ActionJavaBuild) ShouldEnable(s *entity.Session, config entity.MonorepoConfigEntry) bool { + if config.EnableJavaSupport() { return true } @@ -149,7 +149,7 @@ func (a *ActionJavaBuild) ProvideWorkDoneProgressParams(ctx context.Context, par } file := path.Base(resultArgs.Document.URI.Filename()) - target, err := javautils.GetJavaTarget(s.WorkspaceRoot, resultArgs.Document.URI) + target, err := javautils.GetJavaTarget(params.FileSystem, s.WorkspaceRoot, resultArgs.Document.URI) if err != nil { return nil, err } diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_build_test.go b/src/ulsp/controller/quick-actions/actions-java/action_java_build_test.go index 29f18ca..8c936f3 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_build_test.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_build_test.go @@ -5,8 +5,12 @@ import ( "context" "errors" "fmt" + "os" "testing" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock/helpers" + "github.com/stretchr/testify/assert" action "github.com/uber/scip-lsp/src/ulsp/controller/quick-actions/action" "github.com/uber/scip-lsp/src/ulsp/entity" @@ -19,8 +23,8 @@ import ( ) func TestJavaBuildExecute(t *testing.T) { - ctx := context.Background() ctrl := gomock.NewController(t) + ctx := context.Background() a := ActionJavaBuild{} sessionRepository := repositorymock.NewMockRepository(ctrl) @@ -33,33 +37,46 @@ func TestJavaBuildExecute(t *testing.T) { }, } s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava + s.Monorepo = "lm/fievel" t.Run("success", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) - c := &action.ExecuteParams{ + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/tooling/intellij/uber-intellij-plugin-core").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(8).Return([]os.DirEntry{}, nil) + + params := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer - ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).Return(nil) + ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).AnyTimes().Return(nil) ideGatewayMock.EXPECT().GetLogMessageWriter(gomock.Any(), gomock.Any()).Return(&writer, nil) sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) executorMock.EXPECT().RunCommand(gomock.Any(), gomock.Any()).Return(nil) - assert.NoError(t, a.Execute(ctx, c, []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java"}}`))) + assert.NoError(t, a.Execute(ctx, params, []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java"}}`))) }) t.Run("bad args", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Times(0).Return(true, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(0).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) assert.Error(t, a.Execute(ctx, c, []byte(`{"brokenJSON`))) @@ -68,27 +85,40 @@ func TestJavaBuildExecute(t *testing.T) { t.Run("log message failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/tooling/intellij/uber-intellij-plugin-core").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(8).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) ideGatewayMock.EXPECT().GetLogMessageWriter(gomock.Any(), gomock.Any()).Return(&writer, nil) - ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).Return(errors.New("error")) + ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).AnyTimes().Return(errors.New("error")) - assert.Error(t, a.Execute(ctx, c, []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/roadrunner/application-dw/src/test/java/com/uber/roadrunner/application/exception/GatewayErrorExceptionMapperTest.java"}}`))) + assert.Error(t, a.Execute(ctx, c, []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java"}}`))) }) t.Run("writer failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Times(0).Return(true, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(0).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) @@ -99,46 +129,70 @@ func TestJavaBuildExecute(t *testing.T) { t.Run("execution failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/tooling/intellij/uber-intellij-plugin-core").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(8).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer ideGatewayMock.EXPECT().GetLogMessageWriter(gomock.Any(), gomock.Any()).Return(&writer, nil) - ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).AnyTimes().Return(nil).AnyTimes() sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) executorMock.EXPECT().RunCommand(gomock.Any(), gomock.Any()).Return(errors.New("error")) assert.Error(t, a.Execute(ctx, c, []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java"}}`))) }) - } -func TestJavaBuildProcessDocument(t *testing.T) { +func TestJavaBuildProvideWorkDoneProgressParams(t *testing.T) { + ctx := context.Background() + ctrl := gomock.NewController(t) a := ActionJavaBuild{} - doc := protocol.TextDocumentItem{ - URI: "file:///MyExampleTest.java", - LanguageID: "java", - Text: `package com.uber.rider.growth.jobs; -import com.uber.fievel.testing.base.FievelTestBase; -import org.junit.Test; + sessionRepository := repositorymock.NewMockRepository(ctrl) + s := &entity.Session{ + UUID: factory.UUID(), + InitializeParams: &protocol.InitializeParams{ + ClientInfo: &protocol.ClientInfo{ + Name: "Visual Studio Code", + }, + }, + } + s.WorkspaceRoot = "/home/user/fievel" + s.Monorepo = "lm/fievel" -public class MyExampleTest extends FievelTestBase { + t.Run("Success", func(t *testing.T) { + executorMock := executormock.NewMockExecutor(ctrl) + ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) - @Test - public void myTestMethod() throws Exception {} -} + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/tooling").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(8).Return([]os.DirEntry{}, nil) - `} - results, err := a.ProcessDocument(context.Background(), doc) - assert.NoError(t, err) - assert.Equal(t, 1, len(results)) - args := results[0].(protocol.CodeLens).Command.Arguments[0].(argsJavaBuild) - assert.Equal(t, doc.URI, args.Document.URI) - assert.Equal(t, "MyExampleTest", args.ClassName) + params := &action.ExecuteParams{ + IdeGateway: ideGatewayMock, + Sessions: sessionRepository, + Executor: executorMock, + FileSystem: fs, + } + + sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) + + providedParams, err := a.ProvideWorkDoneProgressParams(ctx, params, []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/tooling/src/main/java/com/uber/intellij/bazel/BazelSync.java"}}`)) + + assert.NoError(t, err, "No error should be reported") + assert.NotNil(t, providedParams) + assert.Contains(t, fmt.Sprintf("%v", providedParams.Title), "Build") + }) } func TestJavaBuildCommandName(t *testing.T) { @@ -153,14 +207,16 @@ func TestJavaBuildShouldEnable(t *testing.T) { UUID: factory.UUID(), InitializeParams: &protocol.InitializeParams{ ClientInfo: &protocol.ClientInfo{ - Name: "Visual Studio Code", + Name: "Something else", }, }, } - assert.False(t, a.ShouldEnable(s)) + mce := entity.MonorepoConfigEntry{} + + assert.False(t, a.ShouldEnable(s, mce)) - s.Monorepo = entity.MonorepoNameJava - assert.True(t, a.ShouldEnable(s)) + s.InitializeParams.ClientInfo.Name = string(entity.ClientNameVSCode) + assert.False(t, a.ShouldEnable(s, mce)) } func TestJavaBuildIsRelevantDocument(t *testing.T) { @@ -173,60 +229,6 @@ func TestJavaBuildIsRelevantDocument(t *testing.T) { assert.False(t, a.IsRelevantDocument(nil, irrelevantDoc)) } -func TestJavaBuildProvideWorkDoneProgressParams(t *testing.T) { - ctrl := gomock.NewController(t) - ctx := context.Background() - a := ActionJavaBuild{} - - sessionRepository := repositorymock.NewMockRepository(ctrl) - s := &entity.Session{ - UUID: factory.UUID(), - InitializeParams: &protocol.InitializeParams{ - ClientInfo: &protocol.ClientInfo{ - Name: "Visual Studio Code", - }, - }, - } - s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava - - rawArgs := []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/tooling/src/main/java/com/uber/intellij/bazel/BazelSync.java"}}`) - - execParams := &action.ExecuteParams{ - Sessions: sessionRepository, - } - t.Run("Success", func(t *testing.T) { - sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) - params, err := a.ProvideWorkDoneProgressParams(ctx, execParams, rawArgs) - assert.NoError(t, err) - assert.NotNil(t, params) - assert.Equal(t, fmt.Sprintf(_titleJavaBuildProgressBar, "BazelSync.java"), params.Title) - assert.Equal(t, fmt.Sprintf(_messageJavaBuildProgressBar, "tooling/..."), params.Message) - }) - - t.Run("Context Error", func(t *testing.T) { - sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(nil, errors.New("ctx error")) - _, err := a.ProvideWorkDoneProgressParams(ctx, execParams, rawArgs) - assert.Error(t, err, "context error should be thrown") - }) - - t.Run("missing src", func(t *testing.T) { - invalidPathArgs := []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel"}}`) - sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) - _, err := a.ProvideWorkDoneProgressParams(ctx, execParams, invalidPathArgs) - assert.Error(t, err) - - }) - - t.Run("missing workspace root", func(t *testing.T) { - invalidPathArgs := []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/src/bazel/BazelSync.java"}}`) - sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) - _, err := a.ProvideWorkDoneProgressParams(ctx, execParams, invalidPathArgs) - assert.Error(t, err) - - }) -} - func TestJavaBuildPrepareCommandAndEnv(t *testing.T) { a := ActionJavaBuild{} ctx := context.Background() diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_sync.go b/src/ulsp/controller/quick-actions/actions-java/action_java_sync.go index 3066b67..bbec420 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_sync.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_sync.go @@ -110,9 +110,9 @@ func (a *ActionJavaSync) CommandName() string { } // ShouldEnable returns true if the action should be enabled for the given session. -func (a *ActionJavaSync) ShouldEnable(s *entity.Session) bool { +func (a *ActionJavaSync) ShouldEnable(s *entity.Session, monorepo entity.MonorepoConfigEntry) bool { // Removed monorepo check as it is not used in OS - return true + return monorepo.EnableJavaSupport() } // IsRelevantDocument returns true if the action is relevant for the given document. diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_sync_test.go b/src/ulsp/controller/quick-actions/actions-java/action_java_sync_test.go index 273abe5..73b2011 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_sync_test.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_sync_test.go @@ -28,7 +28,7 @@ func TestJavaSyncExecute(t *testing.T) { UUID: factory.UUID(), } s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava + s.Monorepo = "lm/fievel" t.Run("success", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) @@ -146,7 +146,11 @@ func TestJavaSyncShouldEnable(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - assert.True(t, a.ShouldEnable(s)) + mce := entity.MonorepoConfigEntry{ + Languages: []string{"java"}, + } + + assert.True(t, a.ShouldEnable(s, mce)) } func TestJavaSyncIsRelevantDocument(t *testing.T) { @@ -192,7 +196,7 @@ func TestJavaSyncProvideWorkDoneProgressParams(t *testing.T) { UUID: factory.UUID(), } s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava + s.Monorepo = "lm/fievel" rawArgs := []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/tooling/src/main/java/com/uber/intellij/bazel/BazelSync.java"}}`) diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage.go index dca7932..5aaa88e 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage.go @@ -51,7 +51,7 @@ func (a *ActionJavaTestRunCoverage) Execute(ctx context.Context, params *action. if err != nil { return fmt.Errorf("getting writer: %w", err) } - target, err := javautils.GetJavaTarget(s.WorkspaceRoot, resultArgs.Document.URI) + target, err := javautils.GetJavaTarget(params.FileSystem, s.WorkspaceRoot, resultArgs.Document.URI) if err != nil { return err } @@ -116,12 +116,8 @@ func (a *ActionJavaTestRunCoverage) CommandName() string { } // ShouldEnable returns true if the action should be enabled for the given session. -func (a *ActionJavaTestRunCoverage) ShouldEnable(s *entity.Session) bool { - if s.Monorepo == entity.MonorepoNameJava { - return true - } - - return false +func (a *ActionJavaTestRunCoverage) ShouldEnable(s *entity.Session, monorepo entity.MonorepoConfigEntry) bool { + return monorepo.EnableJavaSupport() } // IsRelevantDocument returns true if the action is relevant for the given document. diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage_test.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage_test.go index 2f21488..5743d90 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage_test.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_coverage_test.go @@ -4,8 +4,12 @@ import ( "bytes" "context" "errors" + "os" "testing" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock/helpers" + "github.com/stretchr/testify/assert" action "github.com/uber/scip-lsp/src/ulsp/controller/quick-actions/action" "github.com/uber/scip-lsp/src/ulsp/entity" @@ -33,15 +37,22 @@ func TestJavaTestRunCoverageExecute(t *testing.T) { }, } s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava + s.Monorepo = "lm/fievel" t.Run("success", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer @@ -69,10 +80,17 @@ func TestJavaTestRunCoverageExecute(t *testing.T) { t.Run("log message failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer @@ -86,10 +104,17 @@ func TestJavaTestRunCoverageExecute(t *testing.T) { t.Run("show message failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer @@ -105,10 +130,16 @@ func TestJavaTestRunCoverageExecute(t *testing.T) { t.Run("writer failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).AnyTimes().Return(true, nil) + fs.EXPECT().ReadDir(gomock.Any()).AnyTimes().Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) @@ -119,10 +150,17 @@ func TestJavaTestRunCoverageExecute(t *testing.T) { t.Run("execution failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).AnyTimes().Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).AnyTimes().Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer @@ -203,10 +241,12 @@ func TestJavaTestRunCoverageShouldEnable(t *testing.T) { s := entity.Session{ UUID: factory.UUID(), } - assert.False(t, a.ShouldEnable(&s)) + mce := entity.MonorepoConfigEntry{} + + assert.False(t, a.ShouldEnable(&s, mce)) - s.Monorepo = entity.MonorepoNameJava - assert.True(t, a.ShouldEnable(&s)) + mce.Languages = []string{"java"} + assert.True(t, a.ShouldEnable(&s, mce)) } func TestJavaTestRunCoverageIsRelevantDocument(t *testing.T) { diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer.go index d2ca6c2..78454ac 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer.go @@ -45,11 +45,11 @@ func (a *ActionJavaTestExplorer) CommandName() string { } // ShouldEnable enables this in the Java monorepo and only when a VS Code client is connected. -func (a *ActionJavaTestExplorer) ShouldEnable(s *entity.Session) bool { +func (a *ActionJavaTestExplorer) ShouldEnable(s *entity.Session, monorepo entity.MonorepoConfigEntry) bool { if s.InitializeParams == nil || s.InitializeParams.ClientInfo == nil { return false } - if s.Monorepo == entity.MonorepoNameJava && entity.ClientName(s.InitializeParams.ClientInfo.Name).IsVSCodeBased() { + if monorepo.EnableJavaSupport() && entity.ClientName(s.InitializeParams.ClientInfo.Name).IsVSCodeBased() { return true } diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info.go index d10d8f7..1aeec06 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info.go @@ -61,11 +61,11 @@ func (a *ActionJavaTestExplorerInfo) CommandName() string { } // ShouldEnable enables this in the Java monorepo and only when a VS Code client is connected. -func (a *ActionJavaTestExplorerInfo) ShouldEnable(s *entity.Session) bool { +func (a *ActionJavaTestExplorerInfo) ShouldEnable(s *entity.Session, monorepo entity.MonorepoConfigEntry) bool { if s.InitializeParams == nil || s.InitializeParams.ClientInfo == nil { return false } - if entity.ClientName(s.InitializeParams.ClientInfo.Name).IsVSCodeBased() { + if monorepo.EnableJavaSupport() && entity.ClientName(s.InitializeParams.ClientInfo.Name).IsVSCodeBased() { return true } diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info_test.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info_test.go index cc81ec7..b9090ff 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info_test.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_info_test.go @@ -25,7 +25,7 @@ func TestJavaTestExplorerInfoExecute(t *testing.T) { UUID: factory.UUID(), } s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava + s.Monorepo = "lm/fievel" executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) @@ -111,14 +111,15 @@ func TestJavaTestExplorerInfoShouldEnable(t *testing.T) { }, }, } + mce := entity.MonorepoConfigEntry{} - assert.False(t, a.ShouldEnable(s)) + assert.False(t, a.ShouldEnable(s, mce)) - s.Monorepo = entity.MonorepoNameJava - assert.False(t, a.ShouldEnable(s)) + mce.Languages = []string{"java"} + assert.False(t, a.ShouldEnable(s, mce)) s.InitializeParams.ClientInfo.Name = string(entity.ClientNameVSCode) - assert.True(t, a.ShouldEnable(s)) + assert.True(t, a.ShouldEnable(s, mce)) } func TestJavaTestExplorerInfoIsRelevantDocument(t *testing.T) { diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_test.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_test.go index a98ae44..36c598d 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_test.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_explorer_test.go @@ -25,7 +25,7 @@ func TestJavaTestExplorerExecute(t *testing.T) { UUID: factory.UUID(), } s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava + s.Monorepo = "lm/fievel" executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) @@ -107,13 +107,14 @@ func TestJavaTestExplorerShouldEnable(t *testing.T) { }, } - assert.False(t, a.ShouldEnable(s)) + mce := entity.MonorepoConfigEntry{} + assert.False(t, a.ShouldEnable(s, mce)) - s.Monorepo = entity.MonorepoNameJava - assert.False(t, a.ShouldEnable(s)) + mce.Languages = []string{"java"} + assert.False(t, a.ShouldEnable(s, mce)) s.InitializeParams.ClientInfo.Name = string(entity.ClientNameVSCode) - assert.True(t, a.ShouldEnable(s)) + assert.True(t, a.ShouldEnable(s, mce)) } func TestJavaTestExplorerIsRelevantDocument(t *testing.T) { diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_run.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_run.go index 1076d74..904898b 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_run.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_run.go @@ -58,7 +58,7 @@ func (a *ActionJavaTestRun) Execute(ctx context.Context, params *action.ExecuteP } testCaseName := a.getTestCaseName(resultArgs) - target, err := javautils.GetJavaTarget(s.WorkspaceRoot, resultArgs.Document.URI) + target, err := javautils.GetJavaTarget(params.FileSystem, s.WorkspaceRoot, resultArgs.Document.URI) if err != nil { return err } @@ -117,12 +117,12 @@ func (a *ActionJavaTestRun) CommandName() string { } // ShouldEnable returns whether the action should be enabled for the given session. -func (a *ActionJavaTestRun) ShouldEnable(s *entity.Session) bool { +func (a *ActionJavaTestRun) ShouldEnable(s *entity.Session, monorepo entity.MonorepoConfigEntry) bool { if s.InitializeParams == nil || s.InitializeParams.ClientInfo == nil { return false } // Users of the VS Code client will instead of use the Test Explorer. - if s.Monorepo == entity.MonorepoNameJava && !entity.ClientName(s.InitializeParams.ClientInfo.Name).IsVSCodeBased() { + if monorepo.EnableJavaSupport() && !entity.ClientName(s.InitializeParams.ClientInfo.Name).IsVSCodeBased() { return true } diff --git a/src/ulsp/controller/quick-actions/actions-java/action_java_test_run_test.go b/src/ulsp/controller/quick-actions/actions-java/action_java_test_run_test.go index d660801..9205e8d 100644 --- a/src/ulsp/controller/quick-actions/actions-java/action_java_test_run_test.go +++ b/src/ulsp/controller/quick-actions/actions-java/action_java_test_run_test.go @@ -4,8 +4,12 @@ import ( "bytes" "context" "errors" + "os" "testing" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock/helpers" + "github.com/stretchr/testify/assert" action "github.com/uber/scip-lsp/src/ulsp/controller/quick-actions/action" "github.com/uber/scip-lsp/src/ulsp/entity" @@ -18,8 +22,8 @@ import ( ) func TestJavaTestRunExecute(t *testing.T) { - ctx := context.Background() ctrl := gomock.NewController(t) + ctx := context.Background() a := ActionJavaTestRun{} sessionRepository := repositorymock.NewMockRepository(ctrl) @@ -32,15 +36,22 @@ func TestJavaTestRunExecute(t *testing.T) { }, } s.WorkspaceRoot = "/home/user/fievel" - s.Monorepo = entity.MonorepoNameJava + s.Monorepo = "lm/fievel" t.Run("success", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer @@ -56,10 +67,16 @@ func TestJavaTestRunExecute(t *testing.T) { t.Run("bad args", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Times(0).Return(true, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(0).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) assert.Error(t, a.Execute(ctx, c, []byte(`{"brokenJSON`))) @@ -68,10 +85,17 @@ func TestJavaTestRunExecute(t *testing.T) { t.Run("log message failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer @@ -85,10 +109,17 @@ func TestJavaTestRunExecute(t *testing.T) { t.Run("show message failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer @@ -104,10 +135,16 @@ func TestJavaTestRunExecute(t *testing.T) { t.Run("writer failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Times(0).Return(true, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(0).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) @@ -118,21 +155,27 @@ func TestJavaTestRunExecute(t *testing.T) { t.Run("execution failure", func(t *testing.T) { executorMock := executormock.NewMockExecutor(ctrl) ideGatewayMock := ideclientmock.NewMockGateway(ctrl) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + c := &action.ExecuteParams{ IdeGateway: ideGatewayMock, Sessions: sessionRepository, Executor: executorMock, + FileSystem: fs, } var writer bytes.Buffer ideGatewayMock.EXPECT().GetLogMessageWriter(gomock.Any(), gomock.Any()).Return(&writer, nil) - ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + ideGatewayMock.EXPECT().LogMessage(gomock.Any(), gomock.Any()).Return(nil).Times(2) sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) executorMock.EXPECT().RunCommand(gomock.Any(), gomock.Any()).Return(errors.New("error")) assert.Error(t, a.Execute(ctx, c, []byte(`{"interfaceName": "myInterface", "document": {"uri": "file:///home/user/fievel/roadrunner/application-dw/src/test/java/com/uber/roadrunner/application/exception/GatewayErrorExceptionMapperTest.java"}}`))) }) - } func TestJavaTestRunProcessDocument(t *testing.T) { @@ -206,11 +249,12 @@ func TestJavaTestRunShouldEnable(t *testing.T) { }, }, } + mce := entity.MonorepoConfigEntry{} - assert.False(t, a.ShouldEnable(s)) + assert.False(t, a.ShouldEnable(s, mce)) s.InitializeParams.ClientInfo.Name = string(entity.ClientNameVSCode) - assert.False(t, a.ShouldEnable(s)) + assert.False(t, a.ShouldEnable(s, mce)) } func TestJavaTestRunIsRelevantDocument(t *testing.T) { diff --git a/src/ulsp/controller/quick-actions/quick_actions.go b/src/ulsp/controller/quick-actions/quick_actions.go index 5ec8cd6..ed419ab 100644 --- a/src/ulsp/controller/quick-actions/quick_actions.go +++ b/src/ulsp/controller/quick-actions/quick_actions.go @@ -6,6 +6,10 @@ import ( "fmt" "sync" + "github.com/uber/scip-lsp/src/ulsp/entity" + "github.com/uber/scip-lsp/src/ulsp/internal/fs" + "go.uber.org/config" + "github.com/gofrs/uuid" docsync "github.com/uber/scip-lsp/src/ulsp/controller/doc-sync" action "github.com/uber/scip-lsp/src/ulsp/controller/quick-actions/action" @@ -40,11 +44,13 @@ const ( type Params struct { fx.In + Config config.Provider Executor executor.Executor Documents docsync.Controller IdeGateway ideclient.Gateway Sessions session.Repository Logger *zap.SugaredLogger + FS fs.UlspFS } // Controller defines the methods that this controller provides. @@ -64,21 +70,30 @@ type controller struct { ideGateway ideclient.Gateway sessions session.Repository logger *zap.SugaredLogger + fs fs.UlspFS + config entity.MonorepoConfigs } // New creates a new controller for quick hints. func New(p Params) Controller { + configs := entity.MonorepoConfigs{} + if err := p.Config.Get(entity.MonorepoConfigKey).Populate(&configs); err != nil { + panic(fmt.Sprintf("getting configuration for %q: %v", entity.MonorepoConfigKey, err)) + } + c := &controller{ documents: p.Documents, ideGateway: p.IdeGateway, sessions: p.Sessions, executor: p.Executor, logger: p.Logger.With("plugin", _nameKey), + fs: p.FS, currentActionRanges: newActionRangeStore(), enabledActions: make(map[uuid.UUID][]action.Action), pendingActionRuns: newInProgressActionStore(), pendingCmds: make(map[protocol.ProgressToken]context.CancelFunc), + config: configs, } return c } @@ -143,7 +158,7 @@ func (c *controller) initialize(ctx context.Context, params *protocol.Initialize commands := []string{} for _, action := range allActions { - if action.ShouldEnable(s) { + if action.ShouldEnable(s, c.config[s.Monorepo]) { c.enabledActions[s.UUID] = append(c.enabledActions[s.UUID], action) if action.CommandName() != "" { commands = append(commands, action.CommandName()) @@ -261,6 +276,7 @@ func (c *controller) executeCommand(ctx context.Context, params *protocol.Execut Executor: c.executor, IdeGateway: c.ideGateway, ProgressToken: progressToken, + FileSystem: c.fs, } progressInfoParams, err := currentAction.ProvideWorkDoneProgressParams(ctx, params, args) diff --git a/src/ulsp/controller/quick-actions/quick_actions_test.go b/src/ulsp/controller/quick-actions/quick_actions_test.go index 5574b5c..252e3ef 100644 --- a/src/ulsp/controller/quick-actions/quick_actions_test.go +++ b/src/ulsp/controller/quick-actions/quick_actions_test.go @@ -6,6 +6,8 @@ import ( "regexp" "testing" + "go.uber.org/config" + "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/uber/scip-lsp/src/ulsp/controller/doc-sync/docsyncmock" @@ -22,12 +24,17 @@ import ( "go.uber.org/zap" ) +const _monorepoNameGoCode entity.MonorepoName = "go-code" + var _sampleRegex = regexp.MustCompile("sampleRegex") func TestNew(t *testing.T) { + mockConfig, _ := config.NewStaticProvider(map[string]interface{}{}) + assert.NotPanics(t, func() { New(Params{ Logger: zap.NewNop().Sugar(), + Config: mockConfig, }) }) } @@ -46,21 +53,37 @@ func TestInitialize(t *testing.T) { tests := []struct { name string monorepo entity.MonorepoName + config entity.MonorepoConfigs client string }{ { name: "go", - monorepo: entity.MonorepoNameGoCode, + monorepo: "go-code", + config: map[entity.MonorepoName]entity.MonorepoConfigEntry{ + "go-code": { + Languages: []string{"go"}, + }, + }, }, { name: "java vs code", - monorepo: entity.MonorepoNameJava, - client: "Visual Studio Code", + monorepo: "lm/fievel", + config: map[entity.MonorepoName]entity.MonorepoConfigEntry{ + "lm/fievel": { + Languages: []string{"java"}, + }, + }, + client: "Visual Studio Code", }, { name: "java other", - monorepo: entity.MonorepoNameJava, - client: "other", + monorepo: "lm/fievel", + config: map[entity.MonorepoName]entity.MonorepoConfigEntry{ + "lm/fievel": { + Languages: []string{"java"}, + }, + }, + client: "other", }, } @@ -78,6 +101,7 @@ func TestInitialize(t *testing.T) { currentActionRanges: newActionRangeStore(), sessions: sessionRepository, enabledActions: make(map[uuid.UUID][]action.Action), + config: tt.config, } result := &protocol.InitializeResult{} @@ -86,7 +110,7 @@ func TestInitialize(t *testing.T) { assert.Equal(t, result.Capabilities.CodeActionProvider, &protocol.CodeActionOptions{CodeActionKinds: []protocol.CodeActionKind{_codeActionKind}}) for _, a := range allActions { - if a.ShouldEnable(s) { + if a.ShouldEnable(s, tt.config[tt.monorepo]) { assert.Contains(t, c.enabledActions[s.UUID], a) } else { assert.NotContains(t, c.enabledActions[s.UUID], a) @@ -111,13 +135,19 @@ func TestInitialize(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = "lm/fievel" sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() c := controller{ currentActionRanges: newActionRangeStore(), sessions: sessionRepository, enabledActions: make(map[uuid.UUID][]action.Action), + // Currently only java adds actions with commands, so we can use that to trigger the duplicate command error. + config: map[entity.MonorepoName]entity.MonorepoConfigEntry{ + "lm/fievel": { + Languages: []string{"java"}, + }, + }, } result := &protocol.InitializeResult{} err := c.initialize(ctx, &protocol.InitializeParams{}, result) @@ -132,7 +162,7 @@ func TestInitialize(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) c := controller{ @@ -159,7 +189,7 @@ func TestRefreshAvailableCodeActions(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() documents := docsyncmock.NewMockController(ctrl) @@ -275,7 +305,7 @@ func TestDidOpen(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) documents := docsyncmock.NewMockController(ctrl) @@ -307,7 +337,7 @@ func TestDidSave(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) documents := docsyncmock.NewMockController(ctrl) @@ -339,7 +369,7 @@ func TestDidClose(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil) c := controller{ @@ -388,7 +418,7 @@ func TestCodeAction(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() documents := docsyncmock.NewMockController(ctrl) @@ -487,7 +517,7 @@ func TestCodeLens(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() documents := docsyncmock.NewMockController(ctrl) @@ -571,7 +601,7 @@ func TestExecuteCommand(t *testing.T) { s := &entity.Session{ UUID: factory.UUID(), } - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameGoCode sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() action1 := quickactionmock.NewMockAction(ctrl) diff --git a/src/ulsp/controller/scala-assist/scala_assist.go b/src/ulsp/controller/scala-assist/scala_assist.go index 67836d6..5f03479 100644 --- a/src/ulsp/controller/scala-assist/scala_assist.go +++ b/src/ulsp/controller/scala-assist/scala_assist.go @@ -87,12 +87,10 @@ func (c *controller) StartupInfo(ctx context.Context) (ulspplugin.PluginInfo, er } return ulspplugin.PluginInfo{ - Priorities: priorities, - Methods: methods, - NameKey: _nameKey, - RelevantRepos: map[entity.MonorepoName]struct{}{ - entity.MonorepoNameJava: {}, - }, + Priorities: priorities, + Methods: methods, + NameKey: _nameKey, + RelevantRepos: c.configs.RelevantScalaRepos(), }, nil } diff --git a/src/ulsp/controller/scala-assist/scala_assist_test.go b/src/ulsp/controller/scala-assist/scala_assist_test.go index 35797f0..58559fe 100644 --- a/src/ulsp/controller/scala-assist/scala_assist_test.go +++ b/src/ulsp/controller/scala-assist/scala_assist_test.go @@ -30,12 +30,18 @@ func TestNew(t *testing.T) { func TestStartupInfo(t *testing.T) { ctx := context.Background() - c := &controller{} + c := &controller{ + configs: map[entity.MonorepoName]entity.MonorepoConfigEntry{ + "lm/fievel": { + Languages: []string{"java", "scala"}, + }, + }, + } info, err := c.StartupInfo(ctx) assert.NoError(t, err) assert.NoError(t, info.Validate()) assert.Equal(t, _nameKey, info.NameKey) - assert.Contains(t, info.RelevantRepos, entity.MonorepoNameJava) + assert.Contains(t, info.RelevantRepos, entity.MonorepoName("lm/fievel")) } func TestInitialized(t *testing.T) { diff --git a/src/ulsp/controller/scip/scip_test.go b/src/ulsp/controller/scip/scip_test.go index 4feca3d..392718f 100644 --- a/src/ulsp/controller/scip/scip_test.go +++ b/src/ulsp/controller/scip/scip_test.go @@ -35,6 +35,8 @@ import ( "go.uber.org/zap" ) +const _monorepoNameJava entity.MonorepoName = "lm/fievel" + type mockDirEntry struct { name string } @@ -98,7 +100,7 @@ func TestStartupInfo(t *testing.T) { func TestInitialize(t *testing.T) { ctx := context.Background() ctrl := gomock.NewController(t) - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) s := &entity.Session{ UUID: factory.UUID(), Monorepo: "_default", @@ -159,7 +161,7 @@ func TestInitialize(t *testing.T) { regMock := NewMockRegistry(ctrl) fsMock := fsmock.NewMockUlspFS(ctrl) - c := newScipCtl(fsMock, regMock, entity.MonorepoNameGoCode, false, false, true) + c := newScipCtl(fsMock, regMock, _monorepoNameJava, false, false, true) err := c.initialize(ctx, &protocol.InitializeParams{}, &protocol.InitializeResult{}) assert.NoError(t, err) @@ -354,7 +356,7 @@ func TestInitialize(t *testing.T) { ctrl := gomock.NewController(t) fsMock := fsmock.NewMockUlspFS(ctrl) - c := newScipCtl(fsMock, nil, entity.MonorepoNameJava, true, false, true) + c := newScipCtl(fsMock, nil, "lm/fievel", true, false, true) err := c.initialize(ctx, &protocol.InitializeParams{}, &protocol.InitializeResult{}) assert.NoError(t, err) @@ -1376,7 +1378,7 @@ func TestIndexReloading(t *testing.T) { }) mockReg.EXPECT().SetDocumentLoadedCallback(gomock.Any()) - c.registries[dir] = c.createNewScipRegistry(dir, entity.MonorepoNameGoCode) + c.registries[dir] = c.createNewScipRegistry(dir, _monorepoNameJava) go func() { c.handleChanges(closer) done <- true @@ -1444,7 +1446,7 @@ func TestIndexReloading(t *testing.T) { }).MinTimes(1).MaxTimes(2) mockReg.EXPECT().SetDocumentLoadedCallback(gomock.Any()) - c.registries[dir] = c.createNewScipRegistry(dir, entity.MonorepoNameGoCode) + c.registries[dir] = c.createNewScipRegistry(dir, _monorepoNameJava) go func() { c.handleChanges(closer) done <- true @@ -1501,7 +1503,7 @@ func TestIndexReloading(t *testing.T) { closer := make(chan bool, 1) mockReg.EXPECT().SetDocumentLoadedCallback(gomock.Any()) - c.registries[dir] = c.createNewScipRegistry(dir, entity.MonorepoNameGoCode) + c.registries[dir] = c.createNewScipRegistry(dir, _monorepoNameJava) go func() { c.handleChanges(closer) done <- true @@ -1543,13 +1545,13 @@ func TestIndexReloading(t *testing.T) { documents := docsyncmock.NewMockController(ctrl) documents.EXPECT().ResetBase(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() dir := t.TempDir() @@ -1591,7 +1593,7 @@ func TestIndexReloading(t *testing.T) { cb = callback }) - c.registries[dir] = c.createNewScipRegistry(dir, entity.MonorepoNameGoCode) + c.registries[dir] = c.createNewScipRegistry(dir, _monorepoNameJava) go func() { c.handleChanges(closer) assert.NotEmpty(t, notChan) @@ -1620,13 +1622,13 @@ func TestHandleChangesErrorConfig(t *testing.T) { mockFs := fsmock.NewMockUlspFS(ctrl) mockReg := NewMockRegistry(ctrl) - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() dir := t.TempDir() @@ -1669,13 +1671,13 @@ func TestHandleChangesErrorSessionsObject(t *testing.T) { mockFs := fsmock.NewMockUlspFS(ctrl) mockReg := NewMockRegistry(ctrl) - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(nil, errors.New("whoops")).AnyTimes() dir := t.TempDir() @@ -1718,13 +1720,13 @@ func TestNotifier(t *testing.T) { ctrl := gomock.NewController(t) ctx := context.Background() - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() fsMock := fsmock.NewMockUlspFS(ctrl) @@ -1764,13 +1766,13 @@ func TestNotifier(t *testing.T) { ctrl := gomock.NewController(t) ctx := context.Background() - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() regMock := NewMockRegistry(ctrl) @@ -1821,7 +1823,7 @@ func TestNotifier(t *testing.T) { ctrl := gomock.NewController(t) ctx := context.Background() - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), @@ -1897,13 +1899,13 @@ func TestNotifier(t *testing.T) { documents := docsyncmock.NewMockController(ctrl) documents.EXPECT().ResetBase(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava dir := t.TempDir() w, err := fsnotify.NewWatcher() @@ -1944,7 +1946,7 @@ func TestNotifier(t *testing.T) { cb = callback }) - c.registries[dir] = c.createNewScipRegistry(dir, entity.MonorepoNameGoCode) + c.registries[dir] = c.createNewScipRegistry(dir, _monorepoNameJava) go func() { c.handleChanges(closer) assert.NotEmpty(t, notChan) @@ -2010,13 +2012,13 @@ func TestNotifier(t *testing.T) { documents := docsyncmock.NewMockController(ctrl) documents.EXPECT().ResetBase(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - sampleWorkspaceRoot := path.Join("/sample/home/", string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join("/sample/home/", string(_monorepoNameJava)) sessionRepository := repositorymock.NewMockRepository(ctrl) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava sessionRepository.EXPECT().GetFromContext(gomock.Any()).Return(s, nil).AnyTimes() dir := t.TempDir() @@ -2059,7 +2061,7 @@ func TestNotifier(t *testing.T) { mockReg.EXPECT().SetDocumentLoadedCallback(gomock.Any()).DoAndReturn(func(callback func(*model.Document)) { cb = callback }) - c.registries[dir] = c.createNewScipRegistry(dir, entity.MonorepoNameGoCode) + c.registries[dir] = c.createNewScipRegistry(dir, _monorepoNameJava) go func() { c.handleChanges(closer) assert.NotEmpty(t, notChan) @@ -2090,12 +2092,12 @@ func TestGetSha(t *testing.T) { mockReg := NewMockRegistry(ctrl) dir := t.TempDir() - sampleWorkspaceRoot := path.Join(dir, string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join(dir, string(_monorepoNameJava)) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava c := &controller{ configs: map[entity.MonorepoName]entity.MonorepoConfigEntry{"_default": { @@ -2131,12 +2133,12 @@ func TestGetSha(t *testing.T) { mockReg := NewMockRegistry(ctrl) dir := t.TempDir() - sampleWorkspaceRoot := path.Join(dir, string(entity.MonorepoNameGoCode)) + sampleWorkspaceRoot := path.Join(dir, string(_monorepoNameJava)) s := &entity.Session{ UUID: factory.UUID(), } s.WorkspaceRoot = sampleWorkspaceRoot - s.Monorepo = entity.MonorepoNameGoCode + s.Monorepo = _monorepoNameJava c := &controller{ configs: map[entity.MonorepoName]entity.MonorepoConfigEntry{"_default": { diff --git a/src/ulsp/entity/ulsp_daemon.go b/src/ulsp/entity/ulsp_daemon.go index 5e99e3c..425d05a 100644 --- a/src/ulsp/entity/ulsp_daemon.go +++ b/src/ulsp/entity/ulsp_daemon.go @@ -2,6 +2,8 @@ package entity import ( + "slices" + "github.com/gofrs/uuid" "go.lsp.dev/jsonrpc2" "go.lsp.dev/protocol" @@ -15,6 +17,9 @@ const SessionContextKey keyType = "SessionUUID" // MonorepoConfigKey is the key that contains monorepo specific configuration. const MonorepoConfigKey = "monorepos" +const _javaLang = "java" +const _scalaLang = "scala" + // UlspDaemon placeholder entity. type UlspDaemon struct { Name string `json:"name" zap:"name"` @@ -56,12 +61,6 @@ type TextDocumentIdenfitierWithSession struct { // MonorepoName for supported Uber monorepos. type MonorepoName string -// Identifier for each monorepo. -const ( - MonorepoNameGoCode MonorepoName = "go-code" - MonorepoNameJava MonorepoName = "lm/fievel" -) - // MonorepoConfigs contain the config entries that differ between monorepos type MonorepoConfigs map[MonorepoName]MonorepoConfigEntry @@ -73,6 +72,7 @@ type MonorepoConfigEntry struct { ProjectViewPaths []string `yaml:"projectViewPaths"` BSPVersionOverride string `yaml:"bspVersionOverride"` Formatters []FormatterConfig `yaml:"formatters"` + Languages []string `yaml:"languages"` BuildEnvOverrides []string `yaml:"buildEnvOverrides"` RegistryFeatureFlags map[string]bool `yaml:"registryFeatureFlags"` } @@ -109,3 +109,34 @@ const ( func (c ClientName) IsVSCodeBased() bool { return c == ClientNameVSCode || c == ClientNameCursor } + +func (mce MonorepoConfigEntry) EnableJavaSupport() bool { + return slices.Contains(mce.Languages, _javaLang) +} + +func (mce MonorepoConfigEntry) EnableScalaSupport() bool { + return slices.Contains(mce.Languages, _scalaLang) +} + +func (configs MonorepoConfigs) RelevantJavaRepos() map[MonorepoName]struct{} { + return configs.relevantRepos(func(entry MonorepoConfigEntry) bool { + return entry.EnableJavaSupport() + }) +} + +func (configs MonorepoConfigs) RelevantScalaRepos() map[MonorepoName]struct{} { + return configs.relevantRepos(func(entry MonorepoConfigEntry) bool { + return entry.EnableScalaSupport() + }) +} + +func (configs MonorepoConfigs) relevantRepos(predicate func(entry MonorepoConfigEntry) bool) map[MonorepoName]struct{} { + relevantRepos := make(map[MonorepoName]struct{}) + for monorepoName, monorepoConfig := range configs { + if predicate(monorepoConfig) { + relevantRepos[monorepoName] = struct{}{} + } + } + + return relevantRepos +} diff --git a/src/ulsp/internal/fs/fsmock/helpers/BUILD.bazel b/src/ulsp/internal/fs/fsmock/helpers/BUILD.bazel new file mode 100644 index 0000000..18b28cc --- /dev/null +++ b/src/ulsp/internal/fs/fsmock/helpers/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["helpers.go"], + importpath = "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock/helpers", + visibility = ["//src/ulsp:__subpackages__"], +) diff --git a/src/ulsp/internal/fs/fsmock/helpers/helpers.go b/src/ulsp/internal/fs/fsmock/helpers/helpers.go new file mode 100644 index 0000000..089b092 --- /dev/null +++ b/src/ulsp/internal/fs/fsmock/helpers/helpers.go @@ -0,0 +1,37 @@ +package helpers + +import ( + "io/fs" + "os" +) + +type mockDirEntry struct { + name string + dir bool +} + +func (m mockDirEntry) Name() string { + return m.name +} + +func (m mockDirEntry) IsDir() bool { + return m.dir +} + +func (m mockDirEntry) Type() fs.FileMode { + if m.dir { + return fs.ModeDir + } else { + return fs.ModeTemporary + } +} + +func (m mockDirEntry) Info() (fs.FileInfo, error) { + panic("implement me") +} + +var _ os.DirEntry = mockDirEntry{} + +func MockDirEntry(name string, dir bool) os.DirEntry { + return mockDirEntry{name, dir} +} diff --git a/src/ulsp/internal/java-utils/BUILD.bazel b/src/ulsp/internal/java-utils/BUILD.bazel index 60df42f..40f2bd2 100644 --- a/src/ulsp/internal/java-utils/BUILD.bazel +++ b/src/ulsp/internal/java-utils/BUILD.bazel @@ -5,7 +5,10 @@ go_library( srcs = ["utils.go"], importpath = "github.com/uber/scip-lsp/src/ulsp/internal/java-utils", visibility = ["//src/ulsp:__subpackages__"], - deps = ["@dev_lsp_go_protocol//:go_default_library"], + deps = [ + "//src/ulsp/internal/fs:go_default_library", + "@dev_lsp_go_protocol//:go_default_library", + ], ) go_test( @@ -13,7 +16,10 @@ go_test( srcs = ["utils_test.go"], embed = [":go_default_library"], deps = [ + "//src/ulsp/internal/fs:go_fs_gomock_library", + "//src/ulsp/internal/fs/fsmock/helpers:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@dev_lsp_go_protocol//:go_default_library", + "@org_uber_go_mock//gomock:go_default_library", ], ) diff --git a/src/ulsp/internal/java-utils/utils.go b/src/ulsp/internal/java-utils/utils.go index eb8133e..3237500 100644 --- a/src/ulsp/internal/java-utils/utils.go +++ b/src/ulsp/internal/java-utils/utils.go @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" + "github.com/uber/scip-lsp/src/ulsp/internal/fs" + "go.lsp.dev/protocol" ) @@ -13,15 +15,66 @@ const _targetSuffix = "..." const _visualStudioCodeIDEClient = "Visual Studio Code" const _cursorIDEClient = "Cursor" -// GetJavaTarget returns the target path for the given document URI. -func GetJavaTarget(workspaceRoot string, docURI protocol.DocumentURI) (string, error) { - filePath := docURI.Filename() - pathSegments := strings.SplitN(filePath, "/src/", 2) - if len(pathSegments) != 2 { - return "", fmt.Errorf("missing src directory") +var _buildFileNames = map[string]interface{}{"BUILD.bazel": nil, "BUILD": nil} + +func isBuildFile(fileName string) bool { + _, ok := _buildFileNames[fileName] + return ok +} + +// getBuildFile returns the absolute path of closest build file or build file directory to the URI, returning an error if the workspaceRoot is reached +func getBuildFile(fs fs.UlspFS, workspaceRoot string, uri protocol.DocumentURI, includeBuildFile bool) (string, error) { + filename := uri.Filename() + + if !strings.HasPrefix(filename, workspaceRoot) { + return "", fmt.Errorf("uri %s is not a child of the workspace %s", filename, workspaceRoot) + } + + isDir, err := fs.DirExists(filename) + if err != nil { + return "", err + } + + currentDir := filename + if !isDir { + currentDir = filepath.Dir(currentDir) + } + + for currentDir != workspaceRoot { + children, err := fs.ReadDir(currentDir) + if err != nil { + return "", err + } + + for _, child := range children { + childName := child.Name() + if isBuildFile(childName) { + if includeBuildFile { + return currentDir + string(filepath.Separator) + childName, nil + } else { + return currentDir, nil + } + } + } + + currentDir = filepath.Dir(currentDir) + } + + return "", fmt.Errorf("no child directory contained a BUILD file") +} + +// GetJavaTarget returns the bazel target for all targets in a path for the given document URI by finding the nearest +// parent BUILD.bazel file and appending the `...` suffix to the path. +// Example: for a document URI of /home/user/fievel/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java +// and a workspace root of /home/user/fievel, the returned target would be tooling/intellij/uber-intellij-plugin-core/... +// assuming that the BUILD.bazel file is located at /home/user/fievel/tooling/intellij/uber-intellij-plugin-core/BUILD.bazel +func GetJavaTarget(fs fs.UlspFS, workspaceRoot string, docURI protocol.DocumentURI) (string, error) { + buildFileDir, err := getBuildFile(fs, workspaceRoot, docURI, false) + if err != nil { + return "", err } - targetSegments := strings.SplitN(pathSegments[0], workspaceRoot+string(filepath.Separator), 2) + targetSegments := strings.SplitN(buildFileDir, workspaceRoot+string(filepath.Separator), 2) if len(targetSegments) != 2 { return "", fmt.Errorf("missing workspace root") } diff --git a/src/ulsp/internal/java-utils/utils_test.go b/src/ulsp/internal/java-utils/utils_test.go index b447ca9..5634e33 100644 --- a/src/ulsp/internal/java-utils/utils_test.go +++ b/src/ulsp/internal/java-utils/utils_test.go @@ -1,8 +1,13 @@ package javautils import ( + "os" "testing" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock" + "github.com/uber/scip-lsp/src/ulsp/internal/fs/fsmock/helpers" + "go.uber.org/mock/gomock" + "github.com/stretchr/testify/assert" "go.lsp.dev/protocol" ) @@ -10,29 +15,61 @@ import ( func TestJavaUtilsGetJavaTarget(t *testing.T) { workspaceRoot := "/home/user/fievel" t.Run("valid src", func(t *testing.T) { + ctrl := gomock.NewController(t) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/tooling/intellij/uber-intellij-plugin-core").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(8).Return([]os.DirEntry{}, nil) + validDoc := protocol.DocumentURI("file:///home/user/fievel/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java") - target, err := GetJavaTarget(workspaceRoot, validDoc) + target, err := GetJavaTarget(fs, workspaceRoot, validDoc) assert.NoError(t, err) assert.Equal(t, "tooling/intellij/uber-intellij-plugin-core/...", target) }) t.Run("valid test doc", func(t *testing.T) { + ctrl := gomock.NewController(t) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(true, nil) + fs.EXPECT().ReadDir("/home/user/fievel/roadrunner/application-dw").Return([]os.DirEntry{helpers.MockDirEntry("BUILD.bazel", false)}, nil) + fs.EXPECT().ReadDir(gomock.Any()).Times(9).Return([]os.DirEntry{}, nil) + validDoc := protocol.DocumentURI("file:///home/user/fievel/roadrunner/application-dw/src/test/java/com/uber/roadrunner/application/exception/GatewayErrorExceptionMapperTest.java") - target, err := GetJavaTarget(workspaceRoot, validDoc) + target, err := GetJavaTarget(fs, workspaceRoot, validDoc) assert.NoError(t, err) assert.Equal(t, "roadrunner/application-dw/...", target) }) t.Run("missing workspace root", func(t *testing.T) { + ctrl := gomock.NewController(t) + fs := fsmock.NewMockUlspFS(ctrl) + invalidDoc := protocol.DocumentURI("file:///home/roadrunner/application-dw/src/test/GatewayErrorExceptionMapperTest.java") - _, err := GetJavaTarget(workspaceRoot, invalidDoc) - assert.Error(t, err) + _, err := GetJavaTarget(fs, workspaceRoot, invalidDoc) + assert.Error(t, err, "uri /home/roadrunner/application-dw/src/test/GatewayErrorExceptionMapperTest.java is not a child of the workspace /home/user/fievel") }) t.Run("missing src", func(t *testing.T) { + ctrl := gomock.NewController(t) + fs := fsmock.NewMockUlspFS(ctrl) + + fs.EXPECT().DirExists(gomock.Any()).Return(false, nil) + fs.EXPECT().ReadDir(gomock.Any()).Return([]os.DirEntry{}, nil) + invalidDoc := protocol.DocumentURI("file:///home/user/fievel/roadrunner/GatewayErrorExceptionMapperTest.java") - _, err := GetJavaTarget(workspaceRoot, invalidDoc) - assert.Error(t, err) + _, err := GetJavaTarget(fs, workspaceRoot, invalidDoc) + assert.Error(t, err, "no child directory contained a BUILD file") + }) + + t.Run("pseudo bazel-out file", func(t *testing.T) { + ctrl := gomock.NewController(t) + fs := fsmock.NewMockUlspFS(ctrl) + + validDoc := protocol.DocumentURI("file:///tmp/bazel_user/abcdef1234567890/bazel-out/k8-fastbuild/bin/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java") + _, err := GetJavaTarget(fs, workspaceRoot, validDoc) + assert.Error(t, err, "uri /tmp/bazel_user/abcdef1234567890/bazel-out/k8-fastbuild/bin/tooling/intellij/uber-intellij-plugin-core/src/main/java/com/uber/intellij/bazel/BazelSyncListener.java is not a child of the workspace /home/user/fievel") }) } diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index d2b9277..27495b6 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -9,18 +9,17 @@ sh_binary( visibility = ["//visibility:public"], ) - # Python formatting and linting py_binary( name = "black_check", srcs = ["black_check.py"], - deps = [requirement("black")], visibility = ["//visibility:public"], + deps = [requirement("black")], ) py_binary( name = "black_fix", srcs = ["black_fix.py"], - deps = [requirement("black")], visibility = ["//visibility:public"], + deps = [requirement("black")], )