From e4876dd05090b242b92f3cba4565a04582279b2c Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Fri, 20 Sep 2024 14:45:48 +0200 Subject: [PATCH 1/7] SLVS-1457 Open ManageBindingDialog from SharedBindingGoldBar --- .../SharedBindingSuggestionServiceTests.cs | 108 ++++-------------- .../MefServices/VsSessionHostTests.cs | 3 +- .../SharedBindingSuggestionService.cs | 57 +++------ src/Integration/MefServices/VsSessionHost.cs | 2 +- 4 files changed, 39 insertions(+), 131 deletions(-) diff --git a/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs b/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs index d7f9573e5c..c7d654c026 100644 --- a/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs +++ b/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs @@ -18,15 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using SonarLint.VisualStudio.ConnectedMode.Binding.Suggestion; -using SonarLint.VisualStudio.Integration.Connection; +using SonarLint.VisualStudio.ConnectedMode.UI; using SonarLint.VisualStudio.Integration.MefServices; -using SonarLint.VisualStudio.Integration.TeamExplorer; -using SonarLint.VisualStudio.Integration.WPF; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client; @@ -35,13 +29,28 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.MefServices; [TestClass] public class SharedBindingSuggestionServiceTests { + private SharedBindingSuggestionService testSubject; + private ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar; + private IConnectedModeServices connectedModeServices; + private IConnectedModeBindingServices connectedModeBindingServices; + + [TestInitialize] + public void TestInitialize() + { + suggestSharedBindingGoldBar = Substitute.For(); + connectedModeServices = Substitute.For(); + connectedModeBindingServices = Substitute.For(); + + testSubject = new SharedBindingSuggestionService(suggestSharedBindingGoldBar, connectedModeServices, connectedModeBindingServices); + } + [TestMethod] public void MefCtor_CheckExports() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); } [TestMethod] @@ -50,98 +59,27 @@ public void MefCtor_CheckIsSingleton() MefTestHelpers.CheckIsSingletonMefComponent(); } - [TestMethod] - public void ConnectAfterTeamExplorerInitialized_ConnectedModeWindowLoaded_NotLoaded_ConnectScheduledForLater() - { - var testSubject = CreateTestSubject(out var bindingGoldBar, out var teamExplorerController, out var scheduler); - testSubject.Suggest(ServerType.SonarQube, () => null); - - var callSequence = new MockSequence(); - - scheduler.InSequence(callSequence).Setup(x => x.ScheduleActionOnNextEvent(It.IsAny())); - teamExplorerController.InSequence(callSequence).Setup(x => x.ShowSonarQubePage()); - - CallConnectHandler(bindingGoldBar); - - scheduler.Verify(x => x.ScheduleActionOnNextEvent(It.IsAny()), Times.Once); - scheduler.VerifyNoOtherCalls(); - teamExplorerController.Verify(x => x.ShowSonarQubePage(), Times.Once); - teamExplorerController.VerifyNoOtherCalls(); - } - - [TestMethod] - public void ConnectAfterTeamExplorerInitialized_ConnectedModeWindowLoaded_CallsConnectCommand() - { - var testSubject = CreateTestSubject(out var bindingGoldBar, out var teamExplorerController, out var scheduler); - testSubject.Suggest(ServerType.SonarQube, CreateConnectCommandProvider(out var connectCommand)); - - var callSequence = new MockSequence(); - - teamExplorerController.InSequence(callSequence).Setup(x => x.ShowSonarQubePage()); - connectCommand.InSequence(callSequence) - .Setup(x => x.CanExecute(testSubject.autobindEnabledConfiguration)) - .Returns(true); - connectCommand.InSequence(callSequence) - .Setup(x => x.Execute(testSubject.autobindEnabledConfiguration)); - - CallConnectHandler(bindingGoldBar); - - scheduler.VerifyNoOtherCalls(); - teamExplorerController.Verify(x => x.ShowSonarQubePage(), Times.Once); - teamExplorerController.VerifyNoOtherCalls(); - connectCommand.Verify(x => x.CanExecute(testSubject.autobindEnabledConfiguration), Times.Once); - connectCommand.Verify(x => x.Execute(testSubject.autobindEnabledConfiguration), Times.Once); - connectCommand.VerifyNoOtherCalls(); - } - [TestMethod] public void Suggest_HasServerType_ShowsGoldBar() { - var testSubject = CreateTestSubject(out var bindingGoldBar, out _, out _); - - testSubject.Suggest(ServerType.SonarQube, CreateConnectCommandProvider(out _)); + testSubject.Suggest(ServerType.SonarQube); - bindingGoldBar.Verify(x => x.Show(ServerType.SonarQube, It.IsAny()), Times.Once); + suggestSharedBindingGoldBar.Received(1).Show(ServerType.SonarQube, Arg.Any()); } [TestMethod] public void Suggest_NoServerType_DoesNotCallGoldBar() { - var testSubject = CreateTestSubject(out var bindingGoldBar, out _, out _); + testSubject.Suggest(null); - testSubject.Suggest(null, CreateConnectCommandProvider(out _)); - - bindingGoldBar.Verify(x => x.Show(It.IsAny(), It.IsAny()), Times.Never); + suggestSharedBindingGoldBar.DidNotReceive().Show(Arg.Any(), Arg.Any()); } [TestMethod] public void Close_ClosesGoldBar() { - var testSubject = CreateTestSubject(out var bindingGoldBar, out _, out _); - testSubject.Close(); - bindingGoldBar.Verify(x => x.Close()); - } - - private void CallConnectHandler(Mock mock) - { - ((Action)mock.Invocations.Single().Arguments[1])(); - } - - private Func> CreateConnectCommandProvider(out Mock> connectCommandMock) - { - var commandMock = new Mock>(); - connectCommandMock = commandMock; - return () => commandMock.Object; - } - - private SharedBindingSuggestionService CreateTestSubject(out Mock bindingGoldBar, - out Mock teamExplorerController, - out Mock connectedModeWindowEventBasedScheduler) - { - return new SharedBindingSuggestionService((bindingGoldBar = new Mock()).Object, - (teamExplorerController = new Mock()).Object, - (connectedModeWindowEventBasedScheduler = new Mock()).Object); + suggestSharedBindingGoldBar.Received(1).Close(); } } diff --git a/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs b/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs index cca5c83122..b9dbfa4b99 100644 --- a/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs +++ b/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs @@ -525,8 +525,7 @@ private static void CheckResetConnectionCalledTimes(VsSessionHost testSubject, i private void CheckSuggestionShowCalledTimes(int count, ServerType? serverType = ServerType.SonarQube) { - sharedBindingSuggestionService.Verify(x => x.Suggest(serverType ?? It.IsAny(), - It.IsAny>>()), + sharedBindingSuggestionService.Verify(x => x.Suggest(serverType ?? It.IsAny()), Times.Exactly(count)); sharedBindingSuggestionService.VerifyNoOtherCalls(); } diff --git a/src/Integration/MefServices/SharedBindingSuggestionService.cs b/src/Integration/MefServices/SharedBindingSuggestionService.cs index cbf7fe4759..079db7fa83 100644 --- a/src/Integration/MefServices/SharedBindingSuggestionService.cs +++ b/src/Integration/MefServices/SharedBindingSuggestionService.cs @@ -18,20 +18,18 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; +using System.Windows; using SonarLint.VisualStudio.ConnectedMode.Binding.Suggestion; -using SonarLint.VisualStudio.ConnectedMode.Shared; -using SonarLint.VisualStudio.Integration.Connection; -using SonarLint.VisualStudio.Integration.TeamExplorer; -using SonarLint.VisualStudio.Integration.WPF; +using SonarLint.VisualStudio.ConnectedMode.UI; +using SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding; using SonarQube.Client; namespace SonarLint.VisualStudio.Integration.MefServices { internal interface ISharedBindingSuggestionService { - void Suggest(ServerType? serverType, Func> connectCommandProvider); + void Suggest(ServerType? serverType); void Close(); } @@ -40,31 +38,28 @@ internal interface ISharedBindingSuggestionService [PartCreationPolicy(CreationPolicy.Shared)] internal class SharedBindingSuggestionService : ISharedBindingSuggestionService { - internal /* for testing */ readonly ConnectConfiguration autobindEnabledConfiguration = - new ConnectConfiguration { UseSharedBinding = true }; - private readonly ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar; - private readonly ITeamExplorerController teamExplorerController; - private readonly IConnectedModeWindowEventBasedScheduler connectedModeWindowEventBasedScheduler; + private readonly IConnectedModeServices connectedModeServices; + private readonly IConnectedModeBindingServices connectedModeBindingServices; [ImportingConstructor] public SharedBindingSuggestionService(ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar, - ITeamExplorerController teamExplorerController, - IConnectedModeWindowEventBasedScheduler connectedModeWindowEventBasedScheduler) + IConnectedModeServices connectedModeServices, + IConnectedModeBindingServices connectedModeBindingServices) { this.suggestSharedBindingGoldBar = suggestSharedBindingGoldBar; - this.teamExplorerController = teamExplorerController; - this.connectedModeWindowEventBasedScheduler = connectedModeWindowEventBasedScheduler; + this.connectedModeServices = connectedModeServices; + this.connectedModeBindingServices = connectedModeBindingServices; } - public void Suggest(ServerType? serverType, Func> connectCommandProvider) + public void Suggest(ServerType? serverType) { if (serverType == null) { return; } - suggestSharedBindingGoldBar.Show(serverType.Value, () => ConnectAfterTeamExplorerInitialized(connectCommandProvider)); + suggestSharedBindingGoldBar.Show(serverType.Value, AutoBind); } public void Close() @@ -72,33 +67,9 @@ public void Close() suggestSharedBindingGoldBar.Close(); } - private void ConnectAfterTeamExplorerInitialized(Func> connectCommandProvider) + private void AutoBind() { - if (IsConnectedModeWindowLoaded(connectCommandProvider, out var connectCommand)) - { - teamExplorerController.ShowSonarQubePage(); - Autobind(connectCommand); - } - else - { - connectedModeWindowEventBasedScheduler.ScheduleActionOnNextEvent(() => Autobind(connectCommandProvider())); - teamExplorerController.ShowSonarQubePage(); - } - } - - private bool IsConnectedModeWindowLoaded(Func> connectCommandProvider, - out ICommand connectCommand) - { - connectCommand = connectCommandProvider(); - return connectCommand != null; - } - - private void Autobind(ICommand connectCommand) - { - if (connectCommand?.CanExecute(autobindEnabledConfiguration) ?? false) - { - connectCommand.Execute(autobindEnabledConfiguration); - } + new ManageBindingDialog(connectedModeServices, connectedModeBindingServices).ShowDialog(Application.Current.MainWindow); } } } diff --git a/src/Integration/MefServices/VsSessionHost.cs b/src/Integration/MefServices/VsSessionHost.cs index 05b6dabe60..bde8736fbe 100644 --- a/src/Integration/MefServices/VsSessionHost.cs +++ b/src/Integration/MefServices/VsSessionHost.cs @@ -255,7 +255,7 @@ private void UpdateSharedBindingSuggestion() VisualStateManager.HasSharedBinding = SharedBindingConfig != null; VisualStateManager.ResetConnectionConfiguration(); - sharedBindingSuggestionService.Suggest(SharedBindingConfig.GetServerType(),() => this.ActiveSection?.ConnectCommand); + sharedBindingSuggestionService.Suggest(SharedBindingConfig.GetServerType()); } private void ClearCurrentBinding() From 9af26cdfce2f1dd7bfe706a0268287b1fd8b9370 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Fri, 20 Sep 2024 17:40:51 +0200 Subject: [PATCH 2/7] SLVS-1457 Fix crash when starting solution caused by circular reference between MEF components. - The circular reference was: VsSessionHost -> SharedBindingSuggestionService -> UnintrusiveBindingController -> ActiveSolutionBoundTracker -> VsSessionHost - Fixed it by removing the SharedBindingSuggestionService from VsSessionHost and instead let it show the goldbar, whenever the active solution state changed --- .../SharedBindingSuggestionServiceTests.cs | 74 ++++++++++++++++--- .../MefServices/VsSessionHostTests.cs | 41 +--------- .../SonarLintIntegrationPackage.cs | 8 +- .../SharedBindingSuggestionService.cs | 65 ++++++++++++---- src/Integration/MefServices/VsSessionHost.cs | 36 --------- 5 files changed, 121 insertions(+), 103 deletions(-) diff --git a/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs b/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs index c7d654c026..e322ff8c08 100644 --- a/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs +++ b/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs @@ -19,7 +19,10 @@ */ using SonarLint.VisualStudio.ConnectedMode.Binding.Suggestion; +using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.ConnectedMode.UI; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Integration.MefServices; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client; @@ -33,6 +36,7 @@ public class SharedBindingSuggestionServiceTests private ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar; private IConnectedModeServices connectedModeServices; private IConnectedModeBindingServices connectedModeBindingServices; + private IActiveSolutionTracker activeSolutionTracker; [TestInitialize] public void TestInitialize() @@ -40,8 +44,9 @@ public void TestInitialize() suggestSharedBindingGoldBar = Substitute.For(); connectedModeServices = Substitute.For(); connectedModeBindingServices = Substitute.For(); + activeSolutionTracker = Substitute.For(); - testSubject = new SharedBindingSuggestionService(suggestSharedBindingGoldBar, connectedModeServices, connectedModeBindingServices); + testSubject = new SharedBindingSuggestionService(suggestSharedBindingGoldBar, connectedModeServices, connectedModeBindingServices, activeSolutionTracker); } [TestMethod] @@ -50,7 +55,8 @@ public void MefCtor_CheckExports() MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); } [TestMethod] @@ -60,26 +66,74 @@ public void MefCtor_CheckIsSingleton() } [TestMethod] - public void Suggest_HasServerType_ShowsGoldBar() + public void Suggest_SharedBindingExistsAndIsStandalone_ShowsGoldBar() { - testSubject.Suggest(ServerType.SonarQube); + MockSharedBindingConfigExists(); + MockSolutionMode(SonarLintMode.Standalone); + + testSubject.Suggest(); suggestSharedBindingGoldBar.Received(1).Show(ServerType.SonarQube, Arg.Any()); } [TestMethod] - public void Suggest_NoServerType_DoesNotCallGoldBar() + public void Suggest_SharedBindingExistsAndIsConnected_DoesNotShowGoldBar() + { + MockSharedBindingConfigExists(); + MockSolutionMode(SonarLintMode.Connected); + + testSubject.Suggest(); + + suggestSharedBindingGoldBar.DidNotReceive().Show(ServerType.SonarQube, Arg.Any()); + } + + [TestMethod] + public void Suggest_SharedBindingDoesNotExistAndIsStandAlone_DoesNotShowGoldBar() { - testSubject.Suggest(null); + MockSolutionMode(SonarLintMode.Standalone); + + testSubject.Suggest(); + + suggestSharedBindingGoldBar.DidNotReceive().Show(ServerType.SonarQube, Arg.Any()); + } + + [TestMethod] + public void ActiveSolutionChanged_SolutionIsOpened_ShowsGoldBar() + { + MockSharedBindingConfigExists(); + MockSolutionMode(SonarLintMode.Standalone); - suggestSharedBindingGoldBar.DidNotReceive().Show(Arg.Any(), Arg.Any()); + RaiseActiveSolutionChanged(true); + + suggestSharedBindingGoldBar.Received(1).Show(ServerType.SonarQube, Arg.Any()); } [TestMethod] - public void Close_ClosesGoldBar() + public void ActiveSolutionChanged_SolutionIsOpened_DoesNotShowGoldBar() + { + MockSharedBindingConfigExists(); + MockSolutionMode(SonarLintMode.Standalone); + + RaiseActiveSolutionChanged(false); + + suggestSharedBindingGoldBar.DidNotReceive().Show(ServerType.SonarQube, Arg.Any()); + } + + private void RaiseActiveSolutionChanged(bool isSolutionOpened) + { + activeSolutionTracker.ActiveSolutionChanged += Raise.EventWith(new ActiveSolutionChangedEventArgs(isSolutionOpened)); + } + + private void MockSolutionMode(SonarLintMode mode) { - testSubject.Close(); + connectedModeServices.ConfigurationProvider.GetConfiguration().Returns(new BindingConfiguration(null, mode, string.Empty)); + } - suggestSharedBindingGoldBar.Received(1).Close(); + private void MockSharedBindingConfigExists() + { + connectedModeBindingServices.SharedBindingConfigProvider.GetSharedBinding().Returns(new SharedBindingConfigModel()); } + + + } diff --git a/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs b/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs index b9dbfa4b99..5eb548a99b 100644 --- a/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs +++ b/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs @@ -18,16 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using FluentAssertions; using Microsoft.Alm.Authentication; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SonarLint.VisualStudio.ConnectedMode.Binding; using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Integration.Connection; using SonarLint.VisualStudio.Integration.MefServices; using SonarLint.VisualStudio.Integration.TeamExplorer; using SonarLint.VisualStudio.Integration.WPF; @@ -44,7 +40,6 @@ public class VsSessionHostTests private ConfigurableStateManager stateManager; private ConfigurableProgressStepRunner stepRunner; private ConfigurableConfigurationProvider configProvider; - private Mock sharedBindingConfigProviderMock; private Mock credentialStoreServiceMock; private Mock sharedBindingSuggestionService; private Mock connectedModeWindowEventListener; @@ -57,7 +52,6 @@ public void TestInitialize() this.sonarQubeServiceMock = new Mock(); this.stepRunner = new ConfigurableProgressStepRunner(); this.configProvider = new ConfigurableConfigurationProvider(); - sharedBindingConfigProviderMock = new Mock(); credentialStoreServiceMock = new Mock(); sharedBindingSuggestionService = new Mock(); connectedModeWindowEventListener = new Mock(); @@ -72,9 +66,7 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -100,7 +92,6 @@ public void Dispose_UnsubscribesFromActiveSolutionChanged() trackerMock.VerifyRemove(x => x.ActiveSolutionChanged -= It.IsAny>(), Times.Once); connectedModeWindowEventListener.Verify(x => x.Dispose(), Times.Once); - sharedBindingSuggestionService.Verify(x => x.Close(), Times.Once); } [TestMethod] @@ -373,14 +364,11 @@ public void ResetBinding_SharedConfigSetWhenUnbound() var tracker = new ConfigurableActiveSolutionTracker(); var testSubject = CreateTestSubject(tracker); var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "abcd" }; - sharedBindingConfigProviderMock.Setup(x => x.GetSharedBinding()) - .Returns(sharedBindingConfig); tracker.SimulateActiveSolutionChanged(isSolutionOpen: true); testSubject.SharedBindingConfig.Should().BeSameAs(sharedBindingConfig); testSubject.VisualStateManager.HasSharedBinding.Should().BeTrue(); - CheckSuggestionShowCalledTimes(1); CheckResetConnectionCalledTimes(testSubject, 1); } @@ -390,14 +378,11 @@ public void ResetBinding_SharedConfigNotSetWhenNull() var tracker = new ConfigurableActiveSolutionTracker(); var testSubject = CreateTestSubject(tracker); sharedBindingSuggestionService.Invocations.Clear(); - sharedBindingConfigProviderMock.Setup(x => x.GetSharedBinding()) - .Returns((SharedBindingConfigModel)null); tracker.SimulateActiveSolutionChanged(isSolutionOpen: true); testSubject.SharedBindingConfig.Should().BeNull(); testSubject.VisualStateManager.HasSharedBinding.Should().BeFalse(); - CheckSuggestionShowCalledTimes(1, serverType: null); CheckResetConnectionCalledTimes(testSubject, 1); } @@ -407,15 +392,11 @@ public void ResetBinding_ClosedSolution_DoesNotSuggestSharedBinding() var tracker = new ConfigurableActiveSolutionTracker(); var testSubject = CreateTestSubject(tracker); sharedBindingSuggestionService.Invocations.Clear(); - sharedBindingConfigProviderMock.Setup(x => x.GetSharedBinding()) - .Returns((SharedBindingConfigModel)null); tracker.SimulateActiveSolutionChanged(isSolutionOpen: false); testSubject.SharedBindingConfig.Should().BeNull(); testSubject.VisualStateManager.HasSharedBinding.Should().BeFalse(); - CheckSuggestionClosed(); - CheckSuggestionShowCalledTimes(0); CheckResetConnectionCalledTimes(testSubject, 0); } @@ -424,14 +405,11 @@ public void InitializeBinding_SharedConfigSetWhenUnbound() { var testSubject = CreateTestSubject(); var section = ConfigurableSectionController.CreateDefault(); - sharedBindingConfigProviderMock.Setup(x => x.GetSharedBinding()) - .Returns(new SharedBindingConfigModel { ProjectKey = "abcd" }); testSubject.SetActiveSection(section); testSubject.SharedBindingConfig.Should().NotBeNull(); testSubject.VisualStateManager.HasSharedBinding.Should().BeTrue(); - CheckSuggestionShowCalledTimes(1); CheckResetConnectionCalledTimes(testSubject, 1); } @@ -441,8 +419,6 @@ public void ResetBinding_SharedConfigRemovedWhenBound() var tracker = new ConfigurableActiveSolutionTracker(); var testSubject = CreateTestSubject(tracker); var section = ConfigurableSectionController.CreateDefault(); - sharedBindingConfigProviderMock.Setup(x => x.GetSharedBinding()) - .Returns(new SharedBindingConfigModel { ProjectKey = "abcd" }); testSubject.SetActiveSection(section); ((ConfigurableStateManager)testSubject.VisualStateManager).ResetConnectionConfigCalled = 0; @@ -456,7 +432,6 @@ public void ResetBinding_SharedConfigRemovedWhenBound() testSubject.SharedBindingConfig.Should().BeNull(); this.stateManager.BoundProjectKey.Should().Be("bla"); - CheckSuggestionShowCalledTimes(0); CheckResetConnectionCalledTimes(testSubject, 0); } @@ -468,8 +443,7 @@ public void GetCredentialsForSharedConfig_CallsCredentialServiceOnlyWhenSharedCo { var testSubject = CreateTestSubject(); var section = ConfigurableSectionController.CreateDefault(); - sharedBindingConfigProviderMock.Setup(x => x.GetSharedBinding()) - .Returns(serverUri != null ? new SharedBindingConfigModel { Uri = new Uri(serverUri)} : null); + testSubject.SetActiveSection(section); var credential = new Credential("a"); credentialStoreServiceMock.Setup(x => x.ReadCredentials(It.IsAny())).Returns(credential); @@ -500,10 +474,8 @@ private VsSessionHost CreateTestSubject(IActiveSolutionTracker tracker = null) this.sonarQubeServiceMock.Object, tracker ?? new ConfigurableActiveSolutionTracker(), this.configProvider, - sharedBindingConfigProviderMock.Object, credentialStoreServiceMock.Object, connectedModeWindowEventListener.Object, - sharedBindingSuggestionService.Object, Mock.Of()); this.stateManager.Host = host; @@ -523,17 +495,6 @@ private static void CheckResetConnectionCalledTimes(VsSessionHost testSubject, i ((ConfigurableStateManager)testSubject.VisualStateManager).ResetConnectionConfigCalled.Should().Be(count); } - private void CheckSuggestionShowCalledTimes(int count, ServerType? serverType = ServerType.SonarQube) - { - sharedBindingSuggestionService.Verify(x => x.Suggest(serverType ?? It.IsAny()), - Times.Exactly(count)); - sharedBindingSuggestionService.VerifyNoOtherCalls(); - } - - private void CheckSuggestionClosed() - { - sharedBindingSuggestionService.Verify(x => x.Close()); - } #endregion Helpers } diff --git a/src/Integration.Vsix/SonarLintIntegrationPackage.cs b/src/Integration.Vsix/SonarLintIntegrationPackage.cs index 4aee981a76..26d0734063 100644 --- a/src/Integration.Vsix/SonarLintIntegrationPackage.cs +++ b/src/Integration.Vsix/SonarLintIntegrationPackage.cs @@ -22,10 +22,11 @@ using System.Runtime.InteropServices; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; -using SonarLint.VisualStudio.ConnectedMode.Binding; +using SonarLint.VisualStudio.ConnectedMode.Binding.Suggestion; using SonarLint.VisualStudio.ConnectedMode.UI; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Infrastructure.VS; +using SonarLint.VisualStudio.Integration.MefServices; using SonarLint.VisualStudio.Integration.TeamExplorer; using SonarLint.VisualStudio.IssueVisualization.Helpers; using SonarLint.VisualStudio.Roslyn.Suppressions.InProcess; @@ -69,6 +70,7 @@ public class SonarLintIntegrationPackage : AsyncPackage private ILogger logger; private IRoslynSettingsFileSynchronizer roslynSettingsFileSynchronizer; + private ISharedBindingSuggestionService suggestSharedBindingGoldBar; protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { @@ -103,6 +105,9 @@ private async System.Threading.Tasks.Task InitOnUIThreadAsync() roslynSettingsFileSynchronizer.UpdateFileStorageAsync().Forget(); // don't wait for it to finish Debug.Assert(threadHandling.CheckAccess(), "Still expecting to be on the UI thread"); + suggestSharedBindingGoldBar = serviceProvider.GetMefService(); + suggestSharedBindingGoldBar.Suggest(); + logger.WriteLine(Resources.Strings.SL_InitializationComplete); } catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) @@ -118,6 +123,7 @@ protected override void Dispose(bool disposing) if (disposing) { this.roslynSettingsFileSynchronizer?.Dispose(); + suggestSharedBindingGoldBar?.Dispose(); this.roslynSettingsFileSynchronizer = null; } } diff --git a/src/Integration/MefServices/SharedBindingSuggestionService.cs b/src/Integration/MefServices/SharedBindingSuggestionService.cs index 079db7fa83..349126f81d 100644 --- a/src/Integration/MefServices/SharedBindingSuggestionService.cs +++ b/src/Integration/MefServices/SharedBindingSuggestionService.cs @@ -21,17 +21,18 @@ using System.ComponentModel.Composition; using System.Windows; using SonarLint.VisualStudio.ConnectedMode.Binding.Suggestion; +using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.ConnectedMode.UI; using SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client; namespace SonarLint.VisualStudio.Integration.MefServices { - internal interface ISharedBindingSuggestionService + public interface ISharedBindingSuggestionService : IDisposable { - void Suggest(ServerType? serverType); - - void Close(); + void Suggest(); } [Export(typeof(ISharedBindingSuggestionService))] @@ -41,35 +42,67 @@ internal class SharedBindingSuggestionService : ISharedBindingSuggestionService private readonly ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar; private readonly IConnectedModeServices connectedModeServices; private readonly IConnectedModeBindingServices connectedModeBindingServices; + private readonly IActiveSolutionTracker activeSolutionTracker; + private bool disposed; [ImportingConstructor] - public SharedBindingSuggestionService(ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar, + public SharedBindingSuggestionService( + ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar, IConnectedModeServices connectedModeServices, - IConnectedModeBindingServices connectedModeBindingServices) + IConnectedModeBindingServices connectedModeBindingServices, + IActiveSolutionTracker activeSolutionTracker) { this.suggestSharedBindingGoldBar = suggestSharedBindingGoldBar; this.connectedModeServices = connectedModeServices; this.connectedModeBindingServices = connectedModeBindingServices; + this.activeSolutionTracker = activeSolutionTracker; + + this.activeSolutionTracker.ActiveSolutionChanged += OnActiveSolutionChanged; } - - public void Suggest(ServerType? serverType) + + public void Suggest() { - if (serverType == null) + var sharedBindingConfig = connectedModeBindingServices.SharedBindingConfigProvider.GetSharedBinding(); + var isStandalone = connectedModeServices.ConfigurationProvider.GetConfiguration().Mode == SonarLintMode.Standalone; + + if (sharedBindingConfig?.GetServerType() is { } serverType && isStandalone) { - return; + suggestSharedBindingGoldBar.Show(serverType, ShowManageBindingDialog); } - - suggestSharedBindingGoldBar.Show(serverType.Value, AutoBind); } - public void Close() + public void Dispose() { - suggestSharedBindingGoldBar.Close(); + Dispose(true); + GC.SuppressFinalize(this); } - private void AutoBind() + private void ShowManageBindingDialog() { - new ManageBindingDialog(connectedModeServices, connectedModeBindingServices).ShowDialog(Application.Current.MainWindow); + var manageBindingDialog = new ManageBindingDialog(connectedModeServices, connectedModeBindingServices); + manageBindingDialog.ShowDialog(Application.Current.MainWindow); + } + + private void OnActiveSolutionChanged(object sender, ActiveSolutionChangedEventArgs e) + { + if (e.IsSolutionOpen) + { + Suggest(); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) + { + return; + } + + disposed = true; + if (disposing) + { + activeSolutionTracker.ActiveSolutionChanged -= OnActiveSolutionChanged; + } } } } diff --git a/src/Integration/MefServices/VsSessionHost.cs b/src/Integration/MefServices/VsSessionHost.cs index bde8736fbe..a1b7045330 100644 --- a/src/Integration/MefServices/VsSessionHost.cs +++ b/src/Integration/MefServices/VsSessionHost.cs @@ -18,16 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; -using System.Diagnostics; using Microsoft.Alm.Authentication; using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.ConnectedMode.Binding.Suggestion; using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Integration.Connection; using SonarLint.VisualStudio.Integration.MefServices; using SonarLint.VisualStudio.Integration.Progress; using SonarLint.VisualStudio.Integration.State; @@ -43,14 +39,10 @@ internal sealed class VsSessionHost : IHost, IProgressStepRunnerWrapper, IDispos { private readonly IActiveSolutionTracker solutionTracker; private readonly IConfigurationProvider configurationProvider; - private readonly ISharedBindingConfigProvider sharedBindingConfigProvider; private readonly ICredentialStoreService credentialStoreService; private readonly IConnectedModeWindowEventListener connectedModeWindowEventListener; - private readonly ISharedBindingSuggestionService sharedBindingSuggestionService; - private readonly IProgressStepRunnerWrapper progressStepRunner; - private bool isDisposed; private bool resetBindingWhenAttaching = true; @@ -58,10 +50,8 @@ internal sealed class VsSessionHost : IHost, IProgressStepRunnerWrapper, IDispos public VsSessionHost(ISonarQubeService sonarQubeService, IActiveSolutionTracker solutionTracker, IConfigurationProvider configurationProvider, - ISharedBindingConfigProvider sharedBindingConfigProvider, ICredentialStoreService credentialStoreService, IConnectedModeWindowEventListener connectedModeWindowEventListener, - ISharedBindingSuggestionService sharedBindingSuggestionService, ILogger logger) : this( null, @@ -69,10 +59,8 @@ public VsSessionHost(ISonarQubeService sonarQubeService, sonarQubeService, solutionTracker, configurationProvider, - sharedBindingConfigProvider, credentialStoreService, connectedModeWindowEventListener, - sharedBindingSuggestionService, logger) { } @@ -82,10 +70,8 @@ public VsSessionHost(ISonarQubeService sonarQubeService, ISonarQubeService sonarQubeService, IActiveSolutionTracker solutionTracker, IConfigurationProvider configurationProvider, - ISharedBindingConfigProvider sharedBindingConfigProvider, ICredentialStoreService credentialStoreService, IConnectedModeWindowEventListener connectedModeWindowEventListener, - ISharedBindingSuggestionService sharedBindingSuggestionService, ILogger logger) { this.VisualStateManager = state ?? new StateManager(this, new TransferableVisualState()); @@ -93,10 +79,8 @@ public VsSessionHost(ISonarQubeService sonarQubeService, this.SonarQubeService = sonarQubeService ?? throw new ArgumentNullException(nameof(sonarQubeService)); this.solutionTracker = solutionTracker ?? throw new ArgumentNullException(nameof(solutionTracker)); this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); - this.sharedBindingConfigProvider = sharedBindingConfigProvider; this.credentialStoreService = credentialStoreService; this.connectedModeWindowEventListener = connectedModeWindowEventListener; - this.sharedBindingSuggestionService = sharedBindingSuggestionService; this.solutionTracker.ActiveSolutionChanged += this.OnActiveSolutionChanged; this.Logger = logger ?? throw new ArgumentNullException(nameof(logger)); connectedModeWindowEventListener.SubscribeToConnectedModeWindowEvents(this); @@ -222,15 +206,6 @@ private void ResetBinding(bool abortCurrentlyRunningWorklows, bool clearCurrentB if (clearCurrentBinding || bindingConfig == null || bindingConfig.Mode == SonarLintMode.Standalone) { this.ClearCurrentBinding(); - - if (!clearCurrentBinding) // when solution is open, but unbound - { - UpdateSharedBindingSuggestion(); - } - else - { - sharedBindingSuggestionService.Close(); - } } else { @@ -248,16 +223,6 @@ private void ResetBinding(bool abortCurrentlyRunningWorklows, bool clearCurrentB } } - private void UpdateSharedBindingSuggestion() - { - SharedBindingConfig = sharedBindingConfigProvider.GetSharedBinding(); - - VisualStateManager.HasSharedBinding = SharedBindingConfig != null; - VisualStateManager.ResetConnectionConfiguration(); - - sharedBindingSuggestionService.Suggest(SharedBindingConfig.GetServerType()); - } - private void ClearCurrentBinding() { this.VisualStateManager.BoundProjectKey = null; @@ -316,7 +281,6 @@ private void Dispose(bool disposing) { this.solutionTracker.ActiveSolutionChanged -= this.OnActiveSolutionChanged; connectedModeWindowEventListener.Dispose(); - sharedBindingSuggestionService.Close(); } this.isDisposed = true; From 9a019e35b320107359d6c90e09c7d2bfaeb3fe28 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Fri, 20 Sep 2024 18:02:10 +0200 Subject: [PATCH 3/7] SLVS-1457 Fix failing unit tests by removing not needed properties from VsSessionHost, which were used only by the ConnectionController (which should be deleted once the TeamExplorer will be deleted) --- .../SectionControllerTests.cs | 8 +- .../SectionController.cs | 11 +- .../Connection/ConnectControllerTests.cs | 48 ++++---- .../MefServices/VsSessionHostTests.cs | 112 ------------------ .../Connection/ConnectionController.cs | 17 ++- src/Integration/MefServices/IHost.cs | 4 - src/Integration/MefServices/VsSessionHost.cs | 10 -- .../Framework/ConfigurableHost.cs | 9 -- 8 files changed, 55 insertions(+), 164 deletions(-) diff --git a/src/Integration.TeamExplorer.UnitTests/SectionControllerTests.cs b/src/Integration.TeamExplorer.UnitTests/SectionControllerTests.cs index ac10b255e5..c3bf99bcb8 100644 --- a/src/Integration.TeamExplorer.UnitTests/SectionControllerTests.cs +++ b/src/Integration.TeamExplorer.UnitTests/SectionControllerTests.cs @@ -25,6 +25,8 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using SonarLint.VisualStudio.ConnectedMode.Binding; +using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Integration.Binding; using SonarLint.VisualStudio.Integration.TeamExplorer; using SonarLint.VisualStudio.Integration.WPF; @@ -70,7 +72,9 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(new ConfigurableHost()), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); } [TestMethod] @@ -475,7 +479,7 @@ public int QueryStatusReturnsResult private SectionController CreateTestSubject(IWebBrowser webBrowser = null) { - var controller = new SectionController(serviceProvider, host, webBrowser ?? new ConfigurableWebBrowser(), Mock.Of()); + var controller = new SectionController(serviceProvider, host, webBrowser ?? new ConfigurableWebBrowser(), Mock.Of(), Mock.Of(), Mock.Of()); controller.Initialize(null, new SectionInitializeEventArgs(new ServiceContainer(), null)); return controller; } diff --git a/src/Integration.TeamExplorer/SectionController.cs b/src/Integration.TeamExplorer/SectionController.cs index ed3b516568..71c3b53ce8 100644 --- a/src/Integration.TeamExplorer/SectionController.cs +++ b/src/Integration.TeamExplorer/SectionController.cs @@ -28,6 +28,7 @@ using Microsoft.TeamFoundation.Controls.WPF.TeamExplorer; using Microsoft.VisualStudio.Shell; using SonarLint.VisualStudio.ConnectedMode.Binding; +using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Integration.Binding; using SonarLint.VisualStudio.Integration.Connection; using SonarLint.VisualStudio.Integration.Progress; @@ -55,18 +56,24 @@ internal class SectionController : TeamExplorerSectionBase, ISectionController private readonly IServiceProvider serviceProvider; private readonly IWebBrowser webBrowser; private readonly IAutoBindTrigger autoBindTrigger; + private readonly ISharedBindingConfigProvider sharedBindingConfigProvider; + private readonly ICredentialStoreService credentialStoreService; [ImportingConstructor] public SectionController( [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, IHost host, IWebBrowser webBrowser, - IAutoBindTrigger autoBindTrigger) + IAutoBindTrigger autoBindTrigger, + ISharedBindingConfigProvider sharedBindingConfigProvider, + ICredentialStoreService credentialStoreService) { this.serviceProvider = serviceProvider; this.Host = host; this.webBrowser = webBrowser; this.autoBindTrigger = autoBindTrigger; + this.sharedBindingConfigProvider = sharedBindingConfigProvider; + this.credentialStoreService = credentialStoreService; } internal /*for testing purposes*/ List CommandTargets @@ -238,7 +245,7 @@ private void InitializeControllerCommands() { // Due to complexity of connect and bind we "outsource" the controlling part // to separate controllers which just expose commands - var connectionController = new Connection.ConnectionController(serviceProvider, Host, autoBindTrigger); + var connectionController = new Connection.ConnectionController(serviceProvider, Host, autoBindTrigger, sharedBindingConfigProvider, credentialStoreService); var bindingController = new Binding.BindingController(serviceProvider, Host); this.CommandTargets.Add(connectionController); diff --git a/src/Integration.UnitTests/Connection/ConnectControllerTests.cs b/src/Integration.UnitTests/Connection/ConnectControllerTests.cs index 82e81f1bcf..e8f4dc396b 100644 --- a/src/Integration.UnitTests/Connection/ConnectControllerTests.cs +++ b/src/Integration.UnitTests/Connection/ConnectControllerTests.cs @@ -25,6 +25,7 @@ using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using SonarLint.VisualStudio.ConnectedMode.Binding; using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Integration.Binding; @@ -48,6 +49,8 @@ public class ConnectControllerTests private ConfigurableSonarLintSettings settings; private Mock solutionInfoProvider; private TestLogger logger; + private Mock sharedBindingConfigProvider; + private Mock credentialsStore; [TestInitialize] public void TestInit() @@ -66,6 +69,8 @@ public void TestInit() SonarQubeService = this.sonarQubeServiceMock.Object, Logger = logger }; + this.sharedBindingConfigProvider = new Mock(); + this.credentialsStore = new Mock(); IComponentModel componentModel = ConfigurableComponentModel.CreateWithExports( new [] @@ -81,9 +86,9 @@ public void TestInit() [TestMethod] public void ConnectionController_Ctor_ArgumentChecks() { - Exceptions.Expect(() => new ConnectionController(null, Mock.Of(), Mock.Of())); - Exceptions.Expect(() => new ConnectionController(Mock.Of(), null, Mock.Of())); - Exceptions.Expect(() => new ConnectionController(Mock.Of(), Mock.Of(), null)); + Exceptions.Expect(() => new ConnectionController(null, Mock.Of(), Mock.Of(), sharedBindingConfigProvider.Object, credentialsStore.Object)); + Exceptions.Expect(() => new ConnectionController(Mock.Of(), null, Mock.Of(), sharedBindingConfigProvider.Object, credentialsStore.Object)); + Exceptions.Expect(() => new ConnectionController(Mock.Of(), Mock.Of(), null, sharedBindingConfigProvider.Object, credentialsStore.Object)); } [TestMethod] @@ -184,7 +189,7 @@ public void ConnectionController_ConnectCommand_Execution() var connectionWorkflowMock = CreateWorkflow(); connectionWorkflowMock.Setup(x => x.EstablishConnection(It.IsAny(), It.IsAny())); ConnectionController testSubject = new ConnectionController(this.serviceProvider, this.host, null, - this.connectionProvider, connectionWorkflowMock.Object); + this.connectionProvider, connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); this.solutionInfoProvider.Setup(x => x.IsSolutionFullyOpened()).Returns(true); // Case 1: connection provider return null connection @@ -232,9 +237,11 @@ public void ConnectionController_ConnectCommand_SharedConfigAndCredentialsPresen SetUpOpenSolution(); var connectionProviderMock = new Mock(); var testSubject = new ConnectionController(this.serviceProvider, this.host, null, connectionProviderMock.Object, - connectionWorkflowMock.Object); - host.SharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org"}; - host.CredentialsForSharedConfig = new Credential("user", "pwd"); + connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); + var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org" }; + sharedBindingConfigProvider.Setup(mock => mock.GetSharedBinding()).Returns(sharedBindingConfig); + credentialsStore.Setup(mock => mock.ReadCredentials(It.IsAny())).Returns(new Credential("user", "pwd")); + testSubject.ConnectCommand.Execute(new ConnectConfiguration(){UseSharedBinding = true}); @@ -253,12 +260,13 @@ public void ConnectionController_ConnectCommand_SharedConfig_AsksForCredentialsP SetUpOpenSolution(); var connectionProviderMock = new Mock(); var testSubject = new ConnectionController(this.serviceProvider, this.host, null, connectionProviderMock.Object, - connectionWorkflowMock.Object); - host.SharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org"}; + connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); + var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org" }; + sharedBindingConfigProvider.Setup(mock => mock.GetSharedBinding()).Returns(sharedBindingConfig); var connectionInformation = - new ConnectionInformation(host.SharedBindingConfig.Uri, "user", "pwd".ToSecureString()) + new ConnectionInformation(sharedBindingConfig.Uri, "user", "pwd".ToSecureString()) { - Organization = new SonarQubeOrganization(host.SharedBindingConfig.Organization, string.Empty) + Organization = new SonarQubeOrganization(sharedBindingConfig.Organization, string.Empty) }; SetupConnectionProvider(connectionProviderMock, connectionInformation); @@ -269,7 +277,7 @@ public void ConnectionController_ConnectCommand_SharedConfig_AsksForCredentialsP Times.Once); connectionProviderMock.Verify(x => x.GetConnectionInformation(It.Is(c => - c.ServerUri == host.SharedBindingConfig.Uri && c.Organization.Key == host.SharedBindingConfig.Organization)), + c.ServerUri == sharedBindingConfig.Uri && c.Organization.Key == sharedBindingConfig.Organization)), Times.Once); } @@ -291,14 +299,14 @@ private void TestDisabledSharedConfig(ConnectConfiguration config) SetupConnectionWorkflow(connectionWorkflowMock); SetUpOpenSolution(); var connectionProviderMock = new Mock(); - host.SharedBindingConfig = new SharedBindingConfigModel - { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org" }; - host.CredentialsForSharedConfig = new Credential("user", "pwd"); + var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org" }; + sharedBindingConfigProvider.Setup(mock => mock.GetSharedBinding()).Returns(sharedBindingConfig); + credentialsStore.Setup(mock => mock.ReadCredentials(It.IsAny())).Returns(new Credential("user", "pwd")); var expectedConnection = new ConnectionInformation(new Uri("https://127.0.0.0")); SetupConnectionProvider(connectionProviderMock, expectedConnection); var testSubject = new ConnectionController(this.serviceProvider, this.host, null, - connectionProviderMock.Object, connectionWorkflowMock.Object); + connectionProviderMock.Object, connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); testSubject.ConnectCommand.Execute(config); @@ -321,7 +329,7 @@ public void ConnectionController_ConnectCommand_SharedConfigNotPresentDoesNotAut SetupConnectionProvider(connectionProviderMock, expectedConnection); var testSubject = new ConnectionController(this.serviceProvider, this.host, null, - connectionProviderMock.Object, connectionWorkflowMock.Object); + connectionProviderMock.Object, connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); testSubject.ConnectCommand.Execute(new ConnectConfiguration() { UseSharedBinding = true }); @@ -381,7 +389,7 @@ public void ConnectionController_RefreshCommand_Execution() // Arrange var connectionWorkflowMock = CreateWorkflow(); ConnectionController testSubject = new ConnectionController(serviceProvider, host, null, connectionProvider, - connectionWorkflowMock.Object); + connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); this.connectionProvider.ConnectionInformationToReturn = new ConnectionInformation(new Uri("http://notExpected")); var connection = new ConnectionInformation(new Uri("http://Expected")); // Sanity @@ -405,7 +413,7 @@ public void ConnectionController_SetConnectionInProgress() // Arrange var connectionWorkflowMock = CreateWorkflow(); ConnectionController testSubject = new ConnectionController(serviceProvider, host, null, connectionProvider, - connectionWorkflowMock.Object); + connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); this.solutionInfoProvider.Setup(x => x.IsSolutionFullyOpened()).Returns(true); this.connectionProvider.ConnectionInformationToReturn = null; var progressEvents = new ConfigurableProgressEvents(); @@ -463,6 +471,6 @@ private static void SetupConnectionProvider(Mock } private ConnectionController CreateTestSubject() => - new ConnectionController(serviceProvider, host, Mock.Of()); + new ConnectionController(serviceProvider, host, Mock.Of(), sharedBindingConfigProvider.Object, credentialsStore.Object); } } diff --git a/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs b/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs index 5eb548a99b..373adc597e 100644 --- a/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs +++ b/src/Integration.UnitTests/MefServices/VsSessionHostTests.cs @@ -18,10 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using Microsoft.Alm.Authentication; using Moq; using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Integration.MefServices; @@ -358,110 +356,6 @@ public void VsSessionHost_ResetBinding_ErrorInReadingBinding() this.stateManager.AssignedProjectKey.Should().BeNull(); } - [TestMethod] - public void ResetBinding_SharedConfigSetWhenUnbound() - { - var tracker = new ConfigurableActiveSolutionTracker(); - var testSubject = CreateTestSubject(tracker); - var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "abcd" }; - - tracker.SimulateActiveSolutionChanged(isSolutionOpen: true); - - testSubject.SharedBindingConfig.Should().BeSameAs(sharedBindingConfig); - testSubject.VisualStateManager.HasSharedBinding.Should().BeTrue(); - CheckResetConnectionCalledTimes(testSubject, 1); - } - - [TestMethod] - public void ResetBinding_SharedConfigNotSetWhenNull() - { - var tracker = new ConfigurableActiveSolutionTracker(); - var testSubject = CreateTestSubject(tracker); - sharedBindingSuggestionService.Invocations.Clear(); - - tracker.SimulateActiveSolutionChanged(isSolutionOpen: true); - - testSubject.SharedBindingConfig.Should().BeNull(); - testSubject.VisualStateManager.HasSharedBinding.Should().BeFalse(); - CheckResetConnectionCalledTimes(testSubject, 1); - } - - [TestMethod] - public void ResetBinding_ClosedSolution_DoesNotSuggestSharedBinding() - { - var tracker = new ConfigurableActiveSolutionTracker(); - var testSubject = CreateTestSubject(tracker); - sharedBindingSuggestionService.Invocations.Clear(); - - tracker.SimulateActiveSolutionChanged(isSolutionOpen: false); - - testSubject.SharedBindingConfig.Should().BeNull(); - testSubject.VisualStateManager.HasSharedBinding.Should().BeFalse(); - CheckResetConnectionCalledTimes(testSubject, 0); - } - - [TestMethod] - public void InitializeBinding_SharedConfigSetWhenUnbound() - { - var testSubject = CreateTestSubject(); - var section = ConfigurableSectionController.CreateDefault(); - - testSubject.SetActiveSection(section); - - testSubject.SharedBindingConfig.Should().NotBeNull(); - testSubject.VisualStateManager.HasSharedBinding.Should().BeTrue(); - CheckResetConnectionCalledTimes(testSubject, 1); - } - - [TestMethod] - public void ResetBinding_SharedConfigRemovedWhenBound() - { - var tracker = new ConfigurableActiveSolutionTracker(); - var testSubject = CreateTestSubject(tracker); - var section = ConfigurableSectionController.CreateDefault(); - testSubject.SetActiveSection(section); - ((ConfigurableStateManager)testSubject.VisualStateManager).ResetConnectionConfigCalled = 0; - - testSubject.SharedBindingConfig.Should().NotBeNull(); - sharedBindingSuggestionService.Invocations.Clear(); - - this.stateManager.SetBoundProject(new Uri("http://bound"), null, "bla"); - SetConfiguration(new BoundServerProject("solution", "bla", new ServerConnection.SonarQube(new Uri("http://bound"))), SonarLintMode.Connected); - - tracker.SimulateActiveSolutionChanged(isSolutionOpen: true); - - testSubject.SharedBindingConfig.Should().BeNull(); - this.stateManager.BoundProjectKey.Should().Be("bla"); - CheckResetConnectionCalledTimes(testSubject, 0); - } - - [DataRow(null)] - [DataRow("http://localhost:9000")] - [DataRow("https://sonarqube.io")] - [DataTestMethod] - public void GetCredentialsForSharedConfig_CallsCredentialServiceOnlyWhenSharedConfigExists(string serverUri) - { - var testSubject = CreateTestSubject(); - var section = ConfigurableSectionController.CreateDefault(); - - testSubject.SetActiveSection(section); - var credential = new Credential("a"); - credentialStoreServiceMock.Setup(x => x.ReadCredentials(It.IsAny())).Returns(credential); - - var result = testSubject.GetCredentialsForSharedConfig(); - - if (serverUri == null) - { - credentialStoreServiceMock.Verify(x => x.ReadCredentials(It.IsAny()), Times.Never); - result.Should().BeNull(); - } - else - { - credentialStoreServiceMock.Verify(x => x.ReadCredentials(It.IsAny()), Times.Once); - result.Should().BeSameAs(credential); - } - } - #endregion Tests #region Helpers @@ -489,12 +383,6 @@ private void SetConfiguration(BoundServerProject project, SonarLintMode mode) this.configProvider.ModeToReturn = mode; this.configProvider.FolderPathToReturn = "c:\\test\\"; } - - private static void CheckResetConnectionCalledTimes(VsSessionHost testSubject, int count) - { - ((ConfigurableStateManager)testSubject.VisualStateManager).ResetConnectionConfigCalled.Should().Be(count); - } - #endregion Helpers } diff --git a/src/Integration/Connection/ConnectionController.cs b/src/Integration/Connection/ConnectionController.cs index 04f2f19379..07d389d5c4 100644 --- a/src/Integration/Connection/ConnectionController.cs +++ b/src/Integration/Connection/ConnectionController.cs @@ -23,6 +23,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using SonarLint.VisualStudio.ConnectedMode.Binding; +using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Integration.Binding; using SonarLint.VisualStudio.Integration.Progress; @@ -45,11 +46,13 @@ internal sealed class ConnectionController : HostedCommandControllerBase, IConne { private readonly IAutoBindTrigger autoBindTrigger; private readonly IHost host; + private readonly ISharedBindingConfigProvider sharedBindingConfigProvider; + private readonly ICredentialStoreService credentialStoreService; private readonly IConnectionInformationProvider connectionProvider; private readonly ISolutionInfoProvider solutionInfoProvider; - public ConnectionController(IServiceProvider serviceProvider, IHost host, IAutoBindTrigger autoBindTrigger) - : this(serviceProvider, host, autoBindTrigger, null, null) + public ConnectionController(IServiceProvider serviceProvider, IHost host, IAutoBindTrigger autoBindTrigger, ISharedBindingConfigProvider sharedBindingConfigProvider, ICredentialStoreService credentialStoreService) + : this(serviceProvider, host, autoBindTrigger, null, null, sharedBindingConfigProvider, credentialStoreService) { if (autoBindTrigger == null) { @@ -61,10 +64,14 @@ public ConnectionController(IServiceProvider serviceProvider, IHost host, IAutoB IHost host, IAutoBindTrigger autoBindTrigger, IConnectionInformationProvider connectionProvider, - IConnectionWorkflowExecutor workflowExecutor) + IConnectionWorkflowExecutor workflowExecutor, + ISharedBindingConfigProvider sharedBindingConfigProvider, + ICredentialStoreService credentialStoreService) : base(serviceProvider) { this.host = host ?? throw new ArgumentNullException(nameof(host)); + this.sharedBindingConfigProvider = sharedBindingConfigProvider; + this.credentialStoreService = credentialStoreService; this.autoBindTrigger = workflowExecutor == null ? autoBindTrigger : null; this.WorkflowExecutor = workflowExecutor ?? this; this.connectionProvider = connectionProvider ?? this; @@ -116,8 +123,8 @@ private void OnConnect(ConnectConfiguration configuration) ConnectionInformation connectionInfo; - var sharedConfig = host.SharedBindingConfig; - var credentials = host.GetCredentialsForSharedConfig(); + var sharedConfig = sharedBindingConfigProvider.GetSharedBinding(); + var credentials = sharedConfig == null ? null : credentialStoreService.ReadCredentials(sharedConfig.Uri); var useSharedConfig = configuration?.UseSharedBinding ?? false; if (useSharedConfig && sharedConfig != null) diff --git a/src/Integration/MefServices/IHost.cs b/src/Integration/MefServices/IHost.cs index af777229a1..3e9c5a3fe2 100644 --- a/src/Integration/MefServices/IHost.cs +++ b/src/Integration/MefServices/IHost.cs @@ -40,10 +40,6 @@ internal interface IHost /// IStateManager VisualStateManager { get; } - SharedBindingConfigModel SharedBindingConfig { get; } - - Credential GetCredentialsForSharedConfig(); - /// /// The currently active section. Null when no active section. /// diff --git a/src/Integration/MefServices/VsSessionHost.cs b/src/Integration/MefServices/VsSessionHost.cs index a1b7045330..18ade46d47 100644 --- a/src/Integration/MefServices/VsSessionHost.cs +++ b/src/Integration/MefServices/VsSessionHost.cs @@ -102,17 +102,9 @@ void IProgressStepRunnerWrapper.ChangeHost(IProgressControlHost host) public event EventHandler ActiveSectionChanged; public IStateManager VisualStateManager { get; } - public SharedBindingConfigModel SharedBindingConfig { get; private set; } public ISonarQubeService SonarQubeService { get; } - public Credential GetCredentialsForSharedConfig() - { - return SharedBindingConfig == null - ? null - : credentialStoreService.ReadCredentials(SharedBindingConfig.Uri); - } - public ISectionController ActiveSection { get; private set; } public ILogger Logger { get; } @@ -233,8 +225,6 @@ private void ClearCurrentBinding() private void ApplyBindingInformation(BindingConfiguration bindingConfig) { - SharedBindingConfig = null; - // Set the project key that should become bound once the connection workflow has completed this.VisualStateManager.BoundProjectKey = bindingConfig.Project.ServerProjectKey; this.VisualStateManager.BoundProjectName = bindingConfig.Project.ServerProjectKey; diff --git a/src/TestInfrastructure/Framework/ConfigurableHost.cs b/src/TestInfrastructure/Framework/ConfigurableHost.cs index f3edf98b34..d52d512fde 100644 --- a/src/TestInfrastructure/Framework/ConfigurableHost.cs +++ b/src/TestInfrastructure/Framework/ConfigurableHost.cs @@ -42,13 +42,6 @@ public ConfigurableHost() public event EventHandler ActiveSectionChanged; - public SharedBindingConfigModel SharedBindingConfig { get; set; } - - public Credential GetCredentialsForSharedConfig() - { - return CredentialsForSharedConfig; - } - public ISectionController ActiveSection { get; @@ -90,8 +83,6 @@ public void SetActiveSection(ISectionController section) #endregion IHost #region Test helpers - - public Credential CredentialsForSharedConfig { get; set; } public void SimulateActiveSectionChanged() { From 505e5d38430df01083b83bb2c1ba28c599e3e3bb Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Mon, 23 Sep 2024 08:46:09 +0200 Subject: [PATCH 4/7] SLVS-1457 Add test to make sure event handler is unsubscribed on dispose. --- .../MefServices/SharedBindingSuggestionServiceTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs b/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs index e322ff8c08..e418188a31 100644 --- a/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs +++ b/src/Integration.UnitTests/MefServices/SharedBindingSuggestionServiceTests.cs @@ -119,6 +119,14 @@ public void ActiveSolutionChanged_SolutionIsOpened_DoesNotShowGoldBar() suggestSharedBindingGoldBar.DidNotReceive().Show(ServerType.SonarQube, Arg.Any()); } + [TestMethod] + public void Dispose_UnsubscribesFromActiveSolutionChanged() + { + testSubject.Dispose(); + + activeSolutionTracker.Received(1).ActiveSolutionChanged -= Arg.Any>(); + } + private void RaiseActiveSolutionChanged(bool isSolutionOpened) { activeSolutionTracker.ActiveSolutionChanged += Raise.EventWith(new ActiveSolutionChangedEventArgs(isSolutionOpened)); From f24a1514fb0cba3eb4cd5d75e4c0ae54077f8289 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Mon, 23 Sep 2024 08:54:12 +0200 Subject: [PATCH 5/7] SLVS-1457 Add support to start the shared binding process in the ManageBindingDialog once the "Bind" button in the goldbar is clicked. --- .../UI/ManageBinding/ManageBindingDialog.xaml.cs | 13 ++++++++++--- .../MefServices/SharedBindingSuggestionService.cs | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml.cs b/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml.cs index 9a86e60f15..03125147b0 100644 --- a/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml.cs +++ b/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml.cs @@ -22,10 +22,8 @@ using System.Windows; using System.Windows.Navigation; using Microsoft.VisualStudio.Threading; -using SonarLint.VisualStudio.ConnectedMode.Binding; using SonarLint.VisualStudio.ConnectedMode.UI.ManageConnections; using SonarLint.VisualStudio.ConnectedMode.UI.ProjectSelection; -using SonarLint.VisualStudio.Core; namespace SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding; @@ -33,10 +31,15 @@ namespace SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding; public partial class ManageBindingDialog : Window { private readonly IConnectedModeServices connectedModeServices; + private readonly bool useSharedBindingOnInitialization; - public ManageBindingDialog(IConnectedModeServices connectedModeServices, IConnectedModeBindingServices connectedModeBindingServices) + public ManageBindingDialog( + IConnectedModeServices connectedModeServices, + IConnectedModeBindingServices connectedModeBindingServices, + bool useSharedBindingOnInitialization = false) { this.connectedModeServices = connectedModeServices; + this.useSharedBindingOnInitialization = useSharedBindingOnInitialization; ViewModel = new ManageBindingViewModel(connectedModeServices, connectedModeBindingServices, new ProgressReporterViewModel()); InitializeComponent(); } @@ -66,6 +69,10 @@ private void SelectProject_OnClick(object sender, RoutedEventArgs e) private async void ManageBindingDialog_OnInitialized(object sender, EventArgs e) { await ViewModel.InitializeDataAsync(); + if (useSharedBindingOnInitialization) + { + await ViewModel.UseSharedBindingWithProgressAsync(); + } } private void Unbind_OnClick(object sender, RoutedEventArgs e) diff --git a/src/Integration/MefServices/SharedBindingSuggestionService.cs b/src/Integration/MefServices/SharedBindingSuggestionService.cs index 349126f81d..7d404c17b2 100644 --- a/src/Integration/MefServices/SharedBindingSuggestionService.cs +++ b/src/Integration/MefServices/SharedBindingSuggestionService.cs @@ -79,7 +79,7 @@ public void Dispose() private void ShowManageBindingDialog() { - var manageBindingDialog = new ManageBindingDialog(connectedModeServices, connectedModeBindingServices); + var manageBindingDialog = new ManageBindingDialog(connectedModeServices, connectedModeBindingServices, useSharedBindingOnInitialization:true); manageBindingDialog.ShowDialog(Application.Current.MainWindow); } From 19324b5ce31a4ab1246b510dcb797658ad3e827e Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Sep 2024 11:00:25 +0200 Subject: [PATCH 6/7] SLVS-1457 Fix typos --- .../Connection/ConnectControllerTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Integration.UnitTests/Connection/ConnectControllerTests.cs b/src/Integration.UnitTests/Connection/ConnectControllerTests.cs index e8f4dc396b..8039a66cc4 100644 --- a/src/Integration.UnitTests/Connection/ConnectControllerTests.cs +++ b/src/Integration.UnitTests/Connection/ConnectControllerTests.cs @@ -238,7 +238,7 @@ public void ConnectionController_ConnectCommand_SharedConfigAndCredentialsPresen var connectionProviderMock = new Mock(); var testSubject = new ConnectionController(this.serviceProvider, this.host, null, connectionProviderMock.Object, connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); - var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org" }; + var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloud.io"), Organization = "Org" }; sharedBindingConfigProvider.Setup(mock => mock.GetSharedBinding()).Returns(sharedBindingConfig); credentialsStore.Setup(mock => mock.ReadCredentials(It.IsAny())).Returns(new Credential("user", "pwd")); @@ -261,7 +261,7 @@ public void ConnectionController_ConnectCommand_SharedConfig_AsksForCredentialsP var connectionProviderMock = new Mock(); var testSubject = new ConnectionController(this.serviceProvider, this.host, null, connectionProviderMock.Object, connectionWorkflowMock.Object, sharedBindingConfigProvider.Object, credentialsStore.Object); - var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org" }; + var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloud.io"), Organization = "Org" }; sharedBindingConfigProvider.Setup(mock => mock.GetSharedBinding()).Returns(sharedBindingConfig); var connectionInformation = new ConnectionInformation(sharedBindingConfig.Uri, "user", "pwd".ToSecureString()) @@ -299,7 +299,7 @@ private void TestDisabledSharedConfig(ConnectConfiguration config) SetupConnectionWorkflow(connectionWorkflowMock); SetUpOpenSolution(); var connectionProviderMock = new Mock(); - var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloudi.io"), Organization = "Org" }; + var sharedBindingConfig = new SharedBindingConfigModel { ProjectKey = "projectKey", Uri = new Uri("https://sonarcloud.io"), Organization = "Org" }; sharedBindingConfigProvider.Setup(mock => mock.GetSharedBinding()).Returns(sharedBindingConfig); credentialsStore.Setup(mock => mock.ReadCredentials(It.IsAny())).Returns(new Credential("user", "pwd")); var expectedConnection = new ConnectionInformation(new Uri("https://127.0.0.0")); From 062f61aacfbd0b16940e13d6e4f3a08a706d41da Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Sep 2024 11:17:17 +0200 Subject: [PATCH 7/7] SLVS-1379 Make class sealed --- .../SharedBindingSuggestionService.cs | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/Integration/MefServices/SharedBindingSuggestionService.cs b/src/Integration/MefServices/SharedBindingSuggestionService.cs index 7d404c17b2..89cbf795ed 100644 --- a/src/Integration/MefServices/SharedBindingSuggestionService.cs +++ b/src/Integration/MefServices/SharedBindingSuggestionService.cs @@ -26,7 +26,6 @@ using SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; -using SonarQube.Client; namespace SonarLint.VisualStudio.Integration.MefServices { @@ -37,13 +36,12 @@ public interface ISharedBindingSuggestionService : IDisposable [Export(typeof(ISharedBindingSuggestionService))] [PartCreationPolicy(CreationPolicy.Shared)] - internal class SharedBindingSuggestionService : ISharedBindingSuggestionService + internal sealed class SharedBindingSuggestionService : ISharedBindingSuggestionService { private readonly ISuggestSharedBindingGoldBar suggestSharedBindingGoldBar; private readonly IConnectedModeServices connectedModeServices; private readonly IConnectedModeBindingServices connectedModeBindingServices; private readonly IActiveSolutionTracker activeSolutionTracker; - private bool disposed; [ImportingConstructor] public SharedBindingSuggestionService( @@ -73,8 +71,7 @@ public void Suggest() public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); + activeSolutionTracker.ActiveSolutionChanged -= OnActiveSolutionChanged; } private void ShowManageBindingDialog() @@ -90,19 +87,5 @@ private void OnActiveSolutionChanged(object sender, ActiveSolutionChangedEventAr Suggest(); } } - - protected virtual void Dispose(bool disposing) - { - if (disposed) - { - return; - } - - disposed = true; - if (disposing) - { - activeSolutionTracker.ActiveSolutionChanged -= OnActiveSolutionChanged; - } - } } }