diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 7903ace5ef4a..911024e53715 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -22,7 +22,7 @@ namespace Microsoft.Macios.Generator; /// [Generator] public class BindingSourceGeneratorGenerator : IIncrementalGenerator { - static readonly CodeChangesEqualityComparer equalityComparer = new (); + static readonly DeclarationCodeChangesEqualityComparer equalityComparer = new (); /// public void Initialize (IncrementalGeneratorInitializationContext context) @@ -42,7 +42,7 @@ public void Initialize (IncrementalGeneratorInitializationContext context) .CreateSyntaxProvider (static (node, _) => IsValidNode (node), static (ctx, _) => GetChangesForSourceGen (ctx)) .Where (tuple => tuple.BindingAttributeFound) - .Select (static (tuple, _) => tuple.Changes) + .Select (static (tuple, _) => (tuple.Declaration, tuple.Changes)) .WithComparer (equalityComparer); context.RegisterSourceOutput (context.CompilationProvider.Combine (provider.Collect ()), @@ -59,7 +59,8 @@ public void Initialize (IncrementalGeneratorInitializationContext context) _ => false, }; - static (CodeChanges Changes, bool BindingAttributeFound) GetChangesForSourceGen (GeneratorSyntaxContext context) + static (BaseTypeDeclarationSyntax Declaration, CodeChanges Changes, bool BindingAttributeFound) + GetChangesForSourceGen (GeneratorSyntaxContext context) { // we do know that the context node has to be one of the base type declarations var declarationSyntax = Unsafe.As (context.Node); @@ -68,30 +69,32 @@ public void Initialize (IncrementalGeneratorInitializationContext context) bool isBindingType = declarationSyntax.HasAttribute (context.SemanticModel, AttributesNames.BindingAttribute); if (!isBindingType) { - // return an empty data + false - return (default, false); + // return empty data + false + return (declarationSyntax, default, false); } var codeChanges = CodeChanges.FromDeclaration (declarationSyntax, context.SemanticModel); // if code changes are null, return the default value and a false to later ignore the change - return codeChanges is not null ? (codeChanges.Value, isBindingType) : (default, false); + return codeChanges is not null + ? (declarationSyntax, codeChanges.Value, isBindingType) + : (declarationSyntax, default, false); } static void GenerateCode (SourceProductionContext context, Compilation compilation, - ImmutableArray changesList) + ImmutableArray<(BaseTypeDeclarationSyntax Declaration, CodeChanges Changes)> changesList) { - // the process is as follows, get all the changes we have received from the incremental generator, - // loop over them and based on the CodeChange.BindingType we are going to build the symbol context + // The process is as follows, get all the changes we have received from the incremental generator, + // loop over them, and based on the CodeChange.BindingType we are going to build the symbol context // and emitter. Those are later used to generate the code. // // Once all the enums, classes and interfaces have been processed, we will use the data collected // in the RootBindingContext to generate the library and trampoline code. var rootContext = new RootBindingContext (compilation); - foreach (var change in changesList) { - var semanticModel = compilation.GetSemanticModel (change.SymbolDeclaration.SyntaxTree); + foreach (var (declaration, change) in changesList) { + var semanticModel = compilation.GetSemanticModel (declaration.SyntaxTree); // This is a bug in the roslyn analyzer for roslyn generator https://github.com/dotnet/roslyn-analyzers/issues/7436 #pragma warning disable RS1039 - if (semanticModel.GetDeclaredSymbol (change.SymbolDeclaration) is not INamedTypeSymbol namedTypeSymbol) + if (semanticModel.GetDeclaredSymbol (declaration) is not INamedTypeSymbol namedTypeSymbol) #pragma warning restore RS1039 continue; @@ -100,10 +103,10 @@ static void GenerateCode (SourceProductionContext context, Compilation compilati sb.WriteHeader (); if (EmitterFactory.TryCreate (change, rootContext, semanticModel, namedTypeSymbol, sb, out var emitter)) { // write the using statements - CollectUsingStatements (change.SymbolDeclaration.SyntaxTree, sb, emitter); + CollectUsingStatements (declaration.SyntaxTree, sb, emitter); if (emitter.TryEmit (out var diagnostics)) { - // only add file when we do generate code + // only add a file when we do generate code var code = sb.ToString (); context.AddSource ($"{Path.Combine (emitter.SymbolNamespace, emitter.SymbolName)}.g.cs", SourceText.From (code, Encoding.UTF8)); @@ -112,10 +115,12 @@ static void GenerateCode (SourceProductionContext context, Compilation compilati context.ReportDiagnostics (diagnostics); } } else { - // we don't have a emitter for this type, so we can't generate the code, add a diagnostic letting the + // we don't have an emitter for this type, so we can't generate the code, add a diagnostic letting the // user we do not support what they are trying to do - context.ReportDiagnostic (Diagnostic.Create (Diagnostics.RBI0000, // An unexpected error ocurred while processing '{0}'. Please fill a bug report at https://github.com/xamarin/xamarin-macios/issues/new. - change.SymbolDeclaration.GetLocation (), + context.ReportDiagnostic (Diagnostic.Create ( + Diagnostics + .RBI0000, // An unexpected error ocurred while processing '{0}'. Please fill a bug report at https://github.com/xamarin/xamarin-macios/issues/new. + declaration.GetLocation (), namedTypeSymbol.ToDisplayString ().Trim ())); } } @@ -138,7 +143,7 @@ static void GenerateLibraryCode (SourceProductionContext context, RootBindingCon var emitter = new LibraryEmitter (rootContext, sb); if (emitter.TryEmit (out var diagnostics)) { - // only add file when we do generate code + // only add a file when we do generate code var code = sb.ToString (); context.AddSource ($"{Path.Combine (emitter.SymbolNamespace, emitter.SymbolName)}.g.cs", SourceText.From (code, Encoding.UTF8)); diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs index 4a24c1f4a601..e283d9b7e619 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs @@ -7,8 +7,8 @@ class ClassBindingContext : SymbolBindingContext { public string RegisterName { get; init; } public ClassBindingContext (RootBindingContext context, SemanticModel semanticModel, - INamedTypeSymbol symbol, ClassDeclarationSyntax declarationSyntax) - : base (context, semanticModel, symbol, declarationSyntax) + INamedTypeSymbol symbol) + : base (context, semanticModel, symbol) { RegisterName = symbol.Name; //TODO: placeholder -> should this be extracted from the BindingTypeAttribute diff --git a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs index 365281bc640c..e39caf9a7449 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs @@ -12,7 +12,6 @@ class SymbolBindingContext { public SemanticModel SemanticModel { get; } public INamedTypeSymbol Symbol { get; } - public BaseTypeDeclarationSyntax DeclarationSyntax { get; } public string Namespace => Symbol.ContainingNamespace.ToDisplayString (); public string SymbolName => Symbol.Name; @@ -20,12 +19,11 @@ class SymbolBindingContext { public SymbolAvailability SymbolAvailability { get; } public SymbolBindingContext (RootBindingContext rootBindingContext, - SemanticModel semanticModel, INamedTypeSymbol symbol, BaseTypeDeclarationSyntax declarationSyntax) + SemanticModel semanticModel, INamedTypeSymbol symbol) { RootBindingContext = rootBindingContext; SemanticModel = semanticModel; Symbol = symbol; - DeclarationSyntax = declarationSyntax; SymbolAvailability = symbol.GetSupportedPlatforms (); } } diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs index dc16fb990803..55d85fc0a9c8 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,40 +24,60 @@ readonly struct CodeChanges { /// public string FullyQualifiedSymbol { get; } - /// - /// Base symbol declaration that triggered the code changes. - /// - public BaseTypeDeclarationSyntax SymbolDeclaration { get; } - /// /// Changes to the attributes of the symbol. /// public ImmutableArray Attributes { get; init; } = []; + readonly ImmutableArray enumMembers = []; + /// /// Changes to the enum members of the symbol. /// - public ImmutableArray EnumMembers { get; init; } = []; + public ImmutableArray EnumMembers { + get => enumMembers; + init => enumMembers = value; + } + + readonly ImmutableArray properties = []; /// /// Changes to the properties of the symbol. /// - public ImmutableArray Properties { get; init; } = []; + public ImmutableArray Properties { + get => properties; + init => properties = value; + } + + readonly ImmutableArray constructors = []; /// /// Changes to the constructors of the symbol. /// - public ImmutableArray Constructors { get; init; } = []; + public ImmutableArray Constructors { + get => constructors; + init => constructors = value; + } + + readonly ImmutableArray events = []; /// /// Changes to the events of the symbol. /// - public ImmutableArray Events { get; init; } = []; + public ImmutableArray Events { + get => events; + init => events = value; + } + + readonly ImmutableArray methods = []; /// /// Changes to the methods of a symbol. /// - public ImmutableArray Methods { get; init; } = []; + public ImmutableArray Methods { + get => methods; + init => methods = value; + } /// /// Decide if an enum value should be ignored as a change. @@ -110,20 +132,43 @@ internal static bool Skip (MethodDeclarationSyntax methodDeclarationSyntax, Sema if (methodDeclarationSyntax.Modifiers.Any (SyntaxKind.PartialKeyword)) { return !methodDeclarationSyntax.HasAttribute (semanticModel, AttributesNames.ExportMethodAttribute); } + return true; } + delegate bool SkipDelegate (T declarationSyntax, SemanticModel semanticModel); + + delegate bool TryCreateDelegate (T declaration, SemanticModel semanticModel, + [NotNullWhen (true)] out TR? change) + where T : MemberDeclarationSyntax + where TR : struct; + + static void GetMembers (TypeDeclarationSyntax baseDeclarationSyntax, SemanticModel semanticModel, + SkipDelegate skip, TryCreateDelegate tryCreate, out ImmutableArray members) + where T : MemberDeclarationSyntax + where TR : struct + { + var bucket = ImmutableArray.CreateBuilder (); + var declarations = baseDeclarationSyntax.Members.OfType (); + foreach (var declaration in declarations) { + if (skip (declaration, semanticModel)) + continue; + if (tryCreate (declaration, semanticModel, out var change)) + bucket.Add (change.Value); + } + + members = bucket.ToImmutable (); + } + /// /// Internal constructor added for testing purposes. /// /// The type of binding for the given code changes. /// The fully qualified name of the symbol. - /// The symbol declaration syntax. - internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, BaseTypeDeclarationSyntax declaration) + internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol) { BindingType = bindingType; FullyQualifiedSymbol = fullyQualifiedSymbol; - SymbolDeclaration = declaration; } /// @@ -135,7 +180,6 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base { BindingType = BindingType.SmartEnum; FullyQualifiedSymbol = enumDeclaration.GetFullyQualifiedIdentifier (); - SymbolDeclaration = enumDeclaration; Attributes = enumDeclaration.GetAttributeCodeChanges (semanticModel); var bucket = ImmutableArray.CreateBuilder (); // loop over the fields and add those that contain a FieldAttribute @@ -158,53 +202,19 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base /// The semantic model of the compilation. CodeChanges (ClassDeclarationSyntax classDeclaration, SemanticModel semanticModel) { + BindingType = BindingType.Class; FullyQualifiedSymbol = classDeclaration.GetFullyQualifiedIdentifier (); - SymbolDeclaration = classDeclaration; - EnumMembers = []; // always empty since classes dot not have enum values - Attributes = []; - - var properties = ImmutableArray.CreateBuilder (); - var propertyDeclarations = classDeclaration.Members.OfType (); - foreach (var declaration in propertyDeclarations) { - if (Skip (declaration, semanticModel)) - continue; - if (Property.TryCreate (declaration, semanticModel, out var change)) - properties.Add (change.Value); - } - - Properties = properties.ToImmutable (); + Attributes = classDeclaration.GetAttributeCodeChanges (semanticModel); - var constructors = ImmutableArray.CreateBuilder (); - var constructorDeclarations = classDeclaration.Members.OfType (); - foreach (var declaration in constructorDeclarations) { - if (Skip (declaration, semanticModel)) - continue; - if (Constructor.TryCreate (declaration, semanticModel, out var change)) - constructors.Add (change.Value); - } - - Constructors = constructors.ToImmutable (); - - var events = ImmutableArray.CreateBuilder (); - var eventDeclarations = classDeclaration.Members.OfType (); - foreach (var declaration in eventDeclarations) { - if (Skip (declaration, semanticModel)) - continue; - if (Event.TryCreate (declaration, semanticModel, out var change)) - events.Add (change.Value); - } - - Events = events.ToImmutable (); - - var methods = ImmutableArray.CreateBuilder (); - var methodDeclarations = classDeclaration.Members.OfType (); - foreach (MethodDeclarationSyntax declaration in methodDeclarations) { - if (Skip (declaration, semanticModel)) - continue; - if (Method.TryCreate (declaration, semanticModel, out var change)) - methods.Add (change.Value); - } - Methods = methods.ToImmutable (); + // use the generic method to get the members, we are using an out param to try an minimize the number of times + // the value types are copied + GetMembers (classDeclaration, semanticModel, Skip, + Constructor.TryCreate, out constructors); + GetMembers (classDeclaration, semanticModel, Skip, Property.TryCreate, + out properties); + GetMembers (classDeclaration, semanticModel, Skip, Event.TryCreate, out events); + GetMembers (classDeclaration, semanticModel, Skip, Method.TryCreate, + out methods); } /// @@ -214,11 +224,17 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base /// The semantic model of the compilation. CodeChanges (InterfaceDeclarationSyntax interfaceDeclaration, SemanticModel semanticModel) { + BindingType = BindingType.Protocol; FullyQualifiedSymbol = interfaceDeclaration.GetFullyQualifiedIdentifier (); - SymbolDeclaration = interfaceDeclaration; - // TODO: to be implemented once we add protocol support - EnumMembers = []; - Attributes = []; + Attributes = interfaceDeclaration.GetAttributeCodeChanges (semanticModel); + // we do not init the constructors, we use the default empty array + + GetMembers (interfaceDeclaration, semanticModel, Skip, Property.TryCreate, + out properties); + GetMembers (interfaceDeclaration, semanticModel, Skip, Event.TryCreate, + out events); + GetMembers (interfaceDeclaration, semanticModel, Skip, Method.TryCreate, + out methods); } /// @@ -237,4 +253,26 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base ClassDeclarationSyntax classDeclarationSyntax => new CodeChanges (classDeclarationSyntax, semanticModel), _ => null }; + + /// + public override string ToString () + { + var sb = new StringBuilder ("Changes: {"); + sb.Append ($"BindingType: {BindingType}, "); + sb.Append ($"FullyQualifiedSymbol: {FullyQualifiedSymbol}, "); + sb.Append ("Attributes: ["); + sb.AppendJoin (", ", Attributes); + sb.Append ("], EnumMembers: ["); + sb.AppendJoin (", ", EnumMembers); + sb.Append ("], Constructors: ["); + sb.AppendJoin (", ", Constructors); + sb.Append ("], Properties: ["); + sb.AppendJoin (", ", Properties); + sb.Append ("], Methods: ["); + sb.AppendJoin (", ", Methods); + sb.Append ("], Events: ["); + sb.AppendJoin (", ", Events); + sb.Append ('}'); + return sb.ToString (); + } } diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs index 489b2fd0509a..e4dd817fec43 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs @@ -1,9 +1,23 @@ using System; using System.Collections.Generic; -using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.Macios.Generator.DataModel; +class DeclarationCodeChangesEqualityComparer : IEqualityComparer<(BaseTypeDeclarationSyntax Declaration, CodeChanges + Changes)> { + readonly CodeChangesEqualityComparer comparer = new (); + + public bool Equals ((BaseTypeDeclarationSyntax Declaration, CodeChanges Changes) x, + (BaseTypeDeclarationSyntax Declaration, CodeChanges Changes) y) + { + return comparer.Equals (x.Changes, y.Changes); + } + + public int GetHashCode ((BaseTypeDeclarationSyntax Declaration, CodeChanges Changes) obj) + => comparer.GetHashCode (obj.Changes); +} + /// /// Custom code changes comparer used for the Roslyn code generation to invalidate caching. /// @@ -23,8 +37,6 @@ public bool Equals (CodeChanges x, CodeChanges y) return false; if (x.BindingType != y.BindingType) return false; - if (x.SymbolDeclaration.GetType () != y.SymbolDeclaration.GetType ()) - return false; if (x.Attributes.Length != y.Attributes.Length) return false; if (x.EnumMembers.Length != y.EnumMembers.Length) diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs index c6c01368ffed..6b2d0780a0c7 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs @@ -14,19 +14,19 @@ public static bool TryCreate (CodeChanges changes, RootBindingContext context, S INamedTypeSymbol symbol, TabbedStringBuilder builder, [NotNullWhen (true)] out ICodeEmitter? emitter) { - switch (changes.SymbolDeclaration) { - case ClassDeclarationSyntax classDeclarationSyntax: { - var ctx = new ClassBindingContext (context, semanticModel, symbol, classDeclarationSyntax); + switch (changes.BindingType) { + case BindingType.Class: { + var ctx = new ClassBindingContext (context, semanticModel, symbol); emitter = new ClassEmitter (ctx, builder); break; } - case EnumDeclarationSyntax enumDeclarationSyntax: { - var ctx = new SymbolBindingContext (context, semanticModel, symbol, enumDeclarationSyntax); + case BindingType.SmartEnum: { + var ctx = new SymbolBindingContext (context, semanticModel, symbol); emitter = new EnumEmitter (ctx, builder); break; } - case InterfaceDeclarationSyntax interfaceDeclarationSyntax: { - var ctx = new SymbolBindingContext (context, semanticModel, symbol, interfaceDeclarationSyntax); + case BindingType.Protocol: { + var ctx = new SymbolBindingContext (context, semanticModel, symbol); emitter = new InterfaceEmitter (ctx, builder); break; } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs new file mode 100644 index 000000000000..befd098227bd --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs @@ -0,0 +1,530 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.DataModel; +using Xamarin.Tests; +using Xamarin.Utils; +using Xunit; + +namespace Microsoft.Macios.Generator.Tests.DataModel; + +public class ClassCodeChangesTests : BaseGeneratorTestClass { + readonly CodeChangesEqualityComparer comparer = new (); + + class TestDataCodeChangesFromClassDeclaration : IEnumerable { + public IEnumerator GetEnumerator () + { + const string emptyClass = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { +} +"; + + yield return [ + emptyClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ] + } + ]; + + const string singleConstructorClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + string name = string.Empty; + + public MyClass () {} +} +"; + + yield return [ + singleConstructorClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Constructors = [ + new ( + type: "NS.MyClass", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword) + ], + parameters: [] + ) + ] + } + ]; + + const string multiConstructorClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + string name = string.Empty; + + public MyClass () {} + + public MyClass(string inName) { + name = inName; + } +} +"; + + yield return [ + multiConstructorClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Constructors = [ + new ( + type: "NS.MyClass", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword) + ], + parameters: [] + ), + new ( + type: "NS.MyClass", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword) + ], + parameters: [ + new ( + position: 0, + type: "string", + name: "inName" + ) + ] + ), + ] + } + ]; + + const string singlePropertyClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Export (""name"")] + public partial string Name { get; set; } = string.Empty; +} +"; + + yield return [ + singlePropertyClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Properties = [ + new ( + name: "Name", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ) + ] + } + ]; + + const string multiPropertyClassMissingExport = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Export (""name"")] + public partial string Name { get; set; } = string.Empty; + + public partial string Surname { get; set; } = string.Empty; +} +"; + + yield return [ + multiPropertyClassMissingExport, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Properties = [ + new ( + name: "Name", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ) + ] + } + ]; + + const string multiPropertyClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Export (""name"")] + public partial string Name { get; set; } = string.Empty; + + [Export (""surname"")] + public partial string Surname { get; set; } = string.Empty; +} +"; + + yield return [ + multiPropertyClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Properties = [ + new ( + name: "Name", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ), + new ( + name: "Surname", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["surname"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ), + ] + } + ]; + + const string singleMethodClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Export (""withName:"")] + public partial void SetName (string inName); +} +"; + + yield return [ + singleMethodClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Methods = [ + new ( + type: "NS.MyClass", + name: "SetName", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withName:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inName") + ] + ), + ] + } + ]; + + const string multiMethodClassMissingExport = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Export (""withName:"")] + public partial void SetName (string inName); + + public void SetSurname (string inSurname) {} +} +"; + + yield return [ + multiMethodClassMissingExport, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Methods = [ + new ( + type: "NS.MyClass", + name: "SetName", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withName:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inName") + ] + ), + ] + } + ]; + + + const string multiMethodClass = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + [Export (""withName:"")] + public partial void SetName (string inName); + + [Export (""withSurname:"")] + public partial void SetSurname (string inSurname); +} +"; + yield return [ + multiMethodClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Methods = [ + new ( + type: "NS.MyClass", + name: "SetName", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withName:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inName") + ] + ), + new ( + type: "NS.MyClass", + name: "SetSurname", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withSurname:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inSurname") + ] + ), + ] + } + ]; + + const string singleEventClass = @" +using System; +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + + public event EventHandler Changed { add; remove; } +} +"; + + yield return [ + singleEventClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Events = [ + new ( + name: "Changed", + type: "System.EventHandler", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + ], + accessors: [ + new (AccessorKind.Add, [], []), + new (AccessorKind.Remove, [], []) + ]) + ], + } + ]; + + const string multiEventClass = @" +using System; +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial class MyClass { + + public event EventHandler Changed { add; remove; } + + public event EventHandler Removed { add; remove; } +} +"; + + yield return [ + multiEventClass, + new CodeChanges ( + bindingType: BindingType.Class, + fullyQualifiedSymbol: "NS.MyClass" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Events = [ + new ( + name: "Changed", + type: "System.EventHandler", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + ], + accessors: [ + new (AccessorKind.Add, [], []), + new (AccessorKind.Remove, [], []) + ] + ), + new ( + name: "Removed", + type: "System.EventHandler", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + ], + accessors: [ + new (AccessorKind.Add, [], []), + new (AccessorKind.Remove, [], []) + ] + ), + ], + } + ]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + void CodeChangesFromClassDeclaration (ApplePlatform platform, string inputText, CodeChanges expected) + { + var (compilation, sourceTrees) = + CreateCompilation (nameof (CodeChangesFromClassDeclaration), platform, inputText); + Assert.Single (sourceTrees); + // get the declarations we want to work with and the semantic model + var node = sourceTrees [0].GetRoot () + .DescendantNodes () + .OfType () + .FirstOrDefault (); + Assert.NotNull (node); + var semanticModel = compilation.GetSemanticModel (sourceTrees [0]); + var changes = CodeChanges.FromDeclaration (node, semanticModel); + Assert.NotNull (changes); + Assert.Equal (expected, changes.Value, comparer); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesComparerTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesComparerTests.cs index c3e931fa807c..439415a37dc4 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesComparerTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesComparerTests.cs @@ -11,98 +11,27 @@ namespace Microsoft.Macios.Generator.Tests.DataModel; public class CodeChangesComparerTests : BaseGeneratorTestClass { readonly CodeChangesEqualityComparer comparer = new (); - // returns a node that matches the given node type from an example syntax tree - T GetSyntaxNode (ApplePlatform platform) where T : BaseTypeDeclarationSyntax + [Fact] + public void CompareDifferentFullyQualifiedSymbol () { - var attrsText = @" -using System; - -namespace ObjCBindings; -public class SimpleAttribute : Attribute { -} - -public class AttributeWithParams : Attribute { - public AttributeWithParams (string name, int value) { - } -} -"; - var inputText = @" -using System; -using Foundation; -using ObjCBindings; - -namespace AVFoundation; - -[SimpleAttribute, AttributeWithParams (""first"", 2)] -public class TestClass { - - [SimpleAttribute, AttributeWithParams (""first"", 2)] - public void SayHello () { - } -} - -[SimpleAttribute, AttributeWithParams (""first"", 2)] -public enum TestEnum { - [SimpleAttribute, AttributeWithParams (""first"", 2)] - First, -} - -[SimpleAttribute, AttributeWithParams (""first"", 2)] -public interface IInterface { - [SimpleAttribute, AttributeWithParams (""first"", 2)] - public void SayHello (); -} -"; - var (_, sourceTrees) = - CreateCompilation (nameof (CodeChangesComparerTests), platform, attrsText, inputText); - Assert.Equal (2, sourceTrees.Length); - // get the declarations we want to work with and the semantic model - var nodes = sourceTrees [1].GetRoot ().DescendantNodes ().ToArray (); - var declarationNode = nodes - .OfType () - .FirstOrDefault (); - Assert.NotNull (declarationNode); - return declarationNode; - } - - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentFullyQualifiedSymbol (ApplePlatform platform) - { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name1", node); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name2", node); + var changes1 = new CodeChanges (BindingType.SmartEnum, "name1"); + var changes2 = new CodeChanges (BindingType.SmartEnum, "name2"); Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentBindingType (ApplePlatform platform) + [Fact] + public void CompareDifferentBindingType () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node); - var changes2 = new CodeChanges (BindingType.Unknown, "name", node); + var changes1 = new CodeChanges (BindingType.SmartEnum, "name"); + var changes2 = new CodeChanges (BindingType.Unknown, "name"); Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentSymbolDeclaration (ApplePlatform platform) + [Fact] + public void CompareDifferentAttributesLength () { - var node1 = GetSyntaxNode (platform); - var node2 = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node1); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node2); - Assert.False (comparer.Equals (changes1, changes2)); - } - - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentAttributesLength (ApplePlatform platform) - { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name"); + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { Attributes = [ new AttributeCodeChange ("name", ["arg1", "arg2"]) ] @@ -110,17 +39,15 @@ public void CompareDifferentAttributesLength (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentAttributes (ApplePlatform platform) + [Fact] + public void CompareDifferentAttributes () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { Attributes = [ new AttributeCodeChange ("name", ["arg1", "arg2"]) ], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { Attributes = [ new AttributeCodeChange ("name2", ["arg1", "arg2"]) ], @@ -128,13 +55,11 @@ public void CompareDifferentAttributes (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentMembersLength (ApplePlatform platform) + [Fact] + public void CompareDifferentMembersLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name"); + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [ new EnumMember ("name", []) ], @@ -142,17 +67,15 @@ public void CompareDifferentMembersLength (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentMembers (ApplePlatform platform) + [Fact] + public void CompareDifferentMembers () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [ new EnumMember ("name", []) ], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [ new EnumMember ("name2", []) ], @@ -160,13 +83,14 @@ public void CompareDifferentMembers (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentPropertyLength (ApplePlatform platform) + [Fact] + public void CompareDifferentPropertyLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { EnumMembers = [], Properties = [] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { + EnumMembers = [], + Properties = [] + }; + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -192,12 +116,10 @@ public void CompareDifferentPropertyLength (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareSamePropertiesDiffOrder (ApplePlatform platform) + [Fact] + public void CompareSamePropertiesDiffOrder () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -234,7 +156,7 @@ public void CompareSamePropertiesDiffOrder (ApplePlatform platform) ]), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -274,12 +196,10 @@ public void CompareSamePropertiesDiffOrder (ApplePlatform platform) Assert.True (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentProperties (ApplePlatform platform) + [Fact] + public void CompareDifferentProperties () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -316,7 +236,7 @@ public void CompareDifferentProperties (ApplePlatform platform) ]), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -356,12 +276,10 @@ public void CompareDifferentProperties (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentEventsLength (ApplePlatform platform) + [Fact] + public void CompareDifferentEventsLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -408,7 +326,7 @@ public void CompareDifferentEventsLength (ApplePlatform platform) ]), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -448,12 +366,10 @@ public void CompareDifferentEventsLength (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareSameEventsDiffOrder (ApplePlatform platform) + [Fact] + public void CompareSameEventsDiffOrder () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -509,7 +425,7 @@ public void CompareSameEventsDiffOrder (ApplePlatform platform) ]), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -569,12 +485,10 @@ public void CompareSameEventsDiffOrder (ApplePlatform platform) Assert.True (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentEvents (ApplePlatform platform) + [Fact] + public void CompareDifferentEvents () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -621,7 +535,7 @@ public void CompareDifferentEvents (ApplePlatform platform) ]), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -673,12 +587,10 @@ public void CompareDifferentEvents (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentMethodsLength (ApplePlatform platform) + [Fact] + public void CompareDifferentMethodsLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -763,7 +675,7 @@ public void CompareDifferentMethodsLength (ApplePlatform platform) ) ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -840,12 +752,10 @@ public void CompareDifferentMethodsLength (ApplePlatform platform) Assert.False (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareSameMethodsDiffOrder (ApplePlatform platform) + [Fact] + public void CompareSameMethodsDiffOrder () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -930,7 +840,7 @@ public void CompareSameMethodsDiffOrder (ApplePlatform platform) ) ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -1019,12 +929,10 @@ public void CompareSameMethodsDiffOrder (ApplePlatform platform) Assert.True (comparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentMethods (ApplePlatform platform) + [Fact] + public void CompareDifferentMethods () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -1094,7 +1002,7 @@ public void CompareDifferentMethods (ApplePlatform platform) ), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesEqualityComparerTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesEqualityComparerTests.cs index 80505742e48c..66686d90d764 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesEqualityComparerTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesEqualityComparerTests.cs @@ -11,98 +11,27 @@ namespace Microsoft.Macios.Generator.Tests.DataModel; public class CodeChangesEqualityComparerTests : BaseGeneratorTestClass { readonly CodeChangesEqualityComparer equalityComparer = new (); - // returns a node that matches the given node type from an example syntax tree - T GetSyntaxNode (ApplePlatform platform) where T : BaseTypeDeclarationSyntax + [Fact] + public void CompareDifferentFullyQualifiedSymbol () { - var attrsText = @" -using System; - -namespace ObjCBindings; -public class SimpleAttribute : Attribute { -} - -public class AttributeWithParams : Attribute { - public AttributeWithParams (string name, int value) { - } -} -"; - var inputText = @" -using System; -using Foundation; -using ObjCBindings; - -namespace AVFoundation; - -[SimpleAttribute, AttributeWithParams (""first"", 2)] -public class TestClass { - - [SimpleAttribute, AttributeWithParams (""first"", 2)] - public void SayHello () { - } -} - -[SimpleAttribute, AttributeWithParams (""first"", 2)] -public enum TestEnum { - [SimpleAttribute, AttributeWithParams (""first"", 2)] - First, -} - -[SimpleAttribute, AttributeWithParams (""first"", 2)] -public interface IInterface { - [SimpleAttribute, AttributeWithParams (""first"", 2)] - public void SayHello (); -} -"; - var (_, sourceTrees) = - CreateCompilation (nameof (CodeChangesEqualityComparerTests), platform, attrsText, inputText); - Assert.Equal (2, sourceTrees.Length); - // get the declarations we want to work with and the semantic model - var nodes = sourceTrees [1].GetRoot ().DescendantNodes ().ToArray (); - var declarationNode = nodes - .OfType () - .FirstOrDefault (); - Assert.NotNull (declarationNode); - return declarationNode; - } - - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentFullyQualifiedSymbol (ApplePlatform platform) - { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name1", node); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name2", node); - Assert.False (equalityComparer.Equals (changes1, changes2)); - } - - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentBindingType (ApplePlatform platform) - { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node); - var changes2 = new CodeChanges (BindingType.Unknown, "name", node); + var changes1 = new CodeChanges (BindingType.SmartEnum, "name1"); + var changes2 = new CodeChanges (BindingType.SmartEnum, "name2"); Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentSymbolDeclaration (ApplePlatform platform) + [Fact] + public void CompareDifferentBindingType () { - var node1 = GetSyntaxNode (platform); - var node2 = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node1); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node2); + var changes1 = new CodeChanges (BindingType.SmartEnum, "name"); + var changes2 = new CodeChanges (BindingType.Unknown, "name"); Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentAttributesLength (ApplePlatform platform) + [Fact] + public void CompareDifferentAttributesLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name"); + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { Attributes = [ new AttributeCodeChange ("name", ["arg1", "arg2"]) ] @@ -110,17 +39,15 @@ public void CompareDifferentAttributesLength (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentAttributes (ApplePlatform platform) + [Fact] + public void CompareDifferentAttributes () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { Attributes = [ new AttributeCodeChange ("name", ["arg1", "arg2"]) ], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { Attributes = [ new AttributeCodeChange ("name2", ["arg1", "arg2"]) ], @@ -128,13 +55,11 @@ public void CompareDifferentAttributes (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentMembersLength (ApplePlatform platform) + [Fact] + public void CompareDifferentMembersLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node); - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name"); + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [ new EnumMember ("name", []) ], @@ -142,17 +67,15 @@ public void CompareDifferentMembersLength (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentMembers (ApplePlatform platform) + [Fact] + public void CompareDifferentMembers () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [ new EnumMember ("name", []) ], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [ new EnumMember ("name2", []) ], @@ -160,13 +83,11 @@ public void CompareDifferentMembers (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentPropertyLength (ApplePlatform platform) + [Fact] + public void CompareDifferentPropertyLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { EnumMembers = [], Properties = [] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [] }; + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -192,12 +113,10 @@ public void CompareDifferentPropertyLength (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareSamePropertiesDiffOrder (ApplePlatform platform) + [Fact] + public void CompareSamePropertiesDiffOrder () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -234,7 +153,7 @@ public void CompareSamePropertiesDiffOrder (ApplePlatform platform) ]), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -274,12 +193,10 @@ public void CompareSamePropertiesDiffOrder (ApplePlatform platform) Assert.True (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentProperties (ApplePlatform platform) + [Fact] + public void CompareDifferentProperties () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -316,7 +233,7 @@ public void CompareDifferentProperties (ApplePlatform platform) ]), ] }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -356,12 +273,10 @@ public void CompareDifferentProperties (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentConstructorLength (ApplePlatform platform) + [Fact] + public void CompareDifferentConstructorLength () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -399,7 +314,7 @@ public void CompareDifferentConstructorLength (ApplePlatform platform) ], Constructors = [], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -442,12 +357,10 @@ public void CompareDifferentConstructorLength (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareDifferentConstructors (ApplePlatform platform) + [Fact] + public void CompareDifferentConstructors () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -487,7 +400,7 @@ public void CompareDifferentConstructors (ApplePlatform platform) new ("MyClass", [], [], []) ], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -535,12 +448,10 @@ public void CompareDifferentConstructors (ApplePlatform platform) Assert.False (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareSameConstructors (ApplePlatform platform) + [Fact] + public void CompareSameConstructors () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -586,7 +497,7 @@ public void CompareSameConstructors (ApplePlatform platform) ]) ], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -635,12 +546,10 @@ public void CompareSameConstructors (ApplePlatform platform) Assert.True (equalityComparer.Equals (changes1, changes2)); } - [Theory] - [AllSupportedPlatforms] - public void CompareSameConstructorsDiffOrder (ApplePlatform platform) + [Fact] + public void CompareSameConstructorsDiffOrder () { - var node = GetSyntaxNode (platform); - var changes1 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes1 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( @@ -686,7 +595,7 @@ public void CompareSameConstructorsDiffOrder (ApplePlatform platform) new ("MyClass", [], [], []), ], }; - var changes2 = new CodeChanges (BindingType.SmartEnum, "name", node) { + var changes2 = new CodeChanges (BindingType.SmartEnum, "name") { EnumMembers = [], Properties = [ new ( diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesTests.cs index 27be3141b0b7..e81d8dec7887 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/CodeChangesTests.cs @@ -160,7 +160,7 @@ public class TestClass { public void SkipPropertyDeclaration (ApplePlatform platform, string inputText, bool expected) { var (compilation, sourceTrees) = - CreateCompilation (nameof (SkipEnumValueDeclaration), platform, inputText); + CreateCompilation (nameof (SkipPropertyDeclaration), platform, inputText); Assert.Single (sourceTrees); // get the declarations we want to work with and the semantic model var node = sourceTrees [0].GetRoot () @@ -226,7 +226,7 @@ public class TestClass { public void SkipMethodDeclaration (ApplePlatform platform, string inputText, bool expected) { var (compilation, sourceTrees) = - CreateCompilation (nameof (SkipEnumValueDeclaration), platform, inputText); + CreateCompilation (nameof (SkipMethodDeclaration), platform, inputText); Assert.Single (sourceTrees); // get the declarations we want to work with and the semantic model var node = sourceTrees [0].GetRoot () diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumDeclarationCodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumDeclarationCodeChangesTests.cs index 7066dd90511d..db8f74f7655a 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumDeclarationCodeChangesTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/EnumDeclarationCodeChangesTests.cs @@ -46,7 +46,7 @@ public enum AVCaptureDeviceType { Assert.Single (codeChanges.Attributes); Assert.Equal (AttributesNames.BindingAttribute, codeChanges.Attributes [0].Name); Assert.Empty (codeChanges.EnumMembers); - Assert.True (codeChanges.SymbolDeclaration is EnumDeclarationSyntax); + Assert.Equal (BindingType.SmartEnum, codeChanges.BindingType); } [Theory] @@ -80,17 +80,17 @@ public enum AVCaptureDeviceType { Assert.Equal (BindingType.SmartEnum, codeChanges.BindingType); Assert.Single (codeChanges.Attributes); Assert.Equal (AttributesNames.BindingAttribute, codeChanges.Attributes [0].Name); - Assert.True (codeChanges.SymbolDeclaration is EnumDeclarationSyntax); + Assert.Equal (BindingType.SmartEnum, codeChanges.BindingType); // validate that we have the 3 members and their attrs Assert.Equal (3, codeChanges.EnumMembers.Length); Assert.Equal ("BuiltInMicrophone", codeChanges.EnumMembers [0].Name); var expectedFields = new [] { - "AVCaptureDeviceTypeBuiltInMicrophone", - "AVCaptureDeviceTypeBuiltInWideAngleCamera", + "AVCaptureDeviceTypeBuiltInMicrophone", "AVCaptureDeviceTypeBuiltInWideAngleCamera", "AVCaptureDeviceTypeBuiltInTelephotoCamera" }; for (var index = 0; index < expectedFields.Length; index++) { - Assert.Equal ("ObjCBindings.FieldAttribute", codeChanges.EnumMembers [index].Attributes [0].Name); + Assert.Equal ("ObjCBindings.FieldAttribute", + codeChanges.EnumMembers [index].Attributes [0].Name); Assert.Equal (expectedFields [index], codeChanges.EnumMembers [index].Attributes [0].Arguments [0]); } } @@ -124,7 +124,7 @@ public enum AVCaptureDeviceType { Assert.Single (codeChanges.Attributes); Assert.Equal (AttributesNames.BindingAttribute, codeChanges.Attributes [0].Name); Assert.Empty (codeChanges.EnumMembers); - Assert.True (codeChanges.SymbolDeclaration is EnumDeclarationSyntax); + Assert.Equal (BindingType.SmartEnum, codeChanges.BindingType); } @@ -159,16 +159,16 @@ public enum AVCaptureDeviceType { Assert.Equal (BindingType.SmartEnum, codeChanges.BindingType); Assert.Single (codeChanges.Attributes); Assert.Equal (AttributesNames.BindingAttribute, codeChanges.Attributes [0].Name); - Assert.True (codeChanges.SymbolDeclaration is EnumDeclarationSyntax); + Assert.Equal (BindingType.SmartEnum, codeChanges.BindingType); // validate that we have the 3 members and their attrs Assert.Equal (2, codeChanges.EnumMembers.Length); Assert.Equal ("BuiltInMicrophone", codeChanges.EnumMembers [0].Name); var expectedFields = new [] { - "AVCaptureDeviceTypeBuiltInMicrophone", - "AVCaptureDeviceTypeBuiltInWideAngleCamera", + "AVCaptureDeviceTypeBuiltInMicrophone", "AVCaptureDeviceTypeBuiltInWideAngleCamera", }; for (var index = 0; index < expectedFields.Length; index++) { - Assert.Equal ("ObjCBindings.FieldAttribute", codeChanges.EnumMembers [index].Attributes [0].Name); + Assert.Equal ("ObjCBindings.FieldAttribute", + codeChanges.EnumMembers [index].Attributes [0].Name); Assert.Equal (expectedFields [index], codeChanges.EnumMembers [index].Attributes [0].Arguments [0]); } } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs new file mode 100644 index 000000000000..f7ca09fe72bc --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs @@ -0,0 +1,442 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.DataModel; +using Xamarin.Tests; +using Xamarin.Utils; +using Xunit; + +namespace Microsoft.Macios.Generator.Tests.DataModel; + +public class InterfaceCodeChangesTests : BaseGeneratorTestClass { + readonly CodeChangesEqualityComparer comparer = new (); + + class TestDataCodeChangesFromClassDeclaration : IEnumerable { + public IEnumerator GetEnumerator () + { + const string emptyInterface = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { +} +"; + + yield return [ + emptyInterface, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ] + } + ]; + + const string singlePropertyInterface = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + [Export (""name"")] + public partial string Name { get; set; } = string.Empty; +} +"; + + yield return [ + singlePropertyInterface, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Properties = [ + new ( + name: "Name", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ) + ] + } + ]; + + const string multiPropertyInterfaceMissingExport = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + [Export (""name"")] + public partial string Name { get; set; } = string.Empty; + + public partial string Surname { get; set; } = string.Empty; +} +"; + + yield return [ + multiPropertyInterfaceMissingExport, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Properties = [ + new ( + name: "Name", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ) + ] + } + ]; + + const string multiPropertyInterface = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + [Export (""name"")] + public partial string Name { get; set; } = string.Empty; + + [Export (""surname"")] + public partial string Surname { get; set; } = string.Empty; +} +"; + + yield return [ + multiPropertyInterface, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Properties = [ + new ( + name: "Name", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["name"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ), + new ( + name: "Surname", + type: "string", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["surname"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + accessors: [ + new (AccessorKind.Getter, [], []), + new (AccessorKind.Setter, [], []), + ] + ), + ] + } + ]; + + const string singleMethodInterface = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + [Export (""withName:"")] + public partial void SetName (string inName); +} +"; + + yield return [ + singleMethodInterface, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Methods = [ + new ( + type: "NS.IProtocol", + name: "SetName", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withName:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inName") + ] + ), + ] + } + ]; + + const string multiMethodInterfaceMissingExport = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + [Export (""withName:"")] + public partial void SetName (string inName); + + public void SetSurname (string inSurname) {} +} +"; + + yield return [ + multiMethodInterfaceMissingExport, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Methods = [ + new ( + type: "NS.IProtocol", + name: "SetName", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withName:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inName") + ] + ), + ] + } + ]; + + + const string multiMethodInterface = @" +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + [Export (""withName:"")] + public partial void SetName (string inName); + + [Export (""withSurname:"")] + public partial void SetSurname (string inSurname); +} +"; + yield return [ + multiMethodInterface, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Methods = [ + new ( + type: "NS.IProtocol", + name: "SetName", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withName:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inName") + ] + ), + new ( + type: "NS.IProtocol", + name: "SetSurname", + returnType: "void", + attributes: [ + new ("ObjCBindings.ExportAttribute", ["withSurname:"]) + ], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + SyntaxFactory.Token (SyntaxKind.PartialKeyword), + ], + parameters: [ + new (0, "string", "inSurname") + ] + ), + ] + } + ]; + + const string singleEventInterface = @" +using System; +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + + public event EventHandler Changed { add; remove; } +} +"; + + yield return [ + singleEventInterface, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Events = [ + new ( + name: "Changed", + type: "System.EventHandler", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + ], + accessors: [ + new (AccessorKind.Add, [], []), + new (AccessorKind.Remove, [], []) + ]) + ], + } + ]; + + const string multiEventInterface = @" +using System; +using ObjCBindings; + +namespace NS; + +[BindingType] +public partial interface IProtocol { + + public event EventHandler Changed { add; remove; } + + public event EventHandler Removed { add; remove; } +} +"; + + yield return [ + multiEventInterface, + new CodeChanges ( + bindingType: BindingType.Protocol, + fullyQualifiedSymbol: "NS.IProtocol" + ) { + Attributes = [ + new ("ObjCBindings.BindingTypeAttribute") + ], + Events = [ + new ( + name: "Changed", + type: "System.EventHandler", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + ], + accessors: [ + new (AccessorKind.Add, [], []), + new (AccessorKind.Remove, [], []) + ] + ), + new ( + name: "Removed", + type: "System.EventHandler", + attributes: [], + modifiers: [ + SyntaxFactory.Token (SyntaxKind.PublicKeyword), + ], + accessors: [ + new (AccessorKind.Add, [], []), + new (AccessorKind.Remove, [], []) + ] + ), + ], + } + ]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + void CodeChangesFromInterfaceDeclaration (ApplePlatform platform, string inputText, CodeChanges expected) + { + var (compilation, sourceTrees) = + CreateCompilation (nameof (CodeChangesFromInterfaceDeclaration), platform, inputText); + Assert.Single (sourceTrees); + // get the declarations we want to work with and the semantic model + var node = sourceTrees [0].GetRoot () + .DescendantNodes () + .OfType () + .FirstOrDefault (); + Assert.NotNull (node); + var semanticModel = compilation.GetSemanticModel (sourceTrees [0]); + var changes = CodeChanges.FromDeclaration (node, semanticModel); + Assert.NotNull (changes); + Assert.Equal (expected, changes.Value, comparer); + } +}