Skip to content

Commit

Permalink
Add IErrorTag implementation that lazily-creates tooltips (#3690)
Browse files Browse the repository at this point in the history
* Add IErrorTag implementation that lazily-creates tooltips

Relates to #2798
  • Loading branch information
duncanp-sonar committed Feb 20, 2023
1 parent 9360d20 commit 01463d3
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SonarLint.VisualStudio.Core.Analysis;
using SonarLint.VisualStudio.Integration;
using SonarLint.VisualStudio.Integration.UnitTests;
using SonarLint.VisualStudio.IssueVisualization.Editor.ErrorTagging;
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
Expand All @@ -40,7 +41,8 @@ public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<ErrorTagTooltipProvider, IErrorTagTooltipProvider>(
MefTestHelpers.CreateExport<INavigateToRuleDescriptionCommand>(),
MefTestHelpers.CreateExport<IVsThemeColorProvider>());
MefTestHelpers.CreateExport<IVsThemeColorProvider>(),
MefTestHelpers.CreateExport<ILogger>());
}

[TestMethod]
Expand All @@ -52,7 +54,7 @@ public void Create_CreatesTooltipWithHyperlink()

var navigateCommand = Mock.Of<INavigateToRuleDescriptionCommand>();

var testSubject = new ErrorTagTooltipProvider(Mock.Of<IVsThemeColorProvider>(), navigateCommand);
var testSubject = new ErrorTagTooltipProvider(Mock.Of<IVsThemeColorProvider>(), navigateCommand, new TestLogger(logToConsole: true));
var result = testSubject.Create(issue.Object);

result.Should().NotBeNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SonarLint.VisualStudio.Core.Analysis;
using SonarLint.VisualStudio.IssueVisualization.Editor.ErrorTagging;

namespace SonarLint.VisualStudio.IssueVisualization.UnitTests.Editor.ErrorTagging
{
[TestClass]
public class SonarErrorTagUnitTests
{
[TestMethod]
public void Ctor_SimplePropertySet_TooltipNotCreated()
{
var tooltipProvider = new Mock<IErrorTagTooltipProvider>();

var testSubject = CreateTestSubject("my error type", tooltipProvider.Object);

testSubject.ErrorType.Should().Be("my error type");
tooltipProvider.Invocations.Should().HaveCount(0);
}

[TestMethod]
public void ToolTipContent_CreatedOnDemandAndCreatedOnlyOnce()
{
var analysisIssue = Mock.Of<IAnalysisIssueBase>();

var expectedTooltipObject = new object();
var tooltipProvider = new Mock<IErrorTagTooltipProvider>();
tooltipProvider.Setup(x => x.Create(analysisIssue)).Returns(expectedTooltipObject);

var testSubject = CreateTestSubject(tooltipProvider: tooltipProvider.Object,
analysisIssue: analysisIssue);

// Sanity check before accessing the property
tooltipProvider.Invocations.Should().HaveCount(0);

// 1. First access - should create the tooltip
var actual1 = testSubject.ToolTipContent;

actual1.Should().BeSameAs(expectedTooltipObject);
tooltipProvider.Invocations.Should().HaveCount(1);

// 2. Subsequent access - should return the original object
var actual2 = testSubject.ToolTipContent;

actual2.Should().BeSameAs(expectedTooltipObject);
tooltipProvider.Invocations.Should().HaveCount(1);
}

private static SonarErrorTag CreateTestSubject(string errorType = null,
IErrorTagTooltipProvider tooltipProvider = null,
IAnalysisIssueBase analysisIssue = null)
{
errorType ??= "any";
analysisIssue ??= Mock.Of<IAnalysisIssueBase>();
tooltipProvider ??= Mock.Of<IErrorTagTooltipProvider>();

return new SonarErrorTag(errorType, analysisIssue, tooltipProvider);
}
}
}
10 changes: 9 additions & 1 deletion src/IssueViz/Editor/ErrorTagging/ErrorTagTooltipProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using Microsoft.VisualStudio.Shell;
using SonarLint.VisualStudio.Core.Analysis;
using SonarLint.VisualStudio.Infrastructure.VS;
using SonarLint.VisualStudio.Integration;
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;

namespace SonarLint.VisualStudio.IssueVisualization.Editor.ErrorTagging
Expand All @@ -44,14 +45,19 @@ internal class ErrorTagTooltipProvider : IErrorTagTooltipProvider
{
private readonly IVsThemeColorProvider vsThemeColorProvider;
private readonly INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand;
private readonly ILogger logger;

[ImportingConstructor]
public ErrorTagTooltipProvider(IVsThemeColorProvider vsThemeColorProvider, INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand)

public ErrorTagTooltipProvider(IVsThemeColorProvider vsThemeColorProvider, INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand, ILogger logger)
{
this.vsThemeColorProvider = vsThemeColorProvider;
this.navigateToRuleDescriptionCommand = navigateToRuleDescriptionCommand;
this.logger = logger;
}

private static long instanceCount = 0;

public object Create(IAnalysisIssueBase analysisIssueBase)
{
var hyperLink = new Hyperlink
Expand All @@ -74,6 +80,8 @@ public object Create(IAnalysisIssueBase analysisIssueBase)
Foreground = GetVsThemedColor(EnvironmentColors.SystemCaptionTextBrushKey)
};

instanceCount++;
logger.LogVerbose($"[ErrorTagTooltipProvider] tooltip instance count: {instanceCount}");

return content;
}
Expand Down
3 changes: 1 addition & 2 deletions src/IssueViz/Editor/ErrorTagging/ErrorTagger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ public ErrorTagger(ITagAggregator<IIssueLocationTag> tagAggregator, ITextBuffer
protected override TagSpan<IErrorTag> CreateTagSpan(IIssueLocationTag trackedTag, NormalizedSnapshotSpanCollection spans)
{
var issueViz = (IAnalysisIssueVisualization)trackedTag.Location;
var tooltip = errorTagTooltipProvider.Create(issueViz.Issue);
return new TagSpan<IErrorTag>(trackedTag.Location.Span.Value, new ErrorTag(PredefinedErrorTypeNames.Warning, tooltip));
return new TagSpan<IErrorTag>(trackedTag.Location.Span.Value, new SonarErrorTag(PredefinedErrorTypeNames.Warning, issueViz.Issue, errorTagTooltipProvider));
}

protected override IEnumerable<IMappingTagSpan<IIssueLocationTag>> Filter(IEnumerable<IMappingTagSpan<IIssueLocationTag>> trackedTagSpans) =>
Expand Down
47 changes: 47 additions & 0 deletions src/IssueViz/Editor/ErrorTagging/SonarErrorTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using Microsoft.VisualStudio.Text.Tagging;
using SonarLint.VisualStudio.Core.Analysis;

namespace SonarLint.VisualStudio.IssueVisualization.Editor.ErrorTagging
{
/// <summary>
/// Implementation of <see cref="IErrorTag"/> that lazily creates tooltips.
/// </summary>
/// <remarks>
/// Mitigation for a performance issue. See https://github.com/SonarSource/sonarlint-visualstudio/issues/2798
/// </remarks>
internal class SonarErrorTag : IErrorTag
{
private readonly Lazy<object> tooltipFactory;

public SonarErrorTag(string errorType, IAnalysisIssueBase analysisIssue, IErrorTagTooltipProvider errorTagTooltipProvider)
{
ErrorType = errorType;
tooltipFactory = new Lazy<object>(() => errorTagTooltipProvider.Create(analysisIssue));
}

public string ErrorType { get; }

public object ToolTipContent => tooltipFactory.Value;
}
}

0 comments on commit 01463d3

Please sign in to comment.