diff --git a/src/rgen/Microsoft.Macios.Transformer/Attributes/NotificationData.cs b/src/rgen/Microsoft.Macios.Transformer/Attributes/NotificationData.cs new file mode 100644 index 00000000000..51ccef7fb59 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Attributes/NotificationData.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Transformer.Attributes; + +readonly record struct NotificationData { + + /// + /// Diff the constructor used in the bindings. + /// + internal enum ConstructorType { + NotificationType, + NotificationCenter, + All + } + + public string? Type { get; init; } + public string? NotificationCenter { get; init; } + + public NotificationData (string? data, ConstructorType constructorType) + { + if (constructorType == ConstructorType.NotificationType) { + Type = data; + } else { + NotificationCenter = data; + } + } + + public NotificationData (string? type, string? notificationCenter) + { + Type = type; + NotificationCenter = notificationCenter; + } + + public static bool TryParse (AttributeData attributeData, + [NotNullWhen (true)] out NotificationData? data) + { + data = null; + var count = attributeData.ConstructorArguments.Length; + ConstructorType constructorType = ConstructorType.NotificationType; + string? notificationType = null; + string? notificationCenter = null; + + switch (count) { + case 0: + break; + case 1: + // we have to diff constructors that take a single parameter, either a string or a type + if (attributeData.ConstructorArguments [0].Value! is string notificationCenterValue) { + constructorType = ConstructorType.NotificationCenter; + notificationCenter = notificationCenterValue; + } else { + constructorType = ConstructorType.NotificationType; + notificationType = ((INamedTypeSymbol) attributeData.ConstructorArguments [0].Value!).ToDisplayString (); + } + break; + case 2: + constructorType = ConstructorType.All; + notificationType = ((INamedTypeSymbol) attributeData.ConstructorArguments [0].Value!).ToDisplayString (); + notificationCenter = (string?) attributeData.ConstructorArguments [1].Value!; + break; + default: + // 0 should not be an option.. + return false; + } + + if (attributeData.NamedArguments.Length == 0) { + data = constructorType switch { + ConstructorType.NotificationCenter => new (notificationCenter, ConstructorType.NotificationCenter), + ConstructorType.NotificationType => new (notificationType, ConstructorType.NotificationType), + _ => new (notificationType, notificationCenter) + }; + return true; + } + + foreach (var (argumentName, value) in attributeData.NamedArguments) { + switch (argumentName) { + case "Type": + notificationType = ((INamedTypeSymbol) value.Value!).ToDisplayString (); + break; + case "NotificationCenter": + notificationCenter = (string) value.Value!; + break; + default: + data = null; + return false; + } + } + + data = new (notificationType, notificationCenter); + return true; + } + +} diff --git a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs index 5c22a1fbf08..27cc5b09c38 100644 --- a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs +++ b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs @@ -184,6 +184,13 @@ static class AttributesNames { [BindingFlag (AttributeTargets.Method)] public const string NoMethodAttribute = "NoMethodAttribute"; + /// + /// When applied, flags the [Flags] as a notification and generates the + /// code to strongly type the notification. + /// + [BindingAttribute(typeof(NotificationData), AttributeTargets.Property)] + public const string NotificationAttribute = "NotificationAttribute"; + public const string NoTVAttribute = "NoTVAttribute"; public const string NoiOSAttribute = "NoiOSAttribute"; diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/BindDataTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/BindDataTests.cs index 31e4c5e575b..10d0b49d301 100644 --- a/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/BindDataTests.cs +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/BindDataTests.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. using System.Collections; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.Macios.Generator.Extensions; using Microsoft.Macios.Transformer.Attributes; using Xamarin.Tests; using Xamarin.Utils; diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/NotificationDataTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/NotificationDataTests.cs new file mode 100644 index 00000000000..fa4ef7f6ad8 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/NotificationDataTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Transformer.Attributes; +using Xamarin.Tests; +using Xamarin.Utils; + +namespace Microsoft.Macios.Transformer.Tests.Attributes; + +public class NotificationDataTests : AttributeParsingTestClass { + + class TestDataTryCreate : IEnumerable { + public IEnumerator GetEnumerator () + { + const string path = "/some/random/path.cs"; + + // simple notification + const string simpleNotification = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[NoMacCatalyst] +[BaseType (typeof (NSView))] +partial interface NSSplitView { + [Notification] + [Field (""NSSplitViewWillResizeSubviewsNotification"")] + NSString NSSplitViewWillResizeSubviewsNotification { get; } +} +"; + yield return [(Source: simpleNotification, Path: path), new NotificationData (null, null)]; + + // notification type + const string notificationWithType = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +class MyNotification {} + +[NoMacCatalyst] +[BaseType (typeof (NSView))] +partial interface NSSplitView { + [Notification (typeof (MyNotification))] + [Field (""NSSplitViewWillResizeSubviewsNotification"")] + NSString NSSplitViewWillResizeSubviewsNotification { get; } +} +"; + + yield return [(Source: notificationWithType, Path: path), new NotificationData ("Test.MyNotification", null)]; + + // notification center + const string notificationWithCenter = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +class MyNotification {} + +[NoMacCatalyst] +[BaseType (typeof (NSView))] +partial interface NSSplitView { + [Notification (""SharedWorkspace.NotificationCenter"")] + [Field (""NSSplitViewWillResizeSubviewsNotification"")] + NSString NSSplitViewWillResizeSubviewsNotification { get; } +} +"; + yield return [(Source: notificationWithCenter, Path: path), new NotificationData (null, "SharedWorkspace.NotificationCenter")]; + + // both + const string notificationWithBoth = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +class MyNotification {} + +[NoMacCatalyst] +[BaseType (typeof (NSView))] +partial interface NSSplitView { + [Notification (typeof (MyNotification), ""SharedWorkspace.NotificationCenter"")] + [Field (""NSSplitViewWillResizeSubviewsNotification"")] + NSString NSSplitViewWillResizeSubviewsNotification { get; } +} +"; + yield return [(Source: notificationWithBoth, Path: path), new NotificationData ("Test.MyNotification", "SharedWorkspace.NotificationCenter")]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + void TryCreateTests (ApplePlatform platform, (string Source, string Path) source, + NotificationData expectedData) + => AssertTryCreate (platform, source, AttributesNames.NotificationAttribute, + expectedData, NotificationData.TryParse, lastOrDefault: true); +}