| File: Rules\ReferencesInNuspecMatchRefAssetsRule.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging) |
// 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. using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Xml.Linq; using NuGet.Common; using NuGet.Frameworks; namespace NuGet.Packaging.Rules { internal class ReferencesInNuspecMatchRefAssetsRule : IPackageRule { private string _addToRefFormat = AnalysisResources.ReferencesInNuspecAndRefFilesDontMatchWarningAddToRefListItemFormat; private string _addToNuspecFormat = AnalysisResources.ReferencesInNuspecAndRefFilesDontMatchWarningAddToNuspecListItemFormat; private string _addToNuspecNoTfmFormat = AnalysisResources.ReferencesInNuspecAndRefFilesDontMatchWarningAddToNuspecNoTfmListItemFormat; public string MessageFormat => AnalysisResources.ReferencesInNuspecAndRefFilesDontMatchWarning; public IEnumerable<PackagingLogMessage> Validate(PackageArchiveReader builder) { var refFiles = builder.GetFiles().Where(t => t.StartsWith(PackagingConstants.Folders.Ref, StringComparison.OrdinalIgnoreCase)); var nuspecReferences = GetReferencesFromNuspec(builder.GetNuspec()); var missingItems = Compare(nuspecReferences, refFiles); return GenerateWarnings(missingItems); } internal IDictionary<string, IEnumerable<string>> GetReferencesFromNuspec(Stream nuspecStream) { var nuspecReferences = new Dictionary<string, IEnumerable<string>>(); var packageNuspec = new NuspecReader(nuspecStream); var nuspec = packageNuspec.Xml; if (nuspec != null) { XNamespace name = nuspec.Root!.Name.Namespace; var targetFrameworks = nuspec.Descendants(XName.Get("{" + name.NamespaceName + "}references")).Elements().Attributes("targetFramework"); nuspecReferences = targetFrameworks.ToDictionary(k => NuGetFramework.Parse(k.Value).GetShortFolderName(), k => k.Parent!.Elements().Attributes("file").Select(f => f.Value)); var filesWithoutTFM = nuspec.Descendants(XName.Get("{" + name.NamespaceName + "}references")) .Elements().Attributes("file").Select(f => f.Value); nuspecReferences.Add("any", filesWithoutTFM); } return nuspecReferences; } internal IEnumerable<MissingReference> Compare(IDictionary<string, IEnumerable<string>> nuspecReferences, IEnumerable<string> refFiles) { var missingReferences = new List<MissingReference>(); if (nuspecReferences.Any()) { if (refFiles.Any()) { var filesByTFM = refFiles.Where(t => t.Count(m => m == '/') > 1) .GroupBy(t => NuGetFramework.ParseFolder(t.Split('/')[1]).GetShortFolderName(), t => Path.GetFileName(t)); var keys = GetAllKeys(filesByTFM); var missingSubfolderInFiles = nuspecReferences.Keys.Where(t => !keys.Contains(t) && !NuGetFramework.ParseFolder(t).GetShortFolderName().Equals("unsupported", StringComparison.Ordinal) && !NuGetFramework.ParseFolder(t).GetShortFolderName().Equals("any", StringComparison.Ordinal)); if (missingSubfolderInFiles.Any()) { var subfolder = nuspecReferences.Where(t => missingSubfolderInFiles.Contains(t.Key)); foreach (var item in subfolder) { missingReferences.Add(new MissingReference("ref", item.Key, item.Value.ToArray())); } } foreach (var files in filesByTFM) { if (files.Key == "unsupported") { continue; } string[] missingNuspecReferences; string[] missingFiles; IEnumerable<string>? anyReferences = null; if (nuspecReferences.TryGetValue(files.Key, out var currentReferences) || nuspecReferences.TryGetValue("any", out anyReferences)) { if (anyReferences != null && currentReferences == null) { missingNuspecReferences = files.Where(m => !anyReferences.Contains(m)).ToArray(); missingFiles = anyReferences.Where(t => !files.Contains(t)).ToArray(); } else { missingNuspecReferences = files.Where(m => !currentReferences!.Contains(m)).ToArray(); missingFiles = currentReferences!.Where(t => !files.Contains(t)).ToArray(); } } else { missingNuspecReferences = files.ToArray(); missingFiles = Array.Empty<string>(); } if (missingFiles.Length != 0) { missingReferences.Add(new MissingReference("ref", files.Key, missingFiles)); } if (missingNuspecReferences.Length != 0) { missingReferences.Add(new MissingReference("nuspec", files.Key, missingNuspecReferences)); } } } else { foreach (var item in nuspecReferences) { var refs = item.Value.ToArray(); if (refs.Length != 0) { missingReferences.Add(new MissingReference("ref", item.Key, refs)); } } } } return missingReferences; } internal IEnumerable<PackagingLogMessage> GenerateWarnings(IEnumerable<MissingReference> missingReferences) { var issues = new List<PackagingLogMessage>(); if (missingReferences.Any()) { var message = new StringBuilder(); message.AppendLine(MessageFormat); var referencesMissing = missingReferences.Where(t => t.MissingFrom == "nuspec"); var refFilesMissing = missingReferences.Where(t => t.MissingFrom == "ref"); foreach (var file in refFilesMissing) { foreach (var item in file.MissingItems) { message.AppendLine(string.Format(CultureInfo.CurrentCulture, _addToRefFormat, item, file.Tfm)); } } foreach (var reference in referencesMissing) { foreach (var item in reference.MissingItems) { if (reference.Tfm.Equals("any", StringComparison.Ordinal)) { message.AppendLine(string.Format(CultureInfo.CurrentCulture, _addToNuspecNoTfmFormat, item)); } else { message.AppendLine(string.Format(CultureInfo.CurrentCulture, _addToNuspecFormat, item, reference.Tfm)); } } } issues.Add(PackagingLogMessage.CreateWarning(message.ToString(), NuGetLogCode.NU5131)); } return issues; } internal class MissingReference { public string MissingFrom { get; } public string Tfm { get; } public string[] MissingItems { get; } public MissingReference(string missingFrom, string tfm, string[] missingItems) { MissingFrom = missingFrom; Tfm = tfm; MissingItems = missingItems; } } internal List<string> GetAllKeys(IEnumerable<IGrouping<string, string>> filesByTFM) { var keys = new List<string>(); foreach (var item in filesByTFM) { keys.Add(item.Key); } return keys; } } }