using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using NUnit.Framework; using Mono.Cecil; using Xamarin.Tests; using Xamarin.Utils; using System.Configuration.Assemblies; using MethodAttributes = Mono.Cecil.MethodAttributes; using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; #nullable enable namespace Cecil.Tests { [TestFixture] public class ApiCapitalizationTest { bool IsMemberObsolete (ICustomAttributeProvider member) { if (member is null || !member.HasCustomAttributes) return false; return member.CustomAttributes.Any ((m) => m.AttributeType.Name == "ObsoleteAttribute" || m.AttributeType.Name == "AdviceAttribute" || m.AttributeType.Name == "ObsoletedOSPlatformAttribute"); } bool IsUnique (MethodDefinition m) { return m is not null && (m.IsRemoveOn || m.IsAddOn || m.IsConstructor || m.IsSpecialName || IsMemberObsolete (m) || m.IsFamilyOrAssembly || m.IsPInvokeImpl); } Dictionary> allowedProperties = new Dictionary> () { ["MPMusicPlayerController"] = new HashSet () { "iPodMusicPlayer" }, ["ABPersonPhoneLabel"] = new HashSet () { "iPhone" }, ["CNLabelPhoneNumberKey"] = new HashSet () { "iPhone" }, ["AVMetadata"] = new HashSet () { "iTunesMetadataKeyAccountKind", "iTunesMetadataKeyAcknowledgement", "iTunesMetadataKeyAlbum", "iTunesMetadataKeyAlbumArtist", "iTunesMetadataKeyAppleID", "iTunesMetadataKeyArranger", "iTunesMetadataKeyArtDirector", "iTunesMetadataKeyArtist", "iTunesMetadataKeyArtistID", "iTunesMetadataKeyAuthor", "iTunesMetadataKeyBeatsPerMin", "iTunesMetadataKeyComposer", "iTunesMetadataKeyConductor", "iTunesMetadataKeyContentRating", "iTunesMetadataKeyCopyright", "iTunesMetadataKeyCoverArt", "iTunesMetadataKeyCredits", "iTunesMetadataKeyDescription", "iTunesMetadataKeyDirector", "iTunesMetadataKeyDiscCompilation", "iTunesMetadataKeyDiscNumber", "iTunesMetadataKeyEQ", "iTunesMetadataKeyEncodedBy", "iTunesMetadataKeyEncodingTool", "iTunesMetadataKeyExecProducer", "iTunesMetadataKeyGenreID", "iTunesMetadataKeyGrouping", "iTunesMetadataKeyLinerNotes", "iTunesMetadataKeyLyrics", "iTunesMetadataKeyOnlineExtras", "iTunesMetadataKeyOriginalArtist", "iTunesMetadataKeyPerformer", "iTunesMetadataKeyPhonogramRights", "iTunesMetadataKeyPlaylistID", "iTunesMetadataKeyPredefinedGenre", "iTunesMetadataKeyProducer", "iTunesMetadataKeyPublisher", "iTunesMetadataKeyRecordCompany", "iTunesMetadataKeyReleaseDate", "iTunesMetadataKeySoloist", "iTunesMetadataKeySongID", "iTunesMetadataKeySongName", "iTunesMetadataKeySoundEngineer", "iTunesMetadataKeyThanks", "iTunesMetadataKeyTrackNumber", "iTunesMetadataKeyTrackSubTitle", "iTunesMetadataKeyUserComment", "iTunesMetadataKeyUserGenre" }, ["QuickTimeMetadata"] = new HashSet () { "iXML" } }; Dictionary> allowedFields = new Dictionary> () { }; Dictionary> allowedMethods = new Dictionary> () { ["Dlfcn"] = new HashSet () { "dlopen", "dlerror", "dlsym" } }; public bool IsSkip (string type, string memberName, Dictionary> allowed) { if (allowed.TryGetValue (type, out var result)) { if (result.Contains (memberName)) return true; } return Char.IsUpper (memberName [0]); } [TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))] [Test] public void PropertiesCapitalizationTest (AssemblyInfo info) { var assembly = info.Assembly; Func> selectLambda = (type) => { var typeName = type.Name; return type.Properties .Where (p => p.GetMethod?.IsPublic == true || p.SetMethod?.IsPublic == true) .Where (p => !IsSkip (type.Name, p.Name, allowedProperties) && !IsMemberObsolete (p) && !p.IsSpecialName) .Select (p => p.Name); }; CapitalizationTest (assembly, selectLambda); } [TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))] [Test] public void MethodsCapitalizationTest (AssemblyInfo info) { var assembly = info.Assembly; Func> selectLambda = (type) => { return from m in type.Methods where m.IsPublic && !IsSkip (type.Name, m.Name, allowedMethods) && !IsUnique (m) select m.Name; }; CapitalizationTest (assembly, selectLambda); } [TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))] [Test] public void EventsCapitalizationTest (AssemblyInfo info) { var assembly = info.Assembly; Func> selectLambda = (type) => { return from e in type.Events where !(Char.IsUpper (e.Name [0])) select e.Name; }; CapitalizationTest (assembly, selectLambda); } [TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))] [Test] public void FieldsCapitalizationTest (AssemblyInfo info) { var assembly = info.Assembly; Func> selectLambda = (type) => { return from f in type.Fields where f.IsPublic && f.IsFamilyOrAssembly && !IsSkip (type.Name, f.Name, allowedFields) select f.Name; }; CapitalizationTest (assembly, selectLambda); } public void CapitalizationTest (AssemblyDefinition assembly, Func> selectLambda) { var typeDict = new Dictionary (); var publicTypes = assembly.MainModule.Types.Where ((t) => t.IsPublic && !IsMemberObsolete (t)); foreach (var type in publicTypes) { TypeCheck (type, selectLambda, typeDict); } Assert.AreEqual (0, typeDict.Count (), $"Capitalization Issues Found: {string.Join (Environment.NewLine, typeDict)}"); } public void TypeCheck (TypeDefinition type, Func> selectLambda, Dictionary typeDict) { var err = selectLambda (type); if (err is not null && err.Any ()) { if (typeDict.TryGetValue ($"Type: {type.Name}", out var errMembers)) { typeDict [$"Type: {type.Name}"] += string.Join ("; ", err); } else { typeDict.Add ($"Type: {type.Name}", string.Join ("; ", err)); } } if (type.HasNestedTypes) { foreach (var nestedType in type.NestedTypes) { TypeCheck (nestedType, selectLambda, typeDict); } } } } }