From ad7f14557aa2af9117a5b043e62db00282fbc8bd Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sat, 18 Nov 2023 16:25:58 +0100 Subject: [PATCH] Feature: Normalize package references, using attributes only. --- src/Services/InfoBarService.cs | 3 +- src/Services/NuGetService.cs | 2 +- src/Services/ProjectService.cs | 70 +++++++++++++++++++++- src/View/Monitor/NuGetMonitorControl.xaml | 3 + src/View/Monitor/NuGetMonitorToolWindow.cs | 4 +- src/View/Monitor/NugetMonitorViewModel.cs | 29 +++++++++ 6 files changed, 105 insertions(+), 6 deletions(-) diff --git a/src/Services/InfoBarService.cs b/src/Services/InfoBarService.cs index 177c2ed..febd090 100644 --- a/src/Services/InfoBarService.cs +++ b/src/Services/InfoBarService.cs @@ -1,5 +1,4 @@ -using System.IO; -using System.Text; +using System.Text; using System.Windows; using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio.Imaging; diff --git a/src/Services/NuGetService.cs b/src/Services/NuGetService.cs index 4305e65..cbb2a23 100644 --- a/src/Services/NuGetService.cs +++ b/src/Services/NuGetService.cs @@ -77,7 +77,7 @@ public static async Task> GetTransitivePacka { var results = new List(); - var projectItemsByTargetFramework = packageReferences.GroupBy(item => item.ProjectItemInTargetFramework.TargetFramework); + var projectItemsByTargetFramework = packageReferences.GroupBy(item => item.ProjectItemInTargetFramework.Project.TargetFramework); foreach (var projectItemsInTargetFramework in projectItemsByTargetFramework) { diff --git a/src/Services/ProjectService.cs b/src/Services/ProjectService.cs index c4e6b31..41e4bfc 100644 --- a/src/Services/ProjectService.cs +++ b/src/Services/ProjectService.cs @@ -22,8 +22,6 @@ public ProjectItemInTargetFramework(ProjectItem projectItem, ProjectInTargetFram public ProjectItem ProjectItem { get; init; } - public NuGetFramework TargetFramework => Project.TargetFramework; - public ProjectInTargetFramework Project { get; } } @@ -63,6 +61,8 @@ private static ReadOnlyDictionary GetCentralVersionMap(Proj internal static class ProjectService { + private static readonly string[] _allAssets = new[] { "runtime", "build", "native", "contentfiles", "analyzers", "buildtransitive" }.OrderBy(i => i).ToArray(); + private static ProjectCollection _projectCollection = new(); static ProjectService() @@ -97,6 +97,72 @@ public static async Task> GetPackageR }); } + public static int NormalizePackageReferences(IEnumerable projectItems) + { + using var projectCollection = new ProjectCollection(); + var numberOfUpdatedItems = 0; + + var projectFiles = projectItems + .Select(i => i.GetContainingProject().FullPath) + .Distinct(StringComparer.OrdinalIgnoreCase); + + foreach (var projectFile in projectFiles) + { + var isDirty = false; + + var project = ProjectRootElement.Open(projectFile, projectCollection, true); + + var itemElements = project.Items + .Where(item => item.ItemType == "PackageReference"); + + foreach (var itemElement in itemElements) + { + var metadata = itemElement.Metadata; + + var metadataElements = metadata.Where(meta => !meta.ExpressedAsAttribute).ToArray(); + if (metadataElements.Length == 0) + continue; + + foreach (var metadataElement in metadataElements) + { + metadataElement.ExpressedAsAttribute = true; + } + + NormalizeIncludeAssets(metadata); + + numberOfUpdatedItems += 1; + isDirty = true; + } + + if (isDirty) + { + project.Save(); + } + } + + return numberOfUpdatedItems; + } + + private static void NormalizeIncludeAssets(IEnumerable metadata) + { + var includeAssetsElement = metadata.FirstOrDefault(meta => meta.Name == "IncludeAssets"); + if (includeAssetsElement is null) + return; + + var includeAssets = includeAssetsElement.Value; + if (string.IsNullOrEmpty(includeAssets)) + return; + + var parts = includeAssets.Split(';') + .Select(s => s.Trim()) + .OrderBy(s => s, StringComparer.OrdinalIgnoreCase); + + if (!_allAssets.SequenceEqual(parts, StringComparer.OrdinalIgnoreCase)) + return; + + includeAssetsElement.Value = string.Empty; + } + public static ProjectInTargetFramework[] GetProjectsInTargetFramework(this Project project) { var frameworkNames = (project.GetProperty("TargetFrameworks") ?? project.GetProperty("TargetFramework")) diff --git a/src/View/Monitor/NuGetMonitorControl.xaml b/src/View/Monitor/NuGetMonitorControl.xaml index ba6da9a..e1f0009 100644 --- a/src/View/Monitor/NuGetMonitorControl.xaml +++ b/src/View/Monitor/NuGetMonitorControl.xaml @@ -49,6 +49,9 @@ + diff --git a/src/View/Monitor/NuGetMonitorToolWindow.cs b/src/View/Monitor/NuGetMonitorToolWindow.cs index b6794d3..f9f4bee 100644 --- a/src/View/Monitor/NuGetMonitorToolWindow.cs +++ b/src/View/Monitor/NuGetMonitorToolWindow.cs @@ -4,9 +4,11 @@ namespace NuGetMonitor.View.Monitor; -[Guid("6ce47eec-3296-48f5-9dec-8883a276a7c8")] +[Guid(Guid)] public sealed class NuGetMonitorToolWindow : ToolWindowPane { + public const string Guid = "6ce47eec-3296-48f5-9dec-8883a276a7c8"; + /// /// Initializes a new instance of the class. /// diff --git a/src/View/Monitor/NugetMonitorViewModel.cs b/src/View/Monitor/NugetMonitorViewModel.cs index 9c9b816..3171410 100644 --- a/src/View/Monitor/NugetMonitorViewModel.cs +++ b/src/View/Monitor/NugetMonitorViewModel.cs @@ -10,6 +10,7 @@ using Microsoft.Build.Evaluation; using Microsoft.VisualStudio.Shell; using NuGetMonitor.Services; +using NuGetMonitor.View.Monitor; using TomsToolbox.Essentials; using TomsToolbox.Wpf; @@ -43,6 +44,8 @@ public NuGetMonitorViewModel() public ICommand CopyIssueDetailsCommand => new DelegateCommand(CanCopyIssueDetails, CopyIssueDetails); + public ICommand NormalizePackageReferencesCommand => new DelegateCommand(NormalizePackageReferences); + private void SolutionEvents_OnAfterOpenSolution(Solution? obj) { Load(); @@ -166,6 +169,32 @@ private static void Update(ICollection packageViewModels) ProjectService.ClearCache(); } + private void NormalizePackageReferences() + { + NormalizePackageReferencesAsync().FireAndForget(); + } + + private async Task NormalizePackageReferencesAsync() + { + var projectItems = Packages + .SelectMany(p => p.Items.Select(item => item.ProjectItemInTargetFramework.ProjectItem)); + + var numberOfUpdatedItems = ProjectService.NormalizePackageReferences(projectItems); + + await ShowInfoBar($"{numberOfUpdatedItems} package references normalized"); + } + + private static async Task ShowInfoBar(string text) + { + var model = new InfoBarModel(text); + var infoBar = await VS.InfoBar.CreateAsync(NuGetMonitorToolWindow.Guid, model).ConfigureAwait(true) ?? throw new InvalidOperationException("Failed to create the info bar"); + await infoBar.TryShowInfoBarUIAsync().ConfigureAwait(true); + + await Task.Delay(TimeSpan.FromSeconds(5)).ConfigureAwait(true); + + infoBar.Close(); + } + private bool CanCopyIssueDetails() { return Packages?.Any(p => p.PackageInfo?.HasIssues ?? false) == true;