Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communicate with OOP process to determine if an AnalyzerReference has analyzers or source generators. #74810

Merged
merged 19 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@
// The .NET Foundation licenses this file to you under the MIT license.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View with whitespace off.

// See the LICENSE file in the project root for more information.

#nullable disable

using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Imaging.Interop;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;

internal partial class AnalyzerItem(
internal sealed partial class AnalyzerItem(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just cleanup. No other changes.

AnalyzersFolderItem analyzersFolder,
AnalyzerReference analyzerReference,
IContextMenuController contextMenuController) : BaseItem(GetNameText(analyzerReference))
IContextMenuController contextMenuController)
: BaseItem(GetNameText(analyzerReference))
{
public AnalyzersFolderItem AnalyzersFolder { get; } = analyzersFolder;
public AnalyzerReference AnalyzerReference { get; } = analyzerReference;
Expand All @@ -37,14 +36,7 @@ public void Remove()
=> this.AnalyzersFolder.RemoveAnalyzer(this.AnalyzerReference.FullPath);

private static string GetNameText(AnalyzerReference analyzerReference)
{
if (analyzerReference is UnresolvedAnalyzerReference)
{
return analyzerReference.FullPath;
}
else
{
return analyzerReference.Display;
}
}
=> analyzerReference is UnresolvedAnalyzerReference unresolvedAnalyzerReference
? unresolvedAnalyzerReference.FullPath
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this prevents an NRT warning by accessing FullPath through teh derived type.

: analyzerReference.Display;
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,28 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
{
[Export(typeof(IAttachedCollectionSourceProvider))]
[Name(nameof(AnalyzerItemSourceProvider))]
[Order]
[AppliesToProject("(CSharp | VB) & !CPS")] // in the CPS case, the Analyzers items are created by the project system
internal sealed class AnalyzerItemSourceProvider : AttachedCollectionSourceProvider<AnalyzersFolderItem>
{
[Import(typeof(AnalyzersCommandHandler))]
private readonly IAnalyzersCommandHandler _commandHandler = null;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public AnalyzerItemSourceProvider()
{
}
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;

protected override IAttachedCollectionSource CreateCollectionSource(AnalyzersFolderItem analyzersFolder, string relationshipName)
{
if (relationshipName == KnownRelationships.Contains)
{
return new AnalyzerItemSource(analyzersFolder, _commandHandler);
}

return null;
}
}
[Export(typeof(IAttachedCollectionSourceProvider))]
[Name(nameof(AnalyzerItemSourceProvider)), Order]
[AppliesToProject("(CSharp | VB) & !CPS")] // in the CPS case, the Analyzers items are created by the project system
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class AnalyzerItemSourceProvider(
[Import(typeof(AnalyzersCommandHandler))] IAnalyzersCommandHandler commandHandler,
IAsynchronousOperationListenerProvider listenerProvider)
: AttachedCollectionSourceProvider<AnalyzersFolderItem>
{
protected override IAttachedCollectionSource? CreateCollectionSource(AnalyzersFolderItem analyzersFolder, string relationshipName)
=> relationshipName == KnownRelationships.Contains
? new AnalyzerItemSource(analyzersFolder, commandHandler, listenerProvider)
: null;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just cleanup. No other changes.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Imaging.Interop;
Expand All @@ -13,11 +14,13 @@
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;

internal sealed partial class AnalyzersFolderItem(
IThreadingContext threadingContext,
Workspace workspace,
ProjectId projectId,
IVsHierarchyItem parentItem,
IContextMenuController contextMenuController) : BaseItem(SolutionExplorerShim.Analyzers)
{
public readonly IThreadingContext ThreadingContext = threadingContext;
public Workspace Workspace { get; } = workspace;
public ProjectId ProjectId { get; } = projectId;
public IVsHierarchyItem ParentItem { get; } = parentItem;
Expand Down Expand Up @@ -64,7 +67,7 @@ public void AddAnalyzer(string path)
/// <summary>
/// Remove an analyzer with the given path from this folder.
/// </summary>
public void RemoveAnalyzer(string path)
public void RemoveAnalyzer(string? path)
{
var vsproject = GetVSProject();
if (vsproject == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,30 @@

using System.Collections;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.Shell;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;

internal sealed class AnalyzersFolderItemSource : IAttachedCollectionSource
internal sealed class AnalyzersFolderItemSource(
IThreadingContext threadingContext,
Workspace workspace,
ProjectId projectId,
IVsHierarchyItem projectHierarchyItem,
IAnalyzersCommandHandler commandHandler)
: IAttachedCollectionSource
{
private readonly IVsHierarchyItem _projectHierarchyItem;
private readonly Workspace _workspace;
private readonly ProjectId _projectId;
private readonly ObservableCollection<AnalyzersFolderItem> _folderItems;
private readonly IAnalyzersCommandHandler _commandHandler;

public AnalyzersFolderItemSource(Workspace workspace, ProjectId projectId, IVsHierarchyItem projectHierarchyItem, IAnalyzersCommandHandler commandHandler)
{
_workspace = workspace;
_projectId = projectId;
_projectHierarchyItem = projectHierarchyItem;
_commandHandler = commandHandler;

_folderItems = [];

Update();
}
private readonly ObservableCollection<AnalyzersFolderItem> _folderItems = [new AnalyzersFolderItem(
threadingContext,
workspace,
projectId,
projectHierarchyItem,
commandHandler.AnalyzerFolderContextMenuController)];

public bool HasItems => true;

public IEnumerable Items => _folderItems;

public object SourceItem => _projectHierarchyItem;

internal void Update()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was only called in the constructor. so it could be inlined. once inlined, it meant that almost all state held int his type went away.

{
// Don't create the item a 2nd time.
if (_folderItems.Any())
return;

_folderItems.Add(new AnalyzersFolderItem(
_workspace,
_projectId,
_projectHierarchyItem,
_commandHandler.AnalyzerFolderContextMenuController));
}
public object SourceItem => projectHierarchyItem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
Expand All @@ -21,13 +22,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
[method: ImportingConstructor]
internal sealed class AnalyzersFolderItemSourceProvider(
IThreadingContext threadingContext,
VisualStudioWorkspace workspace,
[Import(typeof(AnalyzersCommandHandler))] IAnalyzersCommandHandler commandHandler)
: AttachedCollectionSourceProvider<IVsHierarchyItem>
{
private readonly IThreadingContext _threadingContext = threadingContext;
private readonly Workspace _workspace = workspace;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passing along IThreadingContext so we update the UI on the UI thread.

private readonly IAnalyzersCommandHandler _commandHandler = commandHandler;

private IHierarchyItemToProjectIdMap? _projectMap;
private readonly Workspace _workspace = workspace;

protected override IAttachedCollectionSource? CreateCollectionSource(IVsHierarchyItem item, string relationshipName)
{
Expand All @@ -46,7 +50,7 @@ internal sealed class AnalyzersFolderItemSourceProvider(
if (hierarchyMapper != null &&
hierarchyMapper.TryGetProjectId(item.Parent, targetFrameworkMoniker: null, projectId: out var projectId))
{
return new AnalyzersFolderItemSource(_workspace, projectId, item, _commandHandler);
return new AnalyzersFolderItemSource(_threadingContext, _workspace, projectId, item, _commandHandler);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal abstract partial class BaseDiagnosticAndGeneratorItemSource : IAttached
private static readonly DiagnosticDescriptorComparer s_comparer = new();

private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService;
private readonly BulkObservableCollection<BaseItem> _items = new();
private readonly BulkObservableCollection<BaseItem> _items = [];

private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly AsyncBatchingWorkQueue _workQueue;
Expand Down Expand Up @@ -103,7 +103,16 @@ private async ValueTask ProcessQueueAsync(CancellationToken cancellationToken)
this.Workspace.WorkspaceChanged -= OnWorkspaceChanged;

_cancellationTokenSource.Cancel();
_items.Clear();

// Note: mutating _items will be picked up automatically by clients who are bound to the collection. We do
// not need to notify them through some other mechanism.
if (_items.Count > 0)
{
// Go back to UI thread to update the observable collection. Otherwise, it enqueue its own UI work that we cannot track.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
_items.Clear();
}

return;
}

Expand All @@ -115,6 +124,7 @@ private async ValueTask ProcessQueueAsync(CancellationToken cancellationToken)
if (_items.SequenceEqual([.. newDiagnosticItems, .. newSourceGeneratorItems]))
return;

// Go back to UI thread to update the observable collection. Otherwise, it enqueue its own UI work that we cannot track.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

_items.BeginBulkOperation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.Internal.VisualStudio.PlatformUI;
Expand All @@ -19,16 +18,21 @@

namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;

internal sealed class SourceGeneratedFileItemSource(SourceGeneratorItem parentGeneratorItem, Workspace workspace, IAsynchronousOperationListener asyncListener, IThreadingContext threadingContext) : Shell.IAttachedCollectionSource, ISupportExpansionEvents
internal sealed class SourceGeneratedFileItemSource(
SourceGeneratorItem parentGeneratorItem,
IThreadingContext threadingContext,
Workspace workspace,
IAsynchronousOperationListener asyncListener)
: Shell.IAttachedCollectionSource, ISupportExpansionEvents
{
private readonly SourceGeneratorItem _parentGeneratorItem = parentGeneratorItem;
private readonly IThreadingContext _threadingContext = threadingContext;
private readonly Workspace _workspace = workspace;
private readonly IAsynchronousOperationListener _asyncListener = asyncListener;
private readonly IThreadingContext _threadingContext = threadingContext;

/// <summary>
/// The returned collection of items. Can only be mutated on the UI thread, as other parts of WPF are subscribed to the change
/// events and expect that.
/// The returned collection of items. Can only be mutated on the UI thread, as other parts of WPF are subscribed to
/// the change events and expect that.
/// </summary>
private readonly BulkObservableCollectionWithInit<BaseItem> _items = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
Expand All @@ -29,6 +28,6 @@ internal sealed class SourceGeneratedFileItemSourceProvider(

protected override IAttachedCollectionSource? CreateCollectionSource(SourceGeneratorItem item, string relationshipName)
=> relationshipName == KnownRelationships.Contains
? new SourceGeneratedFileItemSource(item, workspace, _asyncListener, threadingContext)
? new SourceGeneratedFileItemSource(item, threadingContext, workspace, _asyncListener)
: null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.

Imports Microsoft.CodeAnalysis.Editor.[Shared].Utilities
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.VisualStudio.LanguageServices.Implementation
Imports Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer

Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer
<[UseExportProvider]>
<UseExportProvider>
<Trait(Traits.Feature, Traits.Features.Diagnostics)>
Public Class AnalyzerItemTests
<Fact>
Expand All @@ -23,7 +24,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer
Using workspace = EditorTestWorkspace.Create(workspaceXml)
Dim project = workspace.Projects.Single()

Dim analyzerFolder = New AnalyzersFolderItem(workspace, project.Id, Nothing, Nothing)
Dim analyzerFolder = New AnalyzersFolderItem(workspace.GetService(Of IThreadingContext), workspace, project.Id, Nothing, Nothing)
Dim analyzer = New AnalyzerItem(analyzerFolder, project.AnalyzerReferences.Single(), Nothing)

Assert.Equal(expected:="Goo", actual:=analyzer.Text)
Expand All @@ -42,7 +43,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer
Using workspace = EditorTestWorkspace.Create(workspaceXml)
Dim project = workspace.Projects.Single()

Dim analyzerFolder = New AnalyzersFolderItem(workspace, project.Id, Nothing, Nothing)
Dim analyzerFolder = New AnalyzersFolderItem(workspace.GetService(Of IThreadingContext), workspace, project.Id, Nothing, Nothing)
Dim analyzer = New AnalyzerItem(analyzerFolder, project.AnalyzerReferences.Single(), Nothing)
Dim browseObject = DirectCast(analyzer.GetBrowseObject(), AnalyzerItem.BrowseObject)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.

Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities
Imports Microsoft.CodeAnalysis.Shared.TestHooks
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
Imports Roslyn.Test.Utilities

Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer
<[UseExportProvider]>
Public Class AnalyzerItemsSourceTests
<Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)>
Public Sub Ordering()
<UseExportProvider>
Public NotInheritable Class AnalyzerItemsSourceTests
<WpfFact, Trait(Traits.Feature, Traits.Features.Diagnostics)>
Public Async Function Ordering() As Task
Dim workspaceXml =
<Workspace>
<Project Language="C#" CommonReferences="true">
Expand All @@ -23,8 +25,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer
Using workspace = EditorTestWorkspace.Create(workspaceXml)
Dim project = workspace.Projects.Single()

Dim analyzerFolder = New AnalyzersFolderItem(workspace, project.Id, Nothing, Nothing)
Dim analyzerItemsSource = New AnalyzerItemSource(analyzerFolder, New FakeAnalyzersCommandHandler)
Dim analyzerFolder = New AnalyzersFolderItem(workspace.GetService(Of IThreadingContext), workspace, project.Id, Nothing, Nothing)
Dim listenerProvider = workspace.GetService(Of IAsynchronousOperationListenerProvider)
Dim analyzerItemsSource = New AnalyzerItemSource(
analyzerFolder, New FakeAnalyzersCommandHandler(), listenerProvider)

Dim waiter = listenerProvider.GetWaiter(FeatureAttribute.SourceGenerators)
Await waiter.ExpeditedWaitAsync()

Dim analyzers = analyzerItemsSource.Items.Cast(Of AnalyzerItem)().ToArray()

Expand All @@ -33,7 +40,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer
Assert.Equal(expected:="Beta", actual:=analyzers(1).Text)
Assert.Equal(expected:="Gamma", actual:=analyzers(2).Text)
End Using
End Sub
End Function
End Class
End Namespace

Loading
Loading