From 24903af17ca7602194543795c3dc431bee979853 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sat, 23 Sep 2023 16:55:14 +0200 Subject: [PATCH 1/2] Fix: Ignore the implicit NetStandard library reference in projects targeting NetStandard. --- src/GlobalConstants.cs | 7 +++++++ src/GlobalUsings.cs | 4 ++++ src/Models/NugetSession.cs | 5 ++--- src/Models/PackageReferenceEntry.cs | 2 +- src/NuGetMonitorPackage.cs | 10 +++++----- src/Options/General.cs | 4 ++-- src/Services/InfoBarService.cs | 8 +++----- src/Services/LoggingService.cs | 26 +++++++++++++++++++------- src/Services/MonitorService.cs | 10 +++++----- src/Services/NuGetService.cs | 6 +++--- src/Services/ProjectService.cs | 10 +++++++--- src/View/NuGetMonitorCommand.cs | 7 +++---- src/View/NuGetMonitorToolWindow.cs | 4 ++-- src/View/NugetMonitorViewModel.cs | 2 +- 14 files changed, 64 insertions(+), 41 deletions(-) create mode 100644 src/GlobalConstants.cs create mode 100644 src/GlobalUsings.cs diff --git a/src/GlobalConstants.cs b/src/GlobalConstants.cs new file mode 100644 index 0000000..0784338 --- /dev/null +++ b/src/GlobalConstants.cs @@ -0,0 +1,7 @@ +namespace NuGetMonitor +{ + internal static class GlobalConstants + { + public static readonly string NetStandardPackageId = "NETStandard.Library"; + } +} diff --git a/src/GlobalUsings.cs b/src/GlobalUsings.cs new file mode 100644 index 0000000..a19ecd8 --- /dev/null +++ b/src/GlobalUsings.cs @@ -0,0 +1,4 @@ +global using static Microsoft.VisualStudio.Shell.ThreadHelper; +global using static NuGetMonitor.GlobalConstants; +global using static NuGetMonitor.Services.LoggingService; + diff --git a/src/Models/NugetSession.cs b/src/Models/NugetSession.cs index 1fb7e9f..ae4fbe6 100644 --- a/src/Models/NugetSession.cs +++ b/src/Models/NugetSession.cs @@ -1,7 +1,6 @@ using System.IO; using Community.VisualStudio.Toolkit; using Microsoft.Extensions.Caching.Memory; -using Microsoft.VisualStudio.Shell; using NuGet.Configuration; using NuGet.Protocol.Core.Types; using Settings = NuGet.Configuration.Settings; @@ -14,7 +13,7 @@ internal sealed class NuGetSession : IDisposable public NuGetSession() { - ThreadHelper.ThrowIfNotOnUIThread(); + ThrowIfNotOnUIThread(); var solution = VS.Solutions.GetCurrentSolution(); var solutionDirectory = Path.GetDirectoryName(solution?.FullPath); @@ -31,7 +30,7 @@ public NuGetSession() PackageDownloadContext = new PackageDownloadContext(SourceCacheContext); } - public MemoryCache Cache { get; } = new(new MemoryCacheOptions { }); + public MemoryCache Cache { get; } = new(new MemoryCacheOptions()); public SourceCacheContext SourceCacheContext { get; } = new(); diff --git a/src/Models/PackageReferenceEntry.cs b/src/Models/PackageReferenceEntry.cs index 00f4a58..5c952df 100644 --- a/src/Models/PackageReferenceEntry.cs +++ b/src/Models/PackageReferenceEntry.cs @@ -17,4 +17,4 @@ public PackageReferenceEntry(string id, VersionRange versionRange, ProjectItem p public ProjectItem ProjectItem { get; } public string Justification { get; } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/NuGetMonitorPackage.cs b/src/NuGetMonitorPackage.cs index eca3f63..ad2f29c 100644 --- a/src/NuGetMonitorPackage.cs +++ b/src/NuGetMonitorPackage.cs @@ -1,10 +1,10 @@ -using Community.VisualStudio.Toolkit; +using System.Runtime.InteropServices; +using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; +using NuGetMonitor.Options; using NuGetMonitor.Services; using NuGetMonitor.View; -using System.Runtime.InteropServices; -using Task = System.Threading.Tasks.Task; namespace NuGetMonitor; @@ -13,8 +13,8 @@ namespace NuGetMonitor; [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_string, PackageAutoLoadFlags.BackgroundLoad)] [ProvideMenuResource("Menus.ctmenu", 1)] [ProvideToolWindow(typeof(NuGetMonitorToolWindow))] -[ProvideOptionPage(typeof(Options.OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)] -[ProvideProfile(typeof(Options.OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)] +[ProvideOptionPage(typeof(OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)] +[ProvideProfile(typeof(OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)] public sealed class NuGetMonitorPackage : ToolkitPackage { public const string PackageGuidString = "38279e01-6b27-4a29-9221-c4ea8748f16e"; diff --git a/src/Options/General.cs b/src/Options/General.cs index 37f6745..3c649ad 100644 --- a/src/Options/General.cs +++ b/src/Options/General.cs @@ -1,6 +1,6 @@ -using Community.VisualStudio.Toolkit; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.InteropServices; +using Community.VisualStudio.Toolkit; namespace NuGetMonitor.Options; diff --git a/src/Services/InfoBarService.cs b/src/Services/InfoBarService.cs index 7d1398d..c2861ed 100644 --- a/src/Services/InfoBarService.cs +++ b/src/Services/InfoBarService.cs @@ -3,16 +3,14 @@ using System.Windows; using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio.Imaging; -using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; using NuGet.Versioning; using NuGetMonitor.Models; +using NuGetMonitor.Options; using NuGetMonitor.View; using TomsToolbox.Essentials; -using static NuGetMonitor.Services.LoggingService; -using NuGetMonitor.Options; - namespace NuGetMonitor.Services; internal static class InfoBarService @@ -114,7 +112,7 @@ public static void CloseInfoBars() private static void InfoBar_ActionItemClicked(object sender, InfoBarActionItemEventArgs e) { - ThreadHelper.ThrowIfNotOnUIThread(); + ThrowIfNotOnUIThread(); switch (e.ActionItem.ActionContext) { diff --git a/src/Services/LoggingService.cs b/src/Services/LoggingService.cs index cdba2c2..ba81775 100644 --- a/src/Services/LoggingService.cs +++ b/src/Services/LoggingService.cs @@ -1,6 +1,7 @@ using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; namespace NuGetMonitor.Services; @@ -8,25 +9,36 @@ internal static class LoggingService { private static Guid _outputPaneGuid = new("{5B951352-356E-45A9-8F73-80DF1C57FED4}"); + private static IVsOutputWindowPane? _outputWindowPane; + public static void Log(string message) { LogAsync(message).FireAndForget(); } public static async Task LogAsync(string message) + { + _outputWindowPane ??= await GetOutputWindowPane(); + + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + _outputWindowPane?.OutputStringThreadSafe($"[{DateTime.Now:T}] {message}\r\n"); + } + + private static async Task GetOutputWindowPane() { var outputWindow = await VS.Services.GetOutputWindowAsync(); - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + await JoinableTaskFactory.SwitchToMainThreadAsync(); var errorCode = outputWindow.GetPane(ref _outputPaneGuid, out var pane); - if (ErrorHandler.Failed(errorCode) || pane == null) - { - outputWindow.CreatePane(ref _outputPaneGuid, "NuGet Monitor", Convert.ToInt32(true), Convert.ToInt32(false)); - outputWindow.GetPane(ref _outputPaneGuid, out pane); - } + if (!ErrorHandler.Failed(errorCode) && pane != null) + return pane; + + outputWindow.CreatePane(ref _outputPaneGuid, "NuGet Monitor", Convert.ToInt32(true), Convert.ToInt32(false)); + outputWindow.GetPane(ref _outputPaneGuid, out pane); - pane?.OutputStringThreadSafe($"[{DateTime.Now:T}] {message}\r\n"); + return pane; } } \ No newline at end of file diff --git a/src/Services/MonitorService.cs b/src/Services/MonitorService.cs index 2142f33..49d7baf 100644 --- a/src/Services/MonitorService.cs +++ b/src/Services/MonitorService.cs @@ -42,22 +42,22 @@ private static async Task CheckForUpdatesInternal() if (solution is null) return; - await LoggingService.LogAsync($"Solution: {solution.Name}").ConfigureAwait(true); + await LogAsync($"Solution: {solution.Name}").ConfigureAwait(true); - await LoggingService.LogAsync("Check top level packages").ConfigureAwait(true); + await LogAsync("Check top level packages").ConfigureAwait(true); var packageReferences = await ProjectService.GetPackageReferences().ConfigureAwait(true); var topLevelPackages = await NuGetService.CheckPackageReferences(packageReferences).ConfigureAwait(true); - await LoggingService.LogAsync($"{topLevelPackages.Count} packages found").ConfigureAwait(true); + await LogAsync($"{topLevelPackages.Count} packages found").ConfigureAwait(true); if (topLevelPackages.Count == 0) return; InfoBarService.ShowTopLevelPackageIssues(topLevelPackages); - await LoggingService.LogAsync("Check transitive packages").ConfigureAwait(true); + await LogAsync("Check transitive packages").ConfigureAwait(true); var transitiveDependencies = await NuGetService.GetTransitivePackages(packageReferences, topLevelPackages).ConfigureAwait(true); @@ -65,7 +65,7 @@ private static async Task CheckForUpdatesInternal() } catch (Exception ex) when (ex is not (OperationCanceledException or ObjectDisposedException)) { - await LoggingService.LogAsync($"Check for updates failed: {ex}"); + await LogAsync($"Check for updates failed: {ex}"); } } } \ No newline at end of file diff --git a/src/Services/NuGetService.cs b/src/Services/NuGetService.cs index 31842d0..f8b6010 100644 --- a/src/Services/NuGetService.cs +++ b/src/Services/NuGetService.cs @@ -1,5 +1,4 @@ -using System.IO; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; using NuGet.Common; using NuGet.Frameworks; using NuGet.Packaging; @@ -345,7 +344,8 @@ public PackageDependenciesCacheEntry(PackageIdentity packageIdentity, SourceRepo private static async Task GetDirectDependencies(PackageIdentity packageIdentity, SourceRepository repository, NuGetSession session) { // Don't scan packages with pseudo-references, they don't get physically included, but cause vulnerability warnings. - if (string.Equals(packageIdentity.Id, "NETStandard.Library", StringComparison.OrdinalIgnoreCase)) + + if (string.Equals(packageIdentity.Id, NetStandardPackageId, StringComparison.OrdinalIgnoreCase)) return Array.Empty(); var resource = await repository.GetResourceAsync(session.CancellationToken); diff --git a/src/Services/ProjectService.cs b/src/Services/ProjectService.cs index 54d20fe..c363fb1 100644 --- a/src/Services/ProjectService.cs +++ b/src/Services/ProjectService.cs @@ -1,13 +1,11 @@ using System.IO; using Community.VisualStudio.Toolkit; -using EnvDTE; using Microsoft.Build.Evaluation; using NuGet.Frameworks; using NuGet.Versioning; using NuGetMonitor.Models; using TomsToolbox.Essentials; using Project = Microsoft.Build.Evaluation.Project; -using ProjectItem = Microsoft.Build.Evaluation.ProjectItem; namespace NuGetMonitor.Services; @@ -120,7 +118,7 @@ private static IEnumerable GetPackageReferenceItems(ProjectCollecti } catch (Exception ex) { - LoggingService.Log($"Get package reference item failed: {ex}"); + Log($"Get package reference item failed: {ex}"); return Enumerable.Empty(); } @@ -129,6 +127,11 @@ private static IEnumerable GetPackageReferenceItems(ProjectCollecti private static PackageReferenceEntry? CreateEntry(ProjectItem projectItem) { var id = projectItem.EvaluatedInclude; + + // Ignore the implicit NetStandard library reference in projects targeting NetStandard. + if (id.Equals(NetStandardPackageId, StringComparison.OrdinalIgnoreCase)) + return null; + var versionValue = projectItem.GetMetadata("Version")?.EvaluatedValue; if (versionValue.IsNullOrEmpty()) return null; @@ -136,5 +139,6 @@ private static IEnumerable GetPackageReferenceItems(ProjectCollecti return VersionRange.TryParse(versionValue, out var versionRange) ? new PackageReferenceEntry(id, versionRange, projectItem, projectItem.GetMetadataValue("Justification")) : null; + } } \ No newline at end of file diff --git a/src/View/NuGetMonitorCommand.cs b/src/View/NuGetMonitorCommand.cs index bd3bd9d..74e21c5 100644 --- a/src/View/NuGetMonitorCommand.cs +++ b/src/View/NuGetMonitorCommand.cs @@ -1,6 +1,5 @@ -using Microsoft.VisualStudio.Shell; -using System.ComponentModel.Design; -using Task = System.Threading.Tasks.Task; +using System.ComponentModel.Design; +using Microsoft.VisualStudio.Shell; namespace NuGetMonitor.View; @@ -30,7 +29,7 @@ public static NuGetMonitorCommand? Instance public static async Task InitializeAsync(AsyncPackage package) { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); + await JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)).ConfigureAwait(true) as OleMenuCommandService ?? throw new InvalidOperationException("Failed to get menu command service"); diff --git a/src/View/NuGetMonitorToolWindow.cs b/src/View/NuGetMonitorToolWindow.cs index 43cb7d6..5fe62cd 100644 --- a/src/View/NuGetMonitorToolWindow.cs +++ b/src/View/NuGetMonitorToolWindow.cs @@ -1,5 +1,5 @@ -using Microsoft.VisualStudio.Shell; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Shell; namespace NuGetMonitor.View; diff --git a/src/View/NugetMonitorViewModel.cs b/src/View/NugetMonitorViewModel.cs index 4846509..9fec669 100644 --- a/src/View/NugetMonitorViewModel.cs +++ b/src/View/NugetMonitorViewModel.cs @@ -75,7 +75,7 @@ private async void Load() } catch (Exception ex) { - await LoggingService.LogAsync($"Loading package data failed: {ex}"); + await LogAsync($"Loading package data failed: {ex}"); } finally { From 0e507475cfb604f1db20c9365db524043c8eae25 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sat, 23 Sep 2023 17:46:42 +0200 Subject: [PATCH 2/2] Fix: Transitive dependencies are listed multiple times in multi-targeting projects --- src/Models/PackageReferenceEntry.cs | 10 +++++----- src/Services/NuGetService.cs | 26 ++++++++++++++------------ src/Services/ProjectService.cs | 22 +++++++++++++++------- src/View/NugetMonitorViewModel.cs | 2 +- src/View/PackageViewModel.cs | 2 +- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/Models/PackageReferenceEntry.cs b/src/Models/PackageReferenceEntry.cs index 5c952df..fedb4fd 100644 --- a/src/Models/PackageReferenceEntry.cs +++ b/src/Models/PackageReferenceEntry.cs @@ -1,20 +1,20 @@ -using Microsoft.Build.Evaluation; -using NuGet.Versioning; +using NuGet.Versioning; +using NuGetMonitor.Services; namespace NuGetMonitor.Models; internal sealed record PackageReferenceEntry { - public PackageReferenceEntry(string id, VersionRange versionRange, ProjectItem projectItem, string justification) + public PackageReferenceEntry(string id, VersionRange versionRange, ProjectItemInTargetFramework projectItemInTargetFramework, string justification) { - ProjectItem = projectItem; + ProjectItemInTargetFramework = projectItemInTargetFramework; Justification = justification; Identity = new PackageReference(id, versionRange); } public PackageReference Identity { get; } - public ProjectItem ProjectItem { get; } + public ProjectItemInTargetFramework ProjectItemInTargetFramework { get; } public string Justification { get; } } \ No newline at end of file diff --git a/src/Services/NuGetService.cs b/src/Services/NuGetService.cs index f8b6010..119cd99 100644 --- a/src/Services/NuGetService.cs +++ b/src/Services/NuGetService.cs @@ -76,21 +76,23 @@ public static async Task> GetTransitivePacka { var results = new List(); - var packagesReferencesByProject = packageReferences.GroupBy(item => item.ProjectItem.Project); + var projectItemsByTargetFramework = packageReferences.GroupBy(item => item.ProjectItemInTargetFramework.TargetFramework); - foreach (var projectPackageReferences in packagesReferencesByProject) + foreach (var projectItemsInTargetFramework in projectItemsByTargetFramework) { - var project = projectPackageReferences.Key; + var targetFramework = projectItemsInTargetFramework.Key; - var projectsInTargetFramework = project.GetProjectsInTargetFramework(); + var packagesReferencesByProject = projectItemsInTargetFramework.GroupBy(item => item.ProjectItemInTargetFramework.ProjectItem.Project); - var topLevelPackagesInProject = topLevelPackages - .Where(package => projectPackageReferences.Any(item => package.PackageReferenceEntries.Contains(item))) - .Select(item => item.PackageInfo) - .ToArray(); - - foreach (var projectInTargetFramework in projectsInTargetFramework) + foreach (var projectPackageReferences in packagesReferencesByProject) { + var project = projectPackageReferences.Key; + + var topLevelPackagesInProject = topLevelPackages + .Where(package => projectPackageReferences.Any(item => package.PackageReferenceEntries.Contains(item))) + .Select(item => item.PackageInfo) + .ToArray(); + var inputQueue = new Queue(topLevelPackagesInProject); var parentsByChild = new Dictionary>(); var processedItemsByPackageId = new Dictionary(); @@ -106,7 +108,7 @@ public static async Task> GetTransitivePacka processedItemsByPackageId[packageIdentity.Id] = packageInfo; - var dependencies = await packageInfo.GetPackageDependenciesInFramework(projectInTargetFramework.TargetFramework); + var dependencies = await packageInfo.GetPackageDependenciesInFramework(targetFramework); foreach (var dependency in dependencies) { @@ -124,7 +126,7 @@ public static async Task> GetTransitivePacka .Where(item => transitivePackages.Contains(item.Key)) .ToDictionary(); - results.Add(new TransitiveDependencies(projectInTargetFramework.Project, projectInTargetFramework.TargetFramework, parentsByChild)); + results.Add(new TransitiveDependencies(project, targetFramework, parentsByChild)); } } diff --git a/src/Services/ProjectService.cs b/src/Services/ProjectService.cs index c363fb1..832109c 100644 --- a/src/Services/ProjectService.cs +++ b/src/Services/ProjectService.cs @@ -1,5 +1,6 @@ using System.IO; using Community.VisualStudio.Toolkit; +using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using NuGet.Frameworks; using NuGet.Versioning; @@ -9,6 +10,11 @@ namespace NuGetMonitor.Services; +internal sealed record ProjectItemInTargetFramework(ProjectItem ProjectItem, NuGetFramework TargetFramework) +{ + public ProjectRootElement ContainingProject => ProjectItem.Xml.ContainingProject; +} + internal sealed record ProjectInTargetFramework(Project Project, NuGetFramework TargetFramework); internal static class ProjectService @@ -42,7 +48,7 @@ public static async Task> GetPackageR return references .SelectMany(items => items) .OrderBy(item => item.Identity.Id) - .ThenBy(item => Path.GetFileName(item.ProjectItem.Xml.ContainingProject.FullPath)) + .ThenBy(item => Path.GetFileName(item.ProjectItemInTargetFramework.ContainingProject.FullPath)) .ToArray(); }); } @@ -51,7 +57,7 @@ public static ProjectInTargetFramework[] GetProjectsInTargetFramework(this Proje { var frameworkNames = (project.GetProperty("TargetFrameworks") ?? project.GetProperty("TargetFramework")) ?.EvaluatedValue - ?.Split(';') + .Split(';') .Select(value => value.Trim()) .ToArray(); @@ -99,7 +105,7 @@ private static IEnumerable GetPackageReferences(ProjectCo return packageReferences; } - private static IEnumerable GetPackageReferenceItems(ProjectCollection projectCollection, string projectPath) + private static IEnumerable GetPackageReferenceItems(ProjectCollection projectCollection, string projectPath) { try { @@ -112,7 +118,7 @@ private static IEnumerable GetPackageReferenceItems(ProjectCollecti var projects = project.GetProjectsInTargetFramework(); - var allItems = projects.SelectMany(p => p.Project.GetItems("PackageReference")); + var allItems = projects.SelectMany(p => p.Project.GetItems("PackageReference").Select(item => new ProjectItemInTargetFramework(item, p.TargetFramework))); return allItems; } @@ -120,12 +126,14 @@ private static IEnumerable GetPackageReferenceItems(ProjectCollecti { Log($"Get package reference item failed: {ex}"); - return Enumerable.Empty(); + return Enumerable.Empty(); } } - private static PackageReferenceEntry? CreateEntry(ProjectItem projectItem) + private static PackageReferenceEntry? CreateEntry(ProjectItemInTargetFramework projectItemInTargetFramework) { + var projectItem = projectItemInTargetFramework.ProjectItem; + var id = projectItem.EvaluatedInclude; // Ignore the implicit NetStandard library reference in projects targeting NetStandard. @@ -137,7 +145,7 @@ private static IEnumerable GetPackageReferenceItems(ProjectCollecti return null; return VersionRange.TryParse(versionValue, out var versionRange) - ? new PackageReferenceEntry(id, versionRange, projectItem, projectItem.GetMetadataValue("Justification")) + ? new PackageReferenceEntry(id, versionRange, projectItemInTargetFramework, projectItem.GetMetadataValue("Justification")) : null; } diff --git a/src/View/NugetMonitorViewModel.cs b/src/View/NugetMonitorViewModel.cs index 9fec669..fe44d92 100644 --- a/src/View/NugetMonitorViewModel.cs +++ b/src/View/NugetMonitorViewModel.cs @@ -113,7 +113,7 @@ private static void Update(ICollection packageViewModels) var packageReferencesByProject = packageViewModels .Where(viewModel => viewModel.IsUpdateAvailable) - .SelectMany(viewModel => viewModel.Items.Select(item => new { item.Identity, item.ProjectItem.Xml.ContainingProject.FullPath, viewModel.SelectedVersion })) + .SelectMany(viewModel => viewModel.Items.Select(item => new { item.Identity, item.ProjectItemInTargetFramework.ContainingProject.FullPath, viewModel.SelectedVersion })) .GroupBy(item => item.FullPath); foreach (var packageReferenceEntries in packageReferencesByProject) diff --git a/src/View/PackageViewModel.cs b/src/View/PackageViewModel.cs index ee8ab49..bf36684 100644 --- a/src/View/PackageViewModel.cs +++ b/src/View/PackageViewModel.cs @@ -14,7 +14,7 @@ public PackageViewModel(IGrouping items { Items = items; PackageReference = items.Key; - Projects = items.GroupBy(item => item.ProjectItem.Xml.ContainingProject).Select(item => new ProjectViewModel(item.Key)).ToArray(); + Projects = items.GroupBy(item => item.ProjectItemInTargetFramework.ContainingProject).Select(item => new ProjectViewModel(item.Key)).ToArray(); ActiveVersion = NuGetVersion.TryParse(PackageReference.VersionRange.OriginalString, out var simpleVersion) ? simpleVersion : PackageReference.VersionRange; Justifications = string.Join(", ", Items.Select(reference => reference.Justification).Distinct());