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)
            {
                var queueNode = new DependencyNode(
                    dependency.Name,
                    IsProject(dependency.LibraryRange.TypeConstraint),
                    nodeWarningProperties);

                // Add the metadata from the parent project here.
                queue.Enqueue(queueNode);
            }
        }
        /// <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)
        {
            var result = new HashSet <NuGetLogCode>();

            if (nodeWarningProperties?.ProjectWide?.Count > 0)
            {
                result.UnionWith(nodeWarningProperties.ProjectWide);
            }

            if (nodeWarningProperties?.PackageSpecific?.Count > 0 &&
                nodeWarningProperties.PackageSpecific.TryGetValue(libraryId, out var codes) &&
                codes?.Count > 0)
            {
                result.UnionWith(codes);
            }

            return(result);
        }
        /// <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 <NuGetFramework, 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;

            // 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);
                    var nearestFramework = nodeProjectSpec.GetTargetFramework(parentTargetFramework).FrameworkName;

                    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 in packageNoWarn.Keys)
            {
                resultWarningProperties.AddRangeOfCodes(packageNoWarn[packageId], packageId, parentTargetFramework);
            }

            return(resultWarningProperties);
        }
 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 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;
 }
 /// <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));
 }