File: Linker\WarningSuppressionWriter.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Mono.Cecil;
 
namespace Mono.Linker
{
	public class WarningSuppressionWriter
	{
		private readonly Dictionary<AssemblyNameDefinition, HashSet<(int Code, IMemberDefinition Member)>> _warnings;
		private readonly FileOutputKind _fileOutputKind;
		readonly LinkContext _context;
 
		public WarningSuppressionWriter (LinkContext context, FileOutputKind fileOutputKind = FileOutputKind.CSharp)
		{
			_warnings = new Dictionary<AssemblyNameDefinition, HashSet<(int, IMemberDefinition)>> ();
			_fileOutputKind = fileOutputKind;
			_context = context;
		}
 
		public bool IsEmpty => _warnings.Count == 0;
 
		public void AddWarning (int code, ICustomAttributeProvider provider)
		{
			// We don't have a targeted suppression mechanism for
			// warnings from assembly-level attributes.
			if (provider is not IMemberDefinition memberDefinition)
				return;
 
			if (UnconditionalSuppressMessageAttributeState.GetModuleFromProvider (provider) is not ModuleDefinition module)
				return;
 
			var assemblyName = module.Assembly.Name;
			if (!_warnings.TryGetValue (assemblyName, out var warnings)) {
				warnings = new HashSet<(int, IMemberDefinition)> ();
				_warnings.Add (assemblyName, warnings);
			}
 
			warnings.Add ((code, memberDefinition));
		}
 
		public void OutputSuppressions (string directory)
		{
			foreach (var assemblyName in _warnings.Keys) {
				if (_fileOutputKind == FileOutputKind.Xml) {
					OutputSuppressionsXmlFormat (assemblyName, directory);
				} else {
					OutputSuppressionsCSharpFormat (assemblyName, directory);
				}
			}
		}
 
		void OutputSuppressionsXmlFormat (AssemblyNameDefinition assemblyName, string directory)
		{
			var xmlTree = new XElement ("linker");
			var xmlAssembly = new XElement ("assembly", new XAttribute ("fullname", assemblyName.FullName));
			xmlTree.Add (xmlAssembly);
 
			foreach (var warning in GetListOfWarnings (assemblyName)) {
				xmlAssembly.Add (
					new XElement ("attribute",
						new XAttribute ("fullname", "System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute"),
						new XElement ("argument", Constants.ILLink),
						new XElement ("argument", $"IL{warning.Code}"),
						new XElement ("property", new XAttribute ("name", UnconditionalSuppressMessageAttributeState.ScopeProperty),
							GetWarningSuppressionScopeString (warning.MemberDocumentationSignature)),
						new XElement ("property", new XAttribute ("name", UnconditionalSuppressMessageAttributeState.TargetProperty),
							warning.MemberDocumentationSignature)));
			}
 
			XDocument xdoc = new XDocument (xmlTree);
			using (var xw = XmlWriter.Create (Path.Combine (directory, $"{assemblyName.Name}.WarningSuppressions.xml"),
				new XmlWriterSettings { Indent = true })) {
				xdoc.Save (xw);
			}
		}
 
		void OutputSuppressionsCSharpFormat (AssemblyNameDefinition assemblyName, string directory)
		{
			using (var sw = new StreamWriter (Path.Combine (directory, $"{assemblyName.Name}.WarningSuppressions.cs"))) {
				StringBuilder sb = new StringBuilder ("using System.Diagnostics.CodeAnalysis;").AppendLine ().AppendLine ();
				foreach (var warning in GetListOfWarnings (assemblyName)) {
					sb.Append ("[assembly: UnconditionalSuppressMessage (\"")
						.Append (Constants.ILLink)
						.Append ("\", \"IL").Append (warning.Code)
						.Append ("\", Scope = \"").Append (GetWarningSuppressionScopeString (warning.MemberDocumentationSignature))
						.Append ("\", Target = \"").Append (warning.MemberDocumentationSignature)
						.AppendLine ("\")]");
				}
 
				sw.Write (sb.ToString ());
			}
		}
 
		List<(int Code, string MemberDocumentationSignature)> GetListOfWarnings (AssemblyNameDefinition assemblyName)
		{
			List<(int Code, string MemberDocumentationSignature)> listOfWarnings = new List<(int Code, string MemberDocumentationSignature)> ();
			StringBuilder sb = new StringBuilder ();
			foreach (var warning in _warnings[assemblyName].ToList ()) {
				DocumentationSignatureGenerator.VisitMember (warning.Member, sb, _context);
				listOfWarnings.Add ((warning.Code, sb.ToString ()));
				sb.Clear ();
			}
 
			listOfWarnings.Sort ();
			return listOfWarnings;
		}
 
		static string GetWarningSuppressionScopeString (string memberDocumentationSignature)
		{
			if (memberDocumentationSignature.StartsWith (DocumentationSignatureGenerator.TypePrefix))
				return "type";
 
			return "member";
		}
 
		public enum FileOutputKind
		{
			CSharp,
			Xml
		};
	}
}