|
// 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.Collections.Immutable;
using System.Linq;
using NuGet.Common;
using NuGet.DependencyResolver;
using NuGet.LibraryModel;
using NuGet.ProjectModel;
using NuGet.Shared;
namespace NuGet.Commands
{
internal static class TransitiveNoWarnUtils
{
/// <summary>
/// Creates a PackageSpecificWarningProperties for a project generated by traversing the dependency graph.
/// </summary>
/// <param name="targetGraphs">Parent project restore target graphs.</param>
/// <param name="parentProjectSpec">PackageSpec of the parent project.</param>
/// <returns>WarningPropertiesCollection with the project frameworks and the transitive package specific no warn properties.</returns>
public static WarningPropertiesCollection CreateTransitiveWarningPropertiesCollection(
IEnumerable<RestoreTargetGraph> targetGraphs,
PackageSpec parentProjectSpec)
{
var transitivePackageSpecificProperties = new PackageSpecificWarningProperties();
var projectFrameworks = new List<string>();
var parentWarningProperties = new WarningPropertiesCollection(
parentProjectSpec.RestoreMetadata?.ProjectWideWarningProperties,
PackageSpecificWarningProperties.CreatePackageSpecificWarningProperties(parentProjectSpec),
parentProjectSpec.TargetFrameworks.Select(f => f.TargetAlias).AsList().AsReadOnly());
var parentPackageSpecificNoWarn = ExtractPackageSpecificNoWarnPerFramework(
parentWarningProperties.PackageSpecificWarningProperties);
var warningPropertiesCache = new Dictionary<string, Dictionary<string, WarningPropertiesCollection>>(
StringComparer.OrdinalIgnoreCase);
foreach (var targetGraph in targetGraphs)
{
if (string.IsNullOrEmpty(targetGraph.RuntimeIdentifier))
{
if (parentPackageSpecificNoWarn == null ||
!parentPackageSpecificNoWarn.TryGetValue(targetGraph.TargetAlias, out var parentPackageSpecificNoWarnForFramework))
{
parentPackageSpecificNoWarnForFramework = null;
}
var transitiveNoWarnFromTargetGraph = ExtractTransitiveNoWarnProperties(
targetGraph,
parentProjectSpec.RestoreMetadata.ProjectName,
parentWarningProperties.ProjectWideWarningProperties.NoWarn.AsHashSet(),
parentPackageSpecificNoWarnForFramework,
warningPropertiesCache);
projectFrameworks.Add(targetGraph.TargetAlias);
transitivePackageSpecificProperties = MergePackageSpecificWarningProperties(
transitivePackageSpecificProperties,
transitiveNoWarnFromTargetGraph);
}
}
return new WarningPropertiesCollection(
projectWideWarningProperties: null,
packageSpecificWarningProperties: transitivePackageSpecificProperties,
projectFrameworks: projectFrameworks
);
}
/// <summary>
/// Traverses a Dependency graph starting from the parent project in BF style.
/// </summary>
/// <param name="targetGraph">Parent project restore target graph.</param>
/// <param name="parentProjectName">File path of the parent project.</param>
/// <param name="parentProjectWideNoWarn">Project Wide NoWarn properties of the parent project.</param>
/// <param name="parentPackageSpecificNoWarn">Package Specific NoWarn properties of the parent project.</param>
/// <returns>PackageSpecificWarningProperties containing all the NoWarn's for each package seen in the graph accumulated while traversing the graph.</returns>
private static PackageSpecificWarningProperties ExtractTransitiveNoWarnProperties(
RestoreTargetGraph targetGraph,
string parentProjectName,
HashSet<NuGetLogCode> parentProjectWideNoWarn,
Dictionary<string, HashSet<NuGetLogCode>> parentPackageSpecificNoWarn,
Dictionary<string, Dictionary<string, WarningPropertiesCollection>> warningPropertiesCache)
{
var dependencyMapping = new Dictionary<string, LookUpNode>(StringComparer.OrdinalIgnoreCase);
var queue = new Queue<DependencyNode>();
var seen = new Dictionary<string, NodeWarningProperties>(StringComparer.OrdinalIgnoreCase);
var resultWarningProperties = new PackageSpecificWarningProperties();
var packageNoWarn = new Dictionary<string, HashSet<NuGetLogCode>>(StringComparer.OrdinalIgnoreCase);
// All the packages in parent project's closure.
// Once we have collected data for all of these, we can exit.
var parentPackageDependencies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var parentTargetFramework = targetGraph.Framework;
var parentAlias = targetGraph.TargetAlias;
// Add all dependencies into a dict for a quick transitive lookup
foreach (var dependencyGraphItem in targetGraph.Flattened)
{
WarningPropertiesCollection nodeWarningProperties = null;
HashSet<NuGetLogCode> nodeProjectWideNoWarn = null;
Dictionary<string, HashSet<NuGetLogCode>> nodePackageSpecificNoWarn = null;
if (IsProject(dependencyGraphItem.Key.Type))
{
var localMatch = (LocalMatch)dependencyGraphItem.Data.Match;
var nodeProjectSpec = GetNodePackageSpec(localMatch);
TargetFrameworkInformation targetFrameworkInformation = nodeProjectSpec.GetTargetFramework(parentTargetFramework);
string nearestFramework = targetFrameworkInformation.FrameworkName != null ? targetFrameworkInformation.TargetAlias : null;
if (nearestFramework != null)
{
// Get the WarningPropertiesCollection from the PackageSpec
nodeWarningProperties = GetNodeWarningProperties(nodeProjectSpec, nearestFramework, warningPropertiesCache);
nodeProjectWideNoWarn = nodeWarningProperties.ProjectWideWarningProperties.NoWarn.AsHashSet();
var nodePackageSpecificWarningProperties = ExtractPackageSpecificNoWarnForFramework(
nodeWarningProperties.PackageSpecificWarningProperties,
nearestFramework);
if (nodePackageSpecificWarningProperties != null)
{
nodePackageSpecificNoWarn = nodePackageSpecificWarningProperties;
}
}
}
else
{
parentPackageDependencies.Add(dependencyGraphItem.Key.Name);
}
var lookUpNode = new LookUpNode()
{
Dependencies = dependencyGraphItem.Data.Dependencies,
NodeWarningProperties = NodeWarningProperties.Create(nodeProjectWideNoWarn, nodePackageSpecificNoWarn)
};
dependencyMapping[dependencyGraphItem.Key.Name] = lookUpNode;
}
// Get the direct dependencies for the parent project to seed the queue
var parentDependencies = dependencyMapping[parentProjectName];
// Seed the queue with the parent project's direct dependencies
var parentNoWarn = NodeWarningProperties.Create(parentProjectWideNoWarn, parentPackageSpecificNoWarn);
AddDependenciesToQueue(parentDependencies.Dependencies,
queue,
parentNoWarn);
// Add the parent project to the seen set to prevent adding it back to the queue
AddToSeen(seen, new DependencyNode(id: parentProjectName, isProject: true, parentNoWarn));
// start taking one node from the queue and get all of it's dependencies
while (queue.Count > 0)
{
var node = queue.Dequeue();
// Check if the node has already been visited, or if the node is a NoWarn superset of
// an existing node. If this is a superset it will not provide any new paths where a
// warning should be shown.
if (AddToSeen(seen, node) && dependencyMapping.TryGetValue(node.Id, out var nodeLookUp))
{
var nodeId = node.Id;
var nodeIsProject = node.IsProject;
var nodeDependencies = nodeLookUp.Dependencies;
var pathWarningProperties = node.NodeWarningProperties;
// If the node is a project then we need to extract the warning properties and
// add those to the warning properties of the current path.
if (nodeIsProject)
{
// Merge the node's no warn properties with the one in the path.
NodeWarningProperties nodeWarningProperties = nodeLookUp.NodeWarningProperties;
AddDependenciesToQueue(nodeDependencies,
queue,
nodeWarningProperties.Merge(node.NodeWarningProperties));
}
else if (parentPackageDependencies.Contains(nodeId))
{
// Evaluate the current path for package properties
var packageNoWarnFromPath = pathWarningProperties.ExtractPathNoWarnProperties(nodeId);
if (packageNoWarn.TryGetValue(nodeId, out var noWarnCodes))
{
// We have seen atleast one path which contained a NoWarn for the package
// We need to update the
noWarnCodes.IntersectWith(packageNoWarnFromPath);
}
else
{
noWarnCodes = packageNoWarnFromPath;
packageNoWarn.Add(nodeId, noWarnCodes);
}
// Check if there was any NoWarn in the path
if (noWarnCodes.Count == 0)
{
// If the path does not "NoWarn" for this package then remove the path from parentPackageDependencies
// This is done because if there are no "NoWarn" in one path, the the warnings must come through
// We no longer care about this package in the graph
parentPackageDependencies.Remove(nodeId);
// If parentPackageDependencies is empty then exit the graph traversal
if (parentPackageDependencies.Count == 0)
{
break;
}
}
AddDependenciesToQueue(nodeDependencies,
queue,
pathWarningProperties);
}
}
}
// At the end of the graph traversal add the remaining package no warn lists into the result
foreach ((var packageId, var codes) in packageNoWarn)
{
resultWarningProperties.AddRangeOfCodes(codes.ToImmutableArray(), packageId, parentAlias);
}
return resultWarningProperties;
}
private static WarningPropertiesCollection GetNodeWarningProperties(
PackageSpec nodeProjectSpec,
string framework,
Dictionary<string, Dictionary<string, WarningPropertiesCollection>> warningPropertiesCache)
{
var key = nodeProjectSpec.RestoreMetadata.ProjectPath;
if (!warningPropertiesCache.TryGetValue(key, out var frameworkCollection))
{
frameworkCollection
= new Dictionary<string, WarningPropertiesCollection>(StringComparer.OrdinalIgnoreCase);
warningPropertiesCache[key] = frameworkCollection;
}
if (!frameworkCollection.TryGetValue(framework, out var collection))
{
collection = new WarningPropertiesCollection(
nodeProjectSpec.RestoreMetadata?.ProjectWideWarningProperties,
PackageSpecificWarningProperties.CreatePackageSpecificWarningProperties(nodeProjectSpec, framework),
nodeProjectSpec.TargetFrameworks.Select(f => f.TargetAlias).AsList().AsReadOnly());
frameworkCollection.Add(framework, collection);
}
return collection;
}
/// <summary>
/// Add to the seen list for tracking.
/// </summary>
/// <returns>True if the node should be walked</returns>
private static bool AddToSeen(Dictionary<string, NodeWarningProperties> seen, DependencyNode node)
{
var id = node.Id;
var nodeProps = node.NodeWarningProperties;
if (!seen.TryGetValue(id, out var visitedProps))
{
// New id
seen.Add(id, nodeProps);
return true;
}
if (!nodeProps.IsSubSetOf(visitedProps))
{
// Find the intersection of properties between these nodes,
// these are the only properties that we need to look for in
// future passes.
seen[id] = nodeProps.GetIntersect(visitedProps);
return true;
}
// This has already been walked
return false;
}
private static void AddDependenciesToQueue(IEnumerable<LibraryDependency> dependencies,
Queue<DependencyNode> queue,
NodeWarningProperties nodeWarningProperties)
{
// Add all the project's dependencies to the Queue with the merged WarningPropertiesCollection
foreach (var dependency in dependencies.NoAllocEnumerate())
{
var queueNode = new DependencyNode(
dependency.Name,
IsProject(dependency.LibraryRange.TypeConstraint),
nodeWarningProperties);
// Add the metadata from the parent project here.
queue.Enqueue(queueNode);
}
}
private static PackageSpec GetNodePackageSpec(LocalMatch localMatch)
{
return (PackageSpec)localMatch.LocalLibrary.Items[KnownLibraryProperties.PackageSpec];
}
/// <summary>
/// Extracts the no warn codes for a libraryId from the warning properties at the node in the graph.
/// </summary>
/// <param name="nodeWarningProperties">warning properties at the node in the graph.</param>
/// <param name="libraryId">libraryId for which the no warn codes have to be extracted.</param>
/// <returns>HashSet of NuGetLogCodes containing the no warn codes for the libraryId.</returns>
public static HashSet<NuGetLogCode> ExtractPathNoWarnProperties(
NodeWarningProperties nodeWarningProperties,
string libraryId)
{
return nodeWarningProperties.ExtractPathNoWarnProperties(libraryId);
}
/// <summary>
/// Merge 2 WarningProperties objects.
/// This method will combine the warning properties from both the collections.
/// </summary>
/// <param name="first">First Object to be merged.</param>
/// <param name="second">Second Object to be merged.</param>
/// <returns>Returns a WarningProperties with the combined warning properties.
/// Returns the reference to one of the inputs if the other input is Null.
/// Returns a Null if both the input properties are Null. </returns>
public static HashSet<NuGetLogCode> MergeCodes(
HashSet<NuGetLogCode> first,
HashSet<NuGetLogCode> second)
{
HashSet<NuGetLogCode> result = null;
if (TryMergeNullObjects(first, second, out var merged))
{
result = merged;
}
else
{
if (first.Count == 0)
{
return second;
}
if (second.Count == 0)
{
return first;
}
if (first.SetEqualsWithNullCheck(second))
{
return first;
}
// Merge NoWarn Sets.
result = new HashSet<NuGetLogCode>(first.Concat(second));
}
return result;
}
/// <summary>
/// Merge 2 PackageSpecific NoWarns.
/// This method will combine the warning properties from both the collections.
/// </summary>
/// <param name="first">First Object to be merged.</param>
/// <param name="second">Second Object to be merged.</param>
/// <returns>Returns a PackageSpecificWarningProperties with the combined warning properties.
/// Will return the reference to one of the inputs if the other input is Null.
/// Returns a Null if both the input properties are Null. </returns>
public static Dictionary<string, HashSet<NuGetLogCode>> MergePackageSpecificNoWarn(
Dictionary<string, HashSet<NuGetLogCode>> first,
Dictionary<string, HashSet<NuGetLogCode>> second)
{
if (TryMergeNullObjects(first, second, out var merged))
{
return merged;
}
if (first.Count == 0)
{
return second;
}
if (second.Count == 0)
{
return first;
}
merged = new Dictionary<string, HashSet<NuGetLogCode>>(StringComparer.OrdinalIgnoreCase);
foreach (var pair in first.Concat(second))
{
var id = pair.Key;
if (!merged.TryGetValue(id, out var codes))
{
// Did not exist, use the existing code set
merged.Add(id, pair.Value);
}
else
{
// Create a new set with the merged codes
merged[id] = MergeCodes(codes, pair.Value);
}
}
return merged;
}
/// <summary>
/// Merge 2 PackageSpecificWarningProperties objects.
/// This method will combine the warning properties from both the collections.
/// </summary>
/// <param name="first">First Object to be merged.</param>
/// <param name="second">Second Object to be merged.</param>
/// <returns>Returns a PackageSpecificWarningProperties with the combined warning properties.
/// Will return the reference to one of the inputs if the other input is Null.
/// Returns a Null if both the input properties are Null. </returns>
public static PackageSpecificWarningProperties MergePackageSpecificWarningProperties(
PackageSpecificWarningProperties first,
PackageSpecificWarningProperties second)
{
PackageSpecificWarningProperties result = null;
if (TryMergeNullObjects(first, second, out var merged))
{
result = merged;
}
else
{
result = new PackageSpecificWarningProperties();
if (first.Properties != null)
{
foreach (var codePair in first.Properties)
{
var code = codePair.Key;
var libraryCollection = codePair.Value;
foreach (var libraryPair in libraryCollection)
{
var libraryId = libraryPair.Key;
var frameworks = libraryPair.Value;
result.AddRangeOfFrameworks(code, libraryId, frameworks);
}
}
}
if (second.Properties != null)
{
foreach (var codePair in second.Properties)
{
var code = codePair.Key;
var libraryCollection = codePair.Value;
foreach (var libraryPair in libraryCollection)
{
var libraryId = libraryPair.Key;
var frameworks = libraryPair.Value;
result.AddRangeOfFrameworks(code, libraryId, frameworks);
}
}
}
}
return result;
}
/// <summary>
/// Try to merge 2 objects if one or both of them are null.
/// </summary>
/// <param name="first">First Object to be merged.</param>
/// <param name="second">Second Object to be merged.</param>
/// <param name="merged">Out Merged Object.</param>
/// <returns>Returns true if atleast one of the objects was Null.
/// If none of them is null then the returns false, indicating that the merge failed.</returns>
public static bool TryMergeNullObjects<T>(T first, T second, out T merged) where T : class
{
merged = null;
var result = false;
if (first == null && second == null)
{
merged = null;
result = true;
}
else if (first == null)
{
merged = second;
result = true;
}
else if (second == null)
{
merged = first;
result = true;
}
return result;
}
/// <summary>
/// Checks if a LibraryDependencyTarget is a project.
/// </summary>
/// <param name="type">LibraryDependencyTarget to be checked.</param>
/// <returns>True if a LibraryDependencyTarget is Project or ExternalProject.</returns>
private static bool IsProject(LibraryDependencyTarget type)
{
return (type == LibraryDependencyTarget.ExternalProject || type == LibraryDependencyTarget.Project);
}
/// <summary>
/// Checks if a LibraryType is a project.
/// </summary>
/// <param name="type">LibraryType to be checked.</param>
/// <returns>True if a LibraryType is Project or ExternalProject.</returns>
private static bool IsProject(LibraryType type)
{
return (type == LibraryType.ExternalProject || type == LibraryType.Project);
}
/// <summary>
/// Indexes a PackageSpecificWarningProperties collection on framework.
/// </summary>
/// <param name="packageSpecificWarningProperties">PackageSpecificWarningProperties to be converted.</param>
/// <returns>New dictionary containing the data of a PackageSpecificWarningProperties collection on framework.</returns>
public static Dictionary<string, Dictionary<string, HashSet<NuGetLogCode>>> ExtractPackageSpecificNoWarnPerFramework(
PackageSpecificWarningProperties packageSpecificWarningProperties)
{
Dictionary<string, Dictionary<string, HashSet<NuGetLogCode>>> result = null;
if (packageSpecificWarningProperties?.Properties != null)
{
result = new Dictionary<string, Dictionary<string, HashSet<NuGetLogCode>>>(StringComparer.OrdinalIgnoreCase);
foreach (var codePair in packageSpecificWarningProperties.Properties)
{
var code = codePair.Key;
var libraryCollection = codePair.Value;
foreach (var libraryPair in libraryCollection)
{
var libraryId = libraryPair.Key;
var frameworks = libraryPair.Value;
foreach (var framework in frameworks)
{
if (!result.TryGetValue(framework, out var frameworkCollection))
{
frameworkCollection = new Dictionary<string, HashSet<NuGetLogCode>>(StringComparer.OrdinalIgnoreCase);
result[framework] = frameworkCollection;
}
if (!frameworkCollection.TryGetValue(libraryId, out var codes))
{
codes = new HashSet<NuGetLogCode>();
frameworkCollection[libraryId] = codes;
}
codes.Add(code);
}
}
}
}
return result;
}
/// <summary>
/// Indexes a PackageSpecificWarningProperties collection on framework.
/// </summary>
/// <param name="packageSpecificWarningProperties">PackageSpecificWarningProperties to be converted.</param>
/// <returns>New dictionary containing the data of a PackageSpecificWarningProperties collection on framework.</returns>
public static Dictionary<string, HashSet<NuGetLogCode>> ExtractPackageSpecificNoWarnForFramework(
PackageSpecificWarningProperties packageSpecificWarningProperties,
string framework)
{
Dictionary<string, HashSet<NuGetLogCode>> result = null;
if (packageSpecificWarningProperties?.Properties != null && framework != null)
{
result = new Dictionary<string, HashSet<NuGetLogCode>>(StringComparer.OrdinalIgnoreCase);
foreach (var codePair in packageSpecificWarningProperties.Properties)
{
var code = codePair.Key;
var libraryCollection = codePair.Value;
foreach (var libraryPair in libraryCollection)
{
var libraryId = libraryPair.Key;
var frameworks = libraryPair.Value;
if (frameworks.Contains(framework))
{
if (!result.TryGetValue(libraryId, out var codes))
{
codes = new HashSet<NuGetLogCode>();
result[libraryId] = codes;
}
codes.Add(code);
}
}
}
}
return result;
}
/// <summary>
/// A simple node class to hold the outgoing dependency edge during the graph walk.
/// </summary>
public class DependencyNode : IEquatable<DependencyNode>
{
// ID of the Node
public string Id { get; }
// bool to indicate if the node is a project node
// if false then the node is a package
public bool IsProject { get; }
// If a node is a project then it will hold these properties
public NodeWarningProperties NodeWarningProperties { get; }
public DependencyNode(string id, bool isProject, HashSet<NuGetLogCode> projectWideNoWarn, Dictionary<string, HashSet<NuGetLogCode>> packageSpecificNoWarn)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
NodeWarningProperties = NodeWarningProperties.Create(projectWideNoWarn, packageSpecificNoWarn);
IsProject = isProject;
}
public DependencyNode(string id, bool isProject, NodeWarningProperties nodeWarningProperties)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
NodeWarningProperties = nodeWarningProperties ?? throw new ArgumentNullException(nameof(nodeWarningProperties));
IsProject = isProject;
}
public override int GetHashCode()
{
var hashCode = new HashCodeCombiner();
hashCode.AddStringIgnoreCase(Id);
hashCode.AddObject(IsProject);
return hashCode.CombinedHash;
}
public override bool Equals(object obj)
{
return Equals(obj as DependencyNode);
}
public bool Equals(DependencyNode other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return IsProject == other.IsProject &&
string.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase) &&
EqualityUtility.EqualsWithNullCheck(NodeWarningProperties, other.NodeWarningProperties);
}
public override string ToString()
{
return $"{(IsProject ? "Project" : "Package")}/{Id}";
}
}
/// <summary>
/// A simple node class to hold the outgoing dependency edges for a quick look up.
/// </summary>
private class LookUpNode
{
// List of dependencies for this node
public IEnumerable<LibraryDependency> Dependencies { get; set; }
// If a node is a project then it will hold these properties
public NodeWarningProperties NodeWarningProperties { get; set; }
}
/// <summary>
/// An immutable class to hold minimal version of project wide nowarn and package specific no warn for a project.
/// </summary>
public class NodeWarningProperties : IEquatable<NodeWarningProperties>
{
// Empty NodeWarningProperties singleton - to avoid allocation for most-common case
internal static readonly NodeWarningProperties Empty = new NodeWarningProperties(null, null);
// ProjectWide NoWarn properties
// Note: this set should not be modified by callers. In the future, this should be converted to a private readonly instance variable
public HashSet<NuGetLogCode> ProjectWide { get; }
// PackageSpecific NoWarn
// We do not use framework here as DependencyNode is created per parent project framework.
// Note: this dictionary should not be modified by callers. In the future, this should be converted to a private readonly instance variable
public Dictionary<string, HashSet<NuGetLogCode>> PackageSpecific { get; }
// Note: internal callers should use the Create function instead so the null/null case re-uses the Empty singleton
public NodeWarningProperties(
HashSet<NuGetLogCode> projectWide,
Dictionary<string, HashSet<NuGetLogCode>> packageSpecific)
{
ProjectWide = projectWide;
PackageSpecific = packageSpecific;
}
public override int GetHashCode()
{
var hashCode = new HashCodeCombiner();
hashCode.AddUnorderedSequence(ProjectWide);
hashCode.AddDictionary(PackageSpecific);
return hashCode.CombinedHash;
}
public override bool Equals(object obj)
{
return Equals(obj as NodeWarningProperties);
}
public bool Equals(NodeWarningProperties other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return EqualityUtility.SetEqualsWithNullCheck(ProjectWide, other.ProjectWide) &&
EqualityUtility.DictionaryEquals(PackageSpecific, other.PackageSpecific, (s, o) => EqualityUtility.SetEqualsWithNullCheck(s, o));
}
/// <summary>
/// Obtain the intersection of this node and the other node
/// This method will return the intersection of the warning properties from both sets, not modifying either object.
/// </summary>
/// <remarks>Null is considered an empty set, which has an empty intersection with anything else.</remarks>
/// <param name="other">other node to intersect with</param>
/// <returns>intersection between this node and other node</returns>
public NodeWarningProperties GetIntersect(NodeWarningProperties other)
{
if (other == null)
{
return null;
}
if (ReferenceEquals(this, other))
{
return this;
}
Dictionary<string, HashSet<NuGetLogCode>> thisPackages = PackageSpecific;
Dictionary<string, HashSet<NuGetLogCode>> otherPackages = other.PackageSpecific;
// null is empty and cannot intersect
Dictionary<string, HashSet<NuGetLogCode>> packages = null;
if (thisPackages != null && otherPackages != null)
{
packages = new Dictionary<string, HashSet<NuGetLogCode>>(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, HashSet<NuGetLogCode>> pair in thisPackages)
{
if (otherPackages.TryGetValue(pair.Key, out var otherCodes))
{
HashSet<NuGetLogCode> intersect = Intersect(pair.Value, otherCodes);
if (intersect != null)
{
packages.Add(pair.Key, intersect);
}
}
}
if (packages.Count == 0)
{
packages = null;
}
}
HashSet<NuGetLogCode> projectWide = Intersect(ProjectWide, other.ProjectWide);
return Create(projectWide, packages);
}
/// <summary>
/// True if the given set is a subset of this set, or equal to it.
/// </summary>
/// <remarks>Null is considered an empty set, and will return true.</remarks>
public bool IsSubSetOf(NodeWarningProperties other)
{
if (other == null)
{
return true;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (IsSubSetOfWithNullCheck(ProjectWide, other.ProjectWide))
{
Dictionary<string, HashSet<NuGetLogCode>> package = PackageSpecific;
Dictionary<string, HashSet<NuGetLogCode>> otherPackage = other.PackageSpecific;
if (otherPackage == null)
{
return true;
}
// To be a subset this set of package specific warnings must contain
// every id and code found in other. A single miss will fail the check.
foreach (KeyValuePair<string, HashSet<NuGetLogCode>> pair in otherPackage)
{
if (pair.Value == null || pair.Value.Count == 0)
{
continue;
}
if (package == null)
{
return false;
}
if (!package.TryGetValue(pair.Key, out var codes))
{
return false;
}
if (codes == null || !pair.Value.IsSubsetOf(codes))
{
return false;
}
}
return true;
}
return false;
}
internal static NodeWarningProperties Create(
HashSet<NuGetLogCode> projectWide,
Dictionary<string, HashSet<NuGetLogCode>> packageSpecific)
{
if (projectWide == null && packageSpecific == null)
{
return Empty;
}
return new NodeWarningProperties(projectWide, packageSpecific);
}
/// <summary>
/// Merge this NodeWarningProperties object with the provided one.
/// This method will return a combination of the warning properties from both sets, not modifying either object.
/// </summary>
/// <param name="other">Object to merge with.</param>
/// <returns>Returns a NodeWarningProperties combining this class's and the other class's node warning properties.</returns>
internal NodeWarningProperties Merge(NodeWarningProperties other)
{
HashSet<NuGetLogCode> mergedProjectWideNoWarn = MergeCodes(ProjectWide, other?.ProjectWide);
Dictionary<string, HashSet<NuGetLogCode>> mergedPackageSpecificNoWarn = MergePackageSpecificNoWarn(PackageSpecific, other?.PackageSpecific);
return Create(mergedProjectWideNoWarn, mergedPackageSpecificNoWarn);
}
/// <summary>
/// Extracts the no warn codes for a libraryId from the warning properties at the node in the graph.
/// </summary>
/// <param name="libraryId">libraryId for which the no warn codes have to be extracted.</param>
/// <returns>HashSet of NuGetLogCodes containing the no warn codes for the libraryId.</returns>
internal HashSet<NuGetLogCode> ExtractPathNoWarnProperties(
string libraryId)
{
var result = new HashSet<NuGetLogCode>();
if (ProjectWide?.Count > 0)
{
result.UnionWith(ProjectWide);
}
if (PackageSpecific?.Count > 0 &&
PackageSpecific.TryGetValue(libraryId, out var codes) &&
codes?.Count > 0)
{
result.UnionWith(codes);
}
return result;
}
private static bool IsSubSetOfWithNullCheck(HashSet<NuGetLogCode> parent, HashSet<NuGetLogCode> other)
{
// Null is empty and always a subset.
if (other == null || other.Count == 0)
{
return true;
}
// A null or empty parent cannot be a superset.
if (parent == null || parent.Count == 0)
{
return false;
}
if (other.Count <= parent.Count)
{
return other.IsSubsetOf(parent);
}
return false;
}
private static HashSet<NuGetLogCode> Intersect(HashSet<NuGetLogCode> first, HashSet<NuGetLogCode> second)
{
if (ReferenceEquals(first, second))
{
return first;
}
// null is empty and cannot intersect
if (first == null || first.Count == 0)
{
return null;
}
if (second == null || second.Count == 0)
{
return null;
}
var result = new HashSet<NuGetLogCode>(first);
result.IntersectWith(second);
if (result.Count == 0)
{
return null;
}
return result;
}
}
}
}
|