From 0b4662fda7abc4f84f645aabe6caebd3d242dc3a Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Mon, 21 Oct 2024 16:41:48 +0200 Subject: [PATCH 1/2] SLVS-1537 Add ISolutionRoslynAnalyzerManager integration to ActiveSolutionBoundTracker --- .../MefServices/ActiveSolutionBoundTrackerTests.cs | 3 ++- .../MefServices/ActiveSolutionBoundTracker.cs | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs b/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs index 695031d496..0534bb59b2 100644 --- a/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs +++ b/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs @@ -25,6 +25,7 @@ using Moq; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.Infrastructure.VS.Roslyn; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client; using SonarQube.Client.Models; @@ -493,7 +494,7 @@ private ActiveSolutionBoundTracker CreateTestSubject( logger ??= new TestLogger(logToConsole: true); gitEvents ??= Mock.Of(); sonarQubeService ??= Mock.Of(); - return new ActiveSolutionBoundTracker(serviceProvider, solutionTracker, configScopeUpdater, logger, gitEvents, configurationProvider, sonarQubeService); + return new ActiveSolutionBoundTracker(serviceProvider, solutionTracker, configScopeUpdater, Substitute.For(), logger, gitEvents, configurationProvider, sonarQubeService); } private void ConfigureService(bool isConnected) diff --git a/src/Integration/MefServices/ActiveSolutionBoundTracker.cs b/src/Integration/MefServices/ActiveSolutionBoundTracker.cs index 762bea9ee3..a66abb4094 100644 --- a/src/Integration/MefServices/ActiveSolutionBoundTracker.cs +++ b/src/Integration/MefServices/ActiveSolutionBoundTracker.cs @@ -23,6 +23,7 @@ using Microsoft.VisualStudio.Shell.Interop; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.Infrastructure.VS.Roslyn; using SonarQube.Client; using ErrorHandler = Microsoft.VisualStudio.ErrorHandler; using Task = System.Threading.Tasks.Task; @@ -48,6 +49,7 @@ internal sealed class ActiveSolutionBoundTracker : IActiveSolutionBoundTracker, private readonly IVsMonitorSelection vsMonitorSelection; private readonly IBoundSolutionGitMonitor gitEventsMonitor; private readonly IConfigScopeUpdater configScopeUpdater; + private readonly ISolutionRoslynAnalyzerManager solutionRoslynAnalyzerManager; private readonly ILogger logger; private readonly uint boundSolutionContextCookie; private bool disposed; @@ -62,6 +64,7 @@ internal sealed class ActiveSolutionBoundTracker : IActiveSolutionBoundTracker, public ActiveSolutionBoundTracker([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, IActiveSolutionTracker activeSolutionTracker, IConfigScopeUpdater configScopeUpdater, + ISolutionRoslynAnalyzerManager solutionRoslynAnalyzerManager, ILogger logger, IBoundSolutionGitMonitor gitEventsMonitor, IConfigurationProvider configurationProvider, @@ -79,6 +82,7 @@ public ActiveSolutionBoundTracker([Import(typeof(SVsServiceProvider))] IServiceP this.configurationProvider = configurationProvider; this.sonarQubeService = sonarQubeService; this.configScopeUpdater = configScopeUpdater; + this.solutionRoslynAnalyzerManager = solutionRoslynAnalyzerManager; // The solution changed inside the IDE solutionTracker.ActiveSolutionChanged += OnActiveSolutionChanged; @@ -97,7 +101,7 @@ public void HandleBindingChange(bool isBindingCleared) return; } - this.RaiseAnalyzersChangedIfBindingChanged(GetBindingConfiguration(), isBindingCleared); + this.RaiseAnalyzersChangedIfBindingChanged(GetBindingConfiguration(), solutionTracker.CurrentSolutionName, isBindingCleared); } private BindingConfiguration GetBindingConfiguration() @@ -127,7 +131,7 @@ private async void OnActiveSolutionChanged(object sender, ActiveSolutionChangedE gitEventsMonitor.Refresh(); - this.RaiseAnalyzersChangedIfBindingChanged(newBindingConfiguration); + this.RaiseAnalyzersChangedIfBindingChanged(newBindingConfiguration, args.SolutionName); } catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) { @@ -155,9 +159,10 @@ await WebServiceHelper.SafeServiceCallAsync(() => sonarQubeService.ConnectAsync( } } - private void RaiseAnalyzersChangedIfBindingChanged(BindingConfiguration newBindingConfiguration, bool? isBindingCleared = null) + private void RaiseAnalyzersChangedIfBindingChanged(BindingConfiguration newBindingConfiguration, string solutionName, bool? isBindingCleared = null) { configScopeUpdater.UpdateConfigScopeForCurrentSolution(newBindingConfiguration.Project); + solutionRoslynAnalyzerManager.OnSolutionChanged(solutionName, newBindingConfiguration); if (!CurrentConfiguration.Equals(newBindingConfiguration)) { From f3d34fc1ee1863a751b00b19590aadb5335dd959 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 22 Oct 2024 13:20:48 +0200 Subject: [PATCH 2/2] Upd --- .../ActiveSolutionBoundTrackerTests.cs | 97 ++++++++++++++++++- .../MefServices/ActiveSolutionBoundTracker.cs | 1 + 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs b/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs index 0534bb59b2..675c1a1475 100644 --- a/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs +++ b/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs @@ -159,11 +159,73 @@ public void ActiveSolutionBoundTracker_UnBoundProject_NullPassedToConfigScopeUpd configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(null)); configScopeUpdaterMock.VerifyNoOtherCalls(); } + + [TestMethod] + public void Ctor_NoSolution_CallsAnalyzerManager() + { + var configScopeUpdaterMock = new Mock(); + var analyzerManager = new Mock(); + + activeSolutionTracker.CurrentSolutionName = null; + ConfigureService(isConnected: false); + + var testSubject = CreateTestSubject( + activeSolutionTracker, + configProvider, + loggerMock.Object, + configScopeUpdater: configScopeUpdaterMock.Object, + sonarQubeService: sonarQubeServiceMock.Object, + solutionRoslynAnalyzerManager: analyzerManager.Object); + + analyzerManager.Verify(x => x.OnSolutionChanged(null, BindingConfiguration.Standalone)); + } + + [TestMethod] + public void Ctor_StandaloneSolution_CallsAnalyzerManager() + { + var configScopeUpdaterMock = new Mock(); + var analyzerManager = new Mock(); + + activeSolutionTracker.CurrentSolutionName = "solution123"; + ConfigureService(isConnected: false); + + var testSubject = CreateTestSubject( + activeSolutionTracker, + configProvider, + loggerMock.Object, + configScopeUpdater: configScopeUpdaterMock.Object, + sonarQubeService: sonarQubeServiceMock.Object, + solutionRoslynAnalyzerManager: analyzerManager.Object); + + analyzerManager.Verify(x => x.OnSolutionChanged("solution123", BindingConfiguration.Standalone)); + } + + [TestMethod] + public void Ctor_BoundSolution_CallsAnalyzerManager() + { + var configScopeUpdaterMock = new Mock(); + var analyzerManager = new Mock(); + + activeSolutionTracker.CurrentSolutionName = "solution123"; + ConfigureService(isConnected: false); + ConfigureSolutionBinding(boundSonarQubeProject); + + var testSubject = CreateTestSubject( + activeSolutionTracker, + configProvider, + loggerMock.Object, + configScopeUpdater: configScopeUpdaterMock.Object, + sonarQubeService: sonarQubeServiceMock.Object, + solutionRoslynAnalyzerManager: analyzerManager.Object); + + analyzerManager.Verify(x => x.OnSolutionChanged("solution123", It.Is(y => y.Mode == SonarLintMode.Connected && y.Project == boundSonarQubeProject))); + } [TestMethod] public void ActiveSolutionBoundTracker_Changes() { var configScopeUpdaterMock = new Mock(); + var analyzerManager = new Mock(); ConfigureService(isConnected: false); ConfigureSolutionBinding(boundSonarQubeProject); @@ -173,7 +235,8 @@ public void ActiveSolutionBoundTracker_Changes() configProvider, loggerMock.Object, configScopeUpdater: configScopeUpdaterMock.Object, - sonarQubeService: sonarQubeServiceMock.Object); + sonarQubeService: sonarQubeServiceMock.Object, + solutionRoslynAnalyzerManager: analyzerManager.Object); var eventCounter = new EventCounter(testSubject); // Sanity @@ -186,6 +249,7 @@ public void ActiveSolutionBoundTracker_Changes() // Case 1: Clear bound project ConfigureSolutionBinding(null); + activeSolutionTracker.CurrentSolutionName = "solution1"; // Act testSubject.HandleBindingChange(true); @@ -196,7 +260,8 @@ public void ActiveSolutionBoundTracker_Changes() eventCounter.SolutionBindingChangedCount.Should().Be(1, "Unbind should trigger reanalysis"); eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Unbind should not trigger change"); eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Unbind should not trigger change"); - configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny()), Times.Exactly(1)); + configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(null), Times.Exactly(1)); + VerifyAnalyzerUpdatedAndClearMock(analyzerManager, "solution1", false); VerifyAndResetBoundSolutionUiContextMock(isActive: false); VerifyServiceDisconnect(Times.Never()); @@ -214,6 +279,7 @@ public void ActiveSolutionBoundTracker_Changes() eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event"); eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event"); configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny()), Times.Exactly(2)); + VerifyAnalyzerUpdatedAndClearMock(analyzerManager, "solution1", true); VerifyAndResetBoundSolutionUiContextMock(isActive: true); // Notifications from the Team Explorer should not trigger connect/disconnect @@ -232,6 +298,7 @@ public void ActiveSolutionBoundTracker_Changes() eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event"); eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event"); configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny()), Times.Exactly(3)); + VerifyAnalyzerUpdatedAndClearMock(analyzerManager, null, false); VerifyAndResetBoundSolutionUiContextMock(isActive: false); // Closing an unbound solution should not call disconnect/connect @@ -241,7 +308,7 @@ public void ActiveSolutionBoundTracker_Changes() // Case 4: Load a bound solution ConfigureSolutionBinding(boundSonarQubeProject); // Act - activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true, "solution"); + activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true, "solution2"); // Assert testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.Connected, "Bound respond to solution change event and report bound"); @@ -250,6 +317,7 @@ public void ActiveSolutionBoundTracker_Changes() eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event"); eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event"); configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny()), Times.Exactly(4)); + VerifyAnalyzerUpdatedAndClearMock(analyzerManager, "solution2", true); VerifyAndResetBoundSolutionUiContextMock(isActive: true); // Loading a bound solution should call connect @@ -267,6 +335,7 @@ public void ActiveSolutionBoundTracker_Changes() eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event"); eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event"); configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny()), Times.Exactly(5)); + VerifyAnalyzerUpdatedAndClearMock(analyzerManager, null, false); VerifyAndResetBoundSolutionUiContextMock(isActive: false); // SonarQubeService.Disconnect should be called since the WPF DisconnectCommand is not available @@ -283,11 +352,27 @@ public void ActiveSolutionBoundTracker_Changes() eventCounter.PreSolutionBindingChangedCount.Should().Be(5, "Once disposed should stop raising the event"); eventCounter.SolutionBindingChangedCount.Should().Be(5, "Once disposed should stop raising the event"); configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny()), Times.Exactly(5)); + analyzerManager.Invocations.Should().BeEmpty(); // SonarQubeService.Disconnect should be called since the WPF DisconnectCommand is not available VerifyServiceDisconnect(Times.Once()); VerifyServiceConnect(Times.Once()); } + private void VerifyAnalyzerUpdatedAndClearMock(Mock analyzerManager, string solutionName, bool bound) + { + if (bound) + { + analyzerManager.Verify(x => x.OnSolutionChanged(solutionName, + It.Is(y => y.Mode == SonarLintMode.Connected && y.Project == boundSonarQubeProject)), + Times.Exactly(1)); + } + else + { + analyzerManager.Verify(x => x.OnSolutionChanged(solutionName, BindingConfiguration.Standalone), Times.Exactly(1)); + } + analyzerManager.Invocations.Clear(); + } + [TestMethod] public void UpdateConnection_WasDisconnected_NewSolutionIsUnbound_NoConnectOrDisconnectCalls() { @@ -488,13 +573,15 @@ private ActiveSolutionBoundTracker CreateTestSubject( ILogger logger = null, IBoundSolutionGitMonitor gitEvents = null, IConfigScopeUpdater configScopeUpdater = null, - ISonarQubeService sonarQubeService = null) + ISonarQubeService sonarQubeService = null, + ISolutionRoslynAnalyzerManager solutionRoslynAnalyzerManager = null) { configScopeUpdater ??= Mock.Of(); logger ??= new TestLogger(logToConsole: true); gitEvents ??= Mock.Of(); sonarQubeService ??= Mock.Of(); - return new ActiveSolutionBoundTracker(serviceProvider, solutionTracker, configScopeUpdater, Substitute.For(), logger, gitEvents, configurationProvider, sonarQubeService); + solutionRoslynAnalyzerManager ??= Mock.Of(); + return new ActiveSolutionBoundTracker(serviceProvider, solutionTracker, configScopeUpdater, solutionRoslynAnalyzerManager, logger, gitEvents, configurationProvider, sonarQubeService); } private void ConfigureService(bool isConnected) diff --git a/src/Integration/MefServices/ActiveSolutionBoundTracker.cs b/src/Integration/MefServices/ActiveSolutionBoundTracker.cs index a66abb4094..2fd479ed1d 100644 --- a/src/Integration/MefServices/ActiveSolutionBoundTracker.cs +++ b/src/Integration/MefServices/ActiveSolutionBoundTracker.cs @@ -88,6 +88,7 @@ public ActiveSolutionBoundTracker([Import(typeof(SVsServiceProvider))] IServiceP solutionTracker.ActiveSolutionChanged += OnActiveSolutionChanged; CurrentConfiguration = GetBindingConfiguration(); + solutionRoslynAnalyzerManager.OnSolutionChanged(activeSolutionTracker.CurrentSolutionName, CurrentConfiguration); SetBoundSolutionUIContext();