提交 8f4d3ef3 编写于 作者: T Tom Meschter

Show diagnostic nodes in multi-targeting projects

Normally a given project will only show a single set of analyzer nodes, and finding those nodes and mapping to their underlying `AnalyzerReference`s is relatively easy.

When a project has multiple target frameworks, however, things get harder. Instead of a one-to-one mapping from projects in hierarchy to projects in the workspace, we have a one-to-many mapping: there will be one Roslyn project **per target framework** per hierarchy project. There are a few pieces to making this work properly:

1. When we have found a hierarchy item for an analyzer we already walk up the hierarchy to find the node for the project. We modify this to also look for the node representing the particular target frameworks (that is, with a "TargetNode" capability), if any, and extract the TargetFrameworkMoniker.

2. The `IHierarchyItemToProjectIdMap` has been updated to take that TargetFrameworkMoniker as an optional argument.

3. When checking the hierarchy associated with each Roslyn project we now extract and compare its TargetFrameworkMoniker, if desired.
上级 73dc32a5
......@@ -76,5 +76,10 @@ public static bool TryGetTypeGuid(this IVsHierarchy hierarchy, out Guid typeGuid
{
return hierarchy.TryGetGuidProperty(__VSHPROPID.VSHPROPID_TypeGuid, out typeGuid);
}
public static bool TryGetTargetFrameworkMoniker(this IVsHierarchy hierarchy, uint itemId, out string targetFrameworkMoniker)
{
return hierarchy.TryGetItemProperty(itemId, (int)__VSHPROPID4.VSHPROPID_TargetFrameworkMoniker, out targetFrameworkMoniker);
}
}
}
......@@ -68,7 +68,7 @@ private IAttachedCollectionSource CreateCollectionSourceCore(IVsHierarchyItem pa
{
var hierarchyMapper = TryGetProjectMap();
if (hierarchyMapper != null &&
hierarchyMapper.TryGetProjectId(parentItem, out var projectId))
hierarchyMapper.TryGetProjectId(parentItem, targetFrameworkMoniker: null, projectId: out var projectId))
{
var workspace = TryGetWorkspace();
return new AnalyzersFolderItemSource(workspace, projectId, item, _commandHandler);
......
......@@ -43,12 +43,12 @@ protected override IAttachedCollectionSource CreateCollectionSource(IVsHierarchy
item.HierarchyIdentity.NestedHierarchy != null &&
relationshipName == KnownRelationships.Contains)
{
if (NestedHierarchyHasProjectTreeCapability(item, "AnalyzerSubTreeNode"))
if (NestedHierarchyHasProjectTreeCapability(item, "AnalyzerDependency"))
{
var projectRootItem = FindProjectRootItem(item);
var projectRootItem = FindProjectRootItem(item, out string targetFrameworkMoniker);
if (projectRootItem != null)
{
return CreateCollectionSourceCore(projectRootItem, item);
return CreateCollectionSourceCore(projectRootItem, item, targetFrameworkMoniker);
}
}
}
......@@ -56,10 +56,22 @@ protected override IAttachedCollectionSource CreateCollectionSource(IVsHierarchy
return null;
}
private IVsHierarchyItem FindProjectRootItem(IVsHierarchyItem item)
/// <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)
{
if (targetFrameworkMoniker == null)
{
targetFrameworkMoniker = GetTargetFrameworkMoniker(parent, targetFrameworkMoniker);
}
if (NestedHierarchyHasProjectTreeCapability(parent, "ProjectRoot"))
{
return parent;
......@@ -69,15 +81,43 @@ private IVsHierarchyItem FindProjectRootItem(IVsHierarchyItem item)
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, string targetFrameworkMoniker)
{
var hierarchy = item.HierarchyIdentity.NestedHierarchy;
var itemId = item.HierarchyIdentity.NestedItemID;
var projectTreeCapabilities = GetProjectTreeCapabilities(hierarchy, itemId);
bool isTargetNode = false;
string potentialTFM = null;
foreach (var capability in projectTreeCapabilities)
{
if (capability.Equals("TargetNode"))
{
isTargetNode = true;
}
else if (capability.StartsWith("$TFM:"))
{
potentialTFM = capability.Substring("$TFM:".Length);
}
}
return isTargetNode ? potentialTFM : null;
}
// This method is separate from CreateCollectionSource and marked with
// MethodImplOptions.NoInlining because we don't want calls to CreateCollectionSource
// to cause Roslyn assemblies to load where they aren't needed.
[MethodImpl(MethodImplOptions.NoInlining)]
private IAttachedCollectionSource CreateCollectionSourceCore(IVsHierarchyItem projectRootItem, IVsHierarchyItem item)
private IAttachedCollectionSource CreateCollectionSourceCore(IVsHierarchyItem projectRootItem, IVsHierarchyItem item, string targetFrameworkMoniker)
{
var hierarchyMapper = TryGetProjectMap();
if (hierarchyMapper != null &&
hierarchyMapper.TryGetProjectId(projectRootItem, out var projectId))
hierarchyMapper.TryGetProjectId(projectRootItem, targetFrameworkMoniker, out var projectId))
{
var workspace = TryGetWorkspace();
var analyzerService = GetAnalyzerService();
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
......@@ -92,7 +91,12 @@ private AnalyzerReference TryGetAnalyzerReference(Solution solution)
return null;
}
return project.AnalyzerReferences.SingleOrDefault(r => r.FullPath.Equals(_item.CanonicalName, StringComparison.OrdinalIgnoreCase));
if (!_item.HierarchyIdentity.NestedHierarchy.TryGetItemName(_item.HierarchyIdentity.NestedItemID, out string name))
{
return null;
}
return project.AnalyzerReferences.FirstOrDefault(r => r.Display.Equals(name));
}
}
}
\ No newline at end of file
......@@ -21,7 +21,7 @@ public HierarchyItemToProjectIdMap(VisualStudioWorkspaceImpl workspace)
_workspace = workspace;
}
public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, out ProjectId projectId)
public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFrameworkMoniker, out ProjectId projectId)
{
if (_workspace.DeferredState == null)
{
......@@ -41,8 +41,19 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, out ProjectId projec
var project = _workspace.DeferredState.ProjectTracker.ImmutableProjects
.Where(p =>
{
return p.Hierarchy.TryGetCanonicalName((uint)VSConstants.VSITEMID.Root, out string projectCanonicalName)
&& projectCanonicalName.Equals(nestedCanonicalName, System.StringComparison.OrdinalIgnoreCase);
if (p.Hierarchy.TryGetCanonicalName((uint)VSConstants.VSITEMID.Root, out string projectCanonicalName)
&& projectCanonicalName.Equals(nestedCanonicalName, System.StringComparison.OrdinalIgnoreCase))
{
if (targetFrameworkMoniker == null)
{
return true;
}
return p.Hierarchy.TryGetTargetFrameworkMoniker((uint)VSConstants.VSITEMID.Root, out string projectTargetFrameworkMoniker)
&& projectTargetFrameworkMoniker.Equals(targetFrameworkMoniker);
}
return false;
})
.SingleOrDefault();
......
......@@ -6,8 +6,21 @@
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
{
/// <summary>
/// Maps from hierarchy items to project IDs.
/// </summary>
internal interface IHierarchyItemToProjectIdMap : IWorkspaceService
{
bool TryGetProjectId(IVsHierarchyItem hierarchyItem, out ProjectId projectId);
/// <summary>
/// Given an <see cref="IVsHierarchyItem"/> representing a project and an optional target framework moniker,
/// returns the <see cref="ProjectId"/> of the equivalent Roslyn <see cref="Project"/>.
/// </summary>
/// <param name="hierarchyItem">An <see cref="IVsHierarchyItem"/> for the project root.</param>
/// <param name="targetFrameworkMoniker">An optional string representing a TargetFrameworkMoniker.
/// This is only useful in multi-targeting scenarios where there may be multiple Roslyn projects
/// (one per target framework) for a single project on disk.</param>
/// <param name="projectId">The <see cref="ProjectId"/> of the found project, if any.</param>
/// <returns>True if the desired project was found; false otherwise.</returns>
bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFrameworkMoniker, out ProjectId projectId);
}
}
......@@ -15,7 +15,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr
_tracker = projectTracker
End Sub
Public Function TryGetProjectId(hierarchyItem As IVsHierarchyItem, ByRef projectId As ProjectId) As Boolean Implements IHierarchyItemToProjectIdMap.TryGetProjectId
Public Function TryGetProjectId(hierarchyItem As IVsHierarchyItem, targetFrameworkMoniker As String, ByRef projectId As ProjectId) As Boolean Implements IHierarchyItemToProjectIdMap.TryGetProjectId
Dim project = _tracker.ImmutableProjects.
Where(Function(p) p.Hierarchy Is hierarchyItem.HierarchyIdentity.NestedHierarchy).
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册