|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
{
using Workspace = Microsoft.CodeAnalysis.Workspace;
[Export(typeof(IAttachedCollectionSourceProvider))]
[Name(nameof(CpsDiagnosticItemSourceProvider))]
[Order]
[AppliesToProject("(CSharp | VB) & CPS")]
internal sealed class CpsDiagnosticItemSourceProvider : AttachedCollectionSourceProvider<IVsHierarchyItem>
{
private readonly IAnalyzersCommandHandler _commandHandler;
private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService;
private readonly Workspace _workspace;
private IHierarchyItemToProjectIdMap? _projectMap;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CpsDiagnosticItemSourceProvider(
[Import(typeof(AnalyzersCommandHandler))] IAnalyzersCommandHandler commandHandler,
IDiagnosticAnalyzerService diagnosticAnalyzerService,
VisualStudioWorkspace workspace)
{
_commandHandler = commandHandler;
_diagnosticAnalyzerService = diagnosticAnalyzerService;
_workspace = workspace;
}
protected override IAttachedCollectionSource? CreateCollectionSource(IVsHierarchyItem item, string relationshipName)
{
if (item != null &&
item.HierarchyIdentity != null &&
item.HierarchyIdentity.NestedHierarchy != null &&
relationshipName == KnownRelationships.Contains)
{
if (NestedHierarchyHasProjectTreeCapability(item, "AnalyzerDependency"))
{
var projectRootItem = FindProjectRootItem(item, out var targetFrameworkMoniker);
if (projectRootItem != null)
{
var hierarchyMapper = TryGetProjectMap();
if (hierarchyMapper != null &&
hierarchyMapper.TryGetProjectId(projectRootItem, targetFrameworkMoniker, out var projectId))
{
var hierarchy = projectRootItem.HierarchyIdentity.NestedHierarchy;
var itemId = projectRootItem.HierarchyIdentity.NestedItemID;
if (hierarchy.GetCanonicalName(itemId, out var projectCanonicalName) == VSConstants.S_OK)
{
return new CpsDiagnosticItemSource(_workspace, projectCanonicalName, projectId, item, _commandHandler, _diagnosticAnalyzerService);
}
}
}
}
}
return null;
}
/// <summary>
/// Starting at the given item, walks up the tree to find the item representing the project root.
/// If the item is located under a target-framwork specific node, the corresponding
/// TargetFrameworkMoniker will be found as well.
/// </summary>
private static IVsHierarchyItem? FindProjectRootItem(IVsHierarchyItem item, out string? targetFrameworkMoniker)
{
targetFrameworkMoniker = null;
for (var parent = item; parent != null; parent = parent.Parent)
{
targetFrameworkMoniker ??= GetTargetFrameworkMoniker(parent);
if (NestedHierarchyHasProjectTreeCapability(parent, "ProjectRoot"))
{
return parent;
}
}
return null;
}
/// <summary>
/// Given an item determines if it represents a particular target frmework.
/// If so, it returns the corresponding TargetFrameworkMoniker.
/// </summary>
private static string? GetTargetFrameworkMoniker(IVsHierarchyItem item)
{
var hierarchy = item.HierarchyIdentity.NestedHierarchy;
var itemId = item.HierarchyIdentity.NestedItemID;
var projectTreeCapabilities = GetProjectTreeCapabilities(hierarchy, itemId);
var isTargetNode = false;
string? potentialTFM = null;
foreach (var capability in projectTreeCapabilities)
{
if (capability.Equals("TargetNode"))
{
isTargetNode = true;
}
else if (capability.StartsWith("$TFM:"))
{
potentialTFM = capability["$TFM:".Length..];
}
}
return isTargetNode ? potentialTFM : null;
}
private static bool NestedHierarchyHasProjectTreeCapability(IVsHierarchyItem item, string capability)
{
var hierarchy = item.HierarchyIdentity.NestedHierarchy;
var itemId = item.HierarchyIdentity.NestedItemID;
var projectTreeCapabilities = GetProjectTreeCapabilities(hierarchy, itemId);
return projectTreeCapabilities.Any(static (c, capability) => c.Equals(capability), capability);
}
private static ImmutableArray<string> GetProjectTreeCapabilities(IVsHierarchy hierarchy, uint itemId)
{
if (hierarchy.GetProperty(itemId, (int)__VSHPROPID7.VSHPROPID_ProjectTreeCapabilities, out var capabilitiesObj) == VSConstants.S_OK)
{
var capabilitiesString = (string)capabilitiesObj;
return ImmutableArray.Create(capabilitiesString.Split(' '));
}
else
{
return ImmutableArray<string>.Empty;
}
}
private IHierarchyItemToProjectIdMap? TryGetProjectMap()
{
_projectMap ??= _workspace.Services.GetService<IHierarchyItemToProjectIdMap>();
return _projectMap;
}
}
}
|