File: Utility\ProjectPackagesPrintUtility.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.CommandLine.XPlat\NuGet.CommandLine.XPlat.csproj (NuGet.CommandLine.XPlat)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
using NuGet.CommandLine.XPlat.ListPackage;
using NuGet.Protocol;
using NuGet.Versioning;

namespace NuGet.CommandLine.XPlat.Utility
{
    /// <summary>
    /// A static class used to print the packages information for list command
    /// </summary>
    internal static class ProjectPackagesPrintUtility
    {
        /// <summary>
        /// Returns the metadata for list package report
        /// </summary>
        /// <param name="packages">A list of framework packages. Check <see cref="FrameworkPackages"/></param>
        /// <param name="listPackageArgs">Command line options</param>
        /// <param name="hasAutoReference">At least one discovered package is autoreference</param>
        /// <returns>The list of package metadata</returns>
        internal static List<ListPackageReportFrameworkPackage> GetPackagesMetadata(
            IEnumerable<FrameworkPackages> packages,
            ListPackageArgs listPackageArgs,
            ref bool hasAutoReference)
        {
            var projectFrameworkPackages = new List<ListPackageReportFrameworkPackage>();

            hasAutoReference = false;
            foreach (FrameworkPackages frameworkPackages in packages)
            {
                string frameWork = frameworkPackages.Framework;
                ListPackageReportFrameworkPackage targetFrameworkPackageMetadata = new ListPackageReportFrameworkPackage(frameWork, frameworkPackages.TargetAlias);
                projectFrameworkPackages.Add(targetFrameworkPackageMetadata);
                var frameworkTopLevelPackages = frameworkPackages.TopLevelPackages;
                var frameworkTransitivePackages = frameworkPackages.TransitivePackages;

                // If no packages exist for this framework, print the
                // appropriate message
                var tableHasAutoReference = false;
                // Print top-level packages
                if (frameworkTopLevelPackages.Any())
                {

                    targetFrameworkPackageMetadata.TopLevelPackages = GetFrameworkPackageMetadata(
                        frameworkTopLevelPackages, printingTransitive: false, listPackageArgs.ReportType, ref tableHasAutoReference).ToList();
                    hasAutoReference = hasAutoReference || tableHasAutoReference;
                }

                // Print transitive packages
                if (listPackageArgs.IncludeTransitive && frameworkTransitivePackages.Any())
                {
                    targetFrameworkPackageMetadata.TransitivePackages = GetFrameworkPackageMetadata(
                        frameworkTransitivePackages, printingTransitive: true, listPackageArgs.ReportType, ref tableHasAutoReference).ToList();
                }
            }

            return projectFrameworkPackages;
        }

        internal static IEnumerable<ListReportPackage> GetFrameworkPackageMetadata(
            IEnumerable<InstalledPackageReference> frameworkPackages,
            bool printingTransitive,
            ReportType reportType,
            ref bool tableHasAutoReference)
        {
            if (!frameworkPackages.Any())
            {
                return Enumerable.Empty<ListReportPackage>();
            }

            frameworkPackages = frameworkPackages.OrderBy(p => p.Name);

            var packages = frameworkPackages.Select(p => new ListReportPackage(
                packageId: p.Name,
                requestedVersion: printingTransitive ? string.Empty : p.OriginalRequestedVersion,
                autoReference: printingTransitive ? false : p.AutoReference,
                resolvedVersion: GetPackageVersion(p),
                latestVersion: reportType == ReportType.Outdated ? GetPackageVersion(p, useLatest: true) : null,
                vulnerabilities: reportType == ReportType.Vulnerable ? p.ResolvedPackageMetadata.Vulnerabilities?.ToList() : null,
                deprecationReasons: reportType == ReportType.Deprecated ? p.ResolvedPackageMetadata.GetDeprecationMetadataAsync().Result : null,
                alternativePackage: reportType == ReportType.Deprecated ? (p.ResolvedPackageMetadata.GetDeprecationMetadataAsync().Result)?.AlternatePackage : null
            ));

            tableHasAutoReference = frameworkPackages.Any(p => p.AutoReference);

            return packages;
        }

        /// <summary>
        /// Given a list of packages, this function will print them in a table
        /// </summary>
        /// <param name="packages">The list of packages</param>
        /// <param name="printingTransitive">Whether the function is printing transitive packages information.</param>
        /// <param name="listPackageArgs">Command line options.</param>
        /// <param name="tableHasAutoReference">Flagged if an autoreference marker was printer</param>
        /// <returns>The table as a string</returns>
        internal static IEnumerable<FormattedCell> BuildPackagesTable(
            IEnumerable<ListReportPackage> packages,
            bool printingTransitive,
            ListPackageArgs listPackageArgs,
            ref bool tableHasAutoReference)
        {
            var autoReferenceFlagged = false;

            if (!packages.Any())
            {
                return null;
            }

            var headers = BuildTableHeaders(printingTransitive, listPackageArgs);

            var valueSelectors = new List<Func<ListReportPackage, object>>
            {
                p => new FormattedCell(p.PackageId),
                p => new FormattedCell(GetAutoReferenceMarker(p, printingTransitive, ref autoReferenceFlagged)),
            };

            // Include "Requested" version column for top level package list
            if (!printingTransitive)
            {
                valueSelectors.Add(p => new FormattedCell(p?.RequestedVersion));
            }

            // "Resolved" version
            valueSelectors.Add(p => new FormattedCell(p.ResolvedVersion));

            switch (listPackageArgs.ReportType)
            {
                case ReportType.Outdated:
                    // "Latest" version
                    valueSelectors.Add(p => new FormattedCell(p.LatestVersion));
                    break;
                case ReportType.Deprecated:
                    valueSelectors.Add(p => new FormattedCell(
                        PrintDeprecationReasons(p.DeprecationReasons)));
                    valueSelectors.Add(p => new FormattedCell(
                        PrintAlternativePackage(p.AlternativePackage)));
                    break;
                case ReportType.Vulnerable:
                    valueSelectors.Add(p => PrintVulnerabilitiesSeverities(p.Vulnerabilities));
                    valueSelectors.Add(p => PrintVulnerabilitiesAdvisoryUrls(p.Vulnerabilities));
                    break;
            }

            var tableToPrint = packages.ToStringTable(headers, valueSelectors.ToArray());

            tableHasAutoReference = autoReferenceFlagged;
            return tableToPrint;
        }

        internal static void PrintPackagesTable(IEnumerable<FormattedCell> tableToPrint)
        {
            foreach (var formattedCell in tableToPrint)
            {
                if (formattedCell.ForegroundColor.HasValue)
                {
                    Console.ForegroundColor = formattedCell.ForegroundColor.Value;
                }

                Console.Write(formattedCell.Value);
                Console.ResetColor();
            }

            Console.WriteLine();
        }

        internal static IEnumerable<FormattedCell> PrintVulnerabilitiesSeverities(
            IEnumerable<PackageVulnerabilityMetadata> vulnerabilityMetadata)
        {
            return vulnerabilityMetadata == null || !vulnerabilityMetadata.Any()
                ? new List<FormattedCell> { new FormattedCell(string.Empty, foregroundColor: null) }
                : vulnerabilityMetadata.Select(VulnerabilityToSeverityFormattedCell);
        }

        internal static IEnumerable<FormattedCell> PrintVulnerabilitiesAdvisoryUrls(
            IEnumerable<PackageVulnerabilityMetadata> vulnerabilityMetadata)
        {
            return vulnerabilityMetadata == null || !vulnerabilityMetadata.Any()
                ? new List<FormattedCell> { new FormattedCell(string.Empty, foregroundColor: null) }
                : vulnerabilityMetadata.Select(v => new FormattedCell(v.AdvisoryUrl?.ToString() ?? string.Empty, foregroundColor: null));
        }

        private static FormattedCell VulnerabilityToSeverityFormattedCell(PackageVulnerabilityMetadata vulnerability)
        {
            switch (vulnerability?.Severity ?? -1)
            {
                case 0: return new FormattedCell("Low", foregroundColor: null); // default color for low severity
                case 1: return new FormattedCell("Moderate", foregroundColor: ConsoleColor.Yellow);
                case 2: return new FormattedCell("High", foregroundColor: ConsoleColor.Red);
                case 3: return new FormattedCell("Critical", foregroundColor: ConsoleColor.Red);
            }

            return new FormattedCell(string.Empty, foregroundColor: null);
        }

        private static string GetAutoReferenceMarker(
            ListReportPackage package,
            bool printingTransitive,
            ref bool autoReferenceFound)
        {
            if (printingTransitive) // we don't mark these on transitive package reports
            {
                return string.Empty;
            }

            if (package?.AutoReference == true)
            {
                autoReferenceFound = true;
                return "(A)";
            }
            return string.Empty;
        }

        private static string PrintDeprecationReasons(PackageDeprecationMetadata deprecationMetadata)
        {
            return deprecationMetadata == null
                ? string.Empty
                : string.Join(",", deprecationMetadata.Reasons);
        }

        private static string PrintAlternativePackage(AlternatePackageMetadata alternatePackageMetadata)
        {
            if (alternatePackageMetadata == null)
            {
                return string.Empty;
            }

            var versionRangeString = VersionRangeFormatter.Instance.Format(
                "p",
                alternatePackageMetadata.Range,
                VersionRangeFormatter.Instance);

            return $"{alternatePackageMetadata.PackageId} {versionRangeString}";
        }

        /// <summary>
        /// Print user-friendly representation of a NuGet version.
        /// </summary>
        /// <param name="package">The package reference having its version printed.</param>
        /// <param name="useLatest"><see langword="true" /> if we're printing the latest version; otherwise <see langword="false" />.</param>
        private static string GetPackageVersion(
            InstalledPackageReference package,
            bool useLatest = false)
        {
            if (package == null)
            {
                return string.Empty;
            }

            var output = useLatest ?
                package.LatestPackageMetadata?.Identity?.Version?.ToString() :
                package.ResolvedPackageMetadata?.Identity?.Version?.ToString();
            if (output == null)
            {
                return Strings.ListPkg_NotFoundAtSources;
            }

            return output;
        }

        /// <summary>
        /// Prepares the headers for the tables that will be printed
        /// </summary>
        /// <param name="printingTransitive">Whether the table is for transitive or not</param>
        /// <param name="listPackageArgs">Command line options</param>
        /// <returns></returns>
        internal static string[] BuildTableHeaders(bool printingTransitive, ListPackageArgs listPackageArgs)
        {
            var result = new List<string>();

            if (printingTransitive)
            {
                result.Add(Strings.ListPkg_TransitiveHeader);
                result.Add(string.Empty);
            }
            else
            {
                result.Add(Strings.ListPkg_TopLevelHeader);
                result.Add(string.Empty);
                result.Add(Strings.ListPkg_Requested);
            }

            result.Add(Strings.ListPkg_Resolved);

            switch (listPackageArgs.ReportType)
            {
                case ReportType.Outdated:
                    result.Add(Strings.ListPkg_Latest);
                    break;
                case ReportType.Deprecated:
                    result.Add(Strings.ListPkg_DeprecationReasons);
                    result.Add(Strings.ListPkg_DeprecationAlternative);
                    break;
                case ReportType.Vulnerable:
                    result.Add(Strings.ListPkg_VulnerabilitySeverity);
                    result.Add(Strings.ListPkg_VulnerabilityAdvisoryUrl);
                    break;
            }

            return result.ToArray();
        }
    }
}