diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs index 099c48ed6fd6..2a9ebfb29777 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs @@ -75,7 +75,7 @@ static ImmutableArray Validate (BindingValidator validator, RootC { var bucket = ImmutableArray.CreateBuilder (); if (declarationNode is T baseTypeDeclarationSyntax) { - var binding = Binding.FromDeclaration (baseTypeDeclarationSyntax, context); + var binding = Binding.FromDeclaration (baseTypeDeclarationSyntax, context, false); if (binding is null) { // add a diagnostic if the binding could not be created bucket.Add (Diagnostic.Create ( diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs index 7bc23d550f36..b1b4d6866671 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs @@ -947,5 +947,59 @@ internal static string RBI0032Title { return ResourceManager.GetString("RBI0032Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to A weak delegate has a duplicate strong delegate name.. + /// + internal static string RBI0033Description { + get { + return ResourceManager.GetString("RBI0033Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The weak delegate '{0}' strong delegate '{1}' is already used by '{2}'. + /// + internal static string RBI0033MessageFormat { + get { + return ResourceManager.GetString("RBI0033MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wrong strong delegate name. + /// + internal static string RBI0033Title { + get { + return ResourceManager.GetString("RBI0033Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A selector is used in more than one symbol.. + /// + internal static string RBI0034Description { + get { + return ResourceManager.GetString("RBI0034Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The selector '{0}' used by '{1}' is already used by '{2}'. + /// + internal static string RBI0034MessageFormat { + get { + return ResourceManager.GetString("RBI0034MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate selector. + /// + internal static string RBI0034Title { + get { + return ResourceManager.GetString("RBI0034Title", resourceCulture); + } + } } } diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx index 6ec693df445d..24ade23e36c2 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx @@ -443,4 +443,30 @@ Wrong weak delegate + + + + A weak delegate has a duplicate strong delegate name. + + + The weak delegate '{0}' strong delegate '{1}' is already used by '{2}' + {0} is the name of the property {1} is the name of the strong delegate and {2} is the name of the other property. + + + Wrong strong delegate name + + + + + + A selector is used in more than one symbol. + + + The selector '{0}' used by '{1}' is already used by '{2}' + {0} is the selector {1} is the name of the duplicate symbol and {2} is the name of the first user of the selector. + + + Duplicate selector + + diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs index ded22afc4295..1dd764340f8e 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs @@ -446,7 +446,7 @@ public static class RgenDiagnostics { new LocalizableResourceString (nameof (Resources.RBI0029MessageFormat), Resources.ResourceManager, typeof (Resources)), "Usage", - DiagnosticSeverity.Warning, + DiagnosticSeverity.Error, isEnabledByDefault: true, description: new LocalizableResourceString (nameof (Resources.RBI0029Description), Resources.ResourceManager, typeof (Resources)) @@ -461,7 +461,7 @@ public static class RgenDiagnostics { new LocalizableResourceString (nameof (Resources.RBI0030MessageFormat), Resources.ResourceManager, typeof (Resources)), "Usage", - DiagnosticSeverity.Warning, + DiagnosticSeverity.Error, isEnabledByDefault: true, description: new LocalizableResourceString (nameof (Resources.RBI0030Description), Resources.ResourceManager, typeof (Resources)) @@ -476,7 +476,7 @@ public static class RgenDiagnostics { new LocalizableResourceString (nameof (Resources.RBI0031MessageFormat), Resources.ResourceManager, typeof (Resources)), "Usage", - DiagnosticSeverity.Warning, + DiagnosticSeverity.Error, isEnabledByDefault: true, description: new LocalizableResourceString (nameof (Resources.RBI0031Description), Resources.ResourceManager, typeof (Resources)) @@ -496,4 +496,34 @@ public static class RgenDiagnostics { description: new LocalizableResourceString (nameof (Resources.RBI0032Description), Resources.ResourceManager, typeof (Resources)) ); + + /// + /// Diagnostic descriptor for when a weak delegate strong delegate is duplicated. + /// + internal static readonly DiagnosticDescriptor RBI0033 = new ( + "RBI0033", + new LocalizableResourceString (nameof (Resources.RBI0033Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0033MessageFormat), Resources.ResourceManager, + typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0033Description), Resources.ResourceManager, + typeof (Resources)) + ); + + /// + /// Diagnostic descriptor for when a selector is used in more than one symbol. + /// + internal static readonly DiagnosticDescriptor RBI0034 = new ( + "RBI0034", + new LocalizableResourceString (nameof (Resources.RBI0034Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0034MessageFormat), Resources.ResourceManager, + typeof (Resources)), + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0034Description), Resources.ResourceManager, + typeof (Resources)) + ); } diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validator.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validator.cs index 3ce4227ace31..f9fe6d98e31d 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validator.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validator.cs @@ -23,6 +23,11 @@ interface IValidator { /// The root context for validation. /// A dictionary where the key is the name of the invalid field and the value is a list of diagnostics. Dictionary> ValidateAll (object data, RootContext context); + + /// + /// Gets all the diagnostic descriptors that this validator and its nested validators can produce. + /// + ImmutableArray Descriptors { get; } } /// @@ -61,6 +66,11 @@ public ImmutableArray Descriptors { } } + // add the nested validators + foreach (var (_, validator) in nestedValidators) { + allDescriptors.UnionWith (validator.Descriptors); + } + return [.. allDescriptors]; } } diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/ClassValidator.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/ClassValidator.cs index f4aa5c98563d..cb9bf1a92aed 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/ClassValidator.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/ClassValidator.cs @@ -1,7 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.Macios.Generator; +using Microsoft.Macios.Generator.Context; +using Microsoft.Macios.Generator.DataModel; +using static Microsoft.Macios.Generator.RgenDiagnostics; namespace Microsoft.Macios.Bindings.Analyzer.Validators; @@ -9,12 +17,170 @@ namespace Microsoft.Macios.Bindings.Analyzer.Validators; /// Validator for class bindings. /// sealed class ClassValidator : BindingValidator { + + readonly ArrayValidator propertiesValidator = new (new PropertyOrFieldValidator ()); + + /// + /// Validates that strong delegate names are unique across all properties. + /// + /// The properties to validate. + /// The root context for validation. + /// When this method returns, contains diagnostics for any duplicate strong delegate names; otherwise, an empty array. + /// The code location to be used for the diagnostics. + /// true if all strong delegate names are unique; otherwise, false. + bool StrongDelegatesAreUnique (ImmutableArray properties, RootContext context, + out ImmutableArray diagnostics, Location? location = null) + { + diagnostics = ImmutableArray.Empty; + // use a dictionary to track all the strong names and the properties that use them + var strongNames = new Dictionary> (); + foreach (var p in properties) { + var strongDelegate = p.ToStrongDelegate (); + if (strongNames.TryGetValue (strongDelegate.Name, out var list)) { + list.Add (p); + } else { + // add list with the current property since we want to use is as a ref + strongNames.Add (strongDelegate.Name, [p]); + } + } + // get all the strong names that have more than one property using them + var duplicates = strongNames.Where (x => x.Value.Count > 1).ToImmutableArray (); + if (duplicates.Length == 0) { + // no duplicates, we are good + return true; + } + // build the diagnostics + var builder = ImmutableArray.CreateBuilder (); + foreach (var duplicate in duplicates) { + // add a diagnostic for each duplicate strong delegate using the first one as a reference and the second + // one as the location of the error. We use the first one as a reference because we have to choose one and + // is the one on top of the file + var firstProperty = duplicate.Value.First (); + for (var index = 1; index < duplicate.Value.Count; index++) { + var dupProperty = duplicate.Value [index]; // used for the msg and the location + builder.Add (Diagnostic.Create ( + descriptor: RBI0033, + location: dupProperty.Location, + messageArgs: [ + dupProperty.Name, + duplicate.Key, + firstProperty.Name + ])); + } + } + diagnostics = builder.ToImmutable (); + return diagnostics.Length == 0; + } + + /// + /// Validates that selectors are unique across all properties and methods in a binding. + /// + /// The binding to validate. + /// The root context for validation. + /// When this method returns, contains diagnostics for any duplicate selectors; otherwise, an empty array. + /// The code location to be used for the diagnostics. + /// true if all selectors are unique; otherwise, false. + bool SelectorsAreUnique (Binding binding, RootContext context, + out ImmutableArray diagnostics, Location? location = null) + { + diagnostics = ImmutableArray.Empty; + var builder = ImmutableArray.CreateBuilder (); + + // the logic is as follows: + // 1. Collect all selectors that we have decided to register. Those are the ones in properties and methods that + // do not have the SkipRegister attribute. + // 2. Collect the selectors based on them being static or instance selectors. We can have the same selector + // for static and instance methods, but not for two static or two instance methods. + + var instanceSelectors = new Dictionary> (); + var staticSelectors = new Dictionary> (); + // collect property selectors + foreach (var property in binding.Properties) { + if (string.IsNullOrEmpty (property.Selector)) + continue; + if (property.SkipRegistration) + // user has decided to skip registration for this property, so we don't need to validate it + continue; + // decide which dictionary to use based on the property being static or instance + var selectors = property.IsStatic ? staticSelectors : instanceSelectors; + if (selectors.TryGetValue (property.Selector, out var list)) { + list.Add ((property.Name, property.Location)); + } else { + // add a new list with the current property + selectors.Add (property.Selector, [(property.Name, property.Location)]); + } + } + + // collect method selectors + foreach (var method in binding.Methods) { + if (string.IsNullOrEmpty (method.Selector)) + continue; + if (method.SkipRegistration) + // user has decided to skip registration for this method, so we don't need to validate it + continue; + var selectors = method.IsStatic ? staticSelectors : instanceSelectors; + if (selectors.TryGetValue (method.Selector, out var list)) { + list.Add ((method.Name, method.Location)); + } else { + // add a new list with the current property + selectors.Add (method.Selector, [(method.Name, method.Location)]); + } + } + // get all the selectors that have more than one property or method + var instanceDuplicates = instanceSelectors.Where (x => x.Value.Count > 1).ToImmutableArray (); + var staticDuplicates = staticSelectors.Where (x => x.Value.Count > 1).ToImmutableArray (); + + if (instanceDuplicates.Length == 0 && staticDuplicates.Length == 0) { + // no duplicates, we are good + return true; + } + // loop over each of the duplicates and create diagnostics for them, we do this separately for instance and + // static selectors to make it easier to read the code and to avoid mixing selectors and getting confused about + // which one is which. + BuildDiagnostics (instanceDuplicates, builder); + BuildDiagnostics (staticDuplicates, builder); + + diagnostics = builder.ToImmutable (); + return diagnostics.Length == 0; + + void BuildDiagnostics (ImmutableArray>> keyValuePairs, + ImmutableArray.Builder builder1) + { + foreach (var duplicate in keyValuePairs) { + var firstSymbol = duplicate.Value.First (); + for (var index = 1; index < duplicate.Value.Count; index++) { + var dupSymbol = duplicate.Value [index]; // used for the msg and the location + builder1.Add (Diagnostic.Create ( + descriptor: RBI0034, + location: dupSymbol.Location, + messageArgs: [ + duplicate.Key, + dupSymbol.SymbolName, + firstSymbol.SymbolName + ])); + } + } + } + } + /// /// Initializes a new instance of the class. /// public ClassValidator () { // class bindings must be partial - AddGlobalStrategy (RgenDiagnostics.RBI0001, IsPartial); + AddGlobalStrategy (RBI0001, IsPartial); + + // use a nested validator to validate the properties and fields individually + AddNestedValidator (b => b.Properties, propertiesValidator); + + // validate that the selectors are not duplicated, this includes properties and methods + AddGlobalStrategy ([RBI0034], SelectorsAreUnique); + + // validate that strong delegates are not duplicated, this is only for weak properties + AddStrategy ( + b => b.Properties.Where (p => p.IsWeakDelegate).ToImmutableArray (), + [RBI0033], + StrongDelegatesAreUnique, "WeakDelegates"); } } diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.Generator.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.Generator.cs index 7b0689c5034d..2cfd5af2dc08 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.Generator.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.Generator.cs @@ -277,7 +277,8 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray @ /// /// The enum declaration that triggered the change. /// The root binding context of the current compilation. - Binding (EnumDeclarationSyntax enumDeclaration, RootContext context) + /// If the struct should validate the members from the declarations. Defaults to true. + Binding (EnumDeclarationSyntax enumDeclaration, RootContext context, bool validateMembers = true) { context.SemanticModel.GetSymbolData ( declaration: enumDeclaration, @@ -341,7 +342,8 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray @ /// /// The class declaration that triggered the change. /// The root binding context of the current compilation. - Binding (ClassDeclarationSyntax classDeclaration, RootContext context) + /// If the struct should validate the members from the declarations. Defaults to true. + Binding (ClassDeclarationSyntax classDeclaration, RootContext context, bool validateMembers = true) { context.SemanticModel.GetSymbolData ( declaration: classDeclaration, @@ -361,19 +363,19 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray @ // 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, context, Skip, - Constructor.TryCreate, out constructors); - GetMembers (classDeclaration, context, Skip, Event.TryCreate, out events); + Constructor.TryCreate, out constructors, validateMembers); + GetMembers (classDeclaration, context, Skip, Event.TryCreate, out events, validateMembers); GetMembers (classDeclaration, context, Skip, Method.TryCreate, - out methods); + out methods, validateMembers); // if an only if the class declaration is a strong dictionary we will retrieve strong dictionary properties, else // we will retrieve the properties as normal properties. if (bindingInfo.BindingType == BindingType.StrongDictionary) { GetMembers (classDeclaration, context, StrongDictionarySkip, Property.TryCreate, - out strongDictproperties); + out strongDictproperties, validateMembers); } else { GetMembers (classDeclaration, context, PropertySkip, Property.TryCreate, - out properties); + out properties, validateMembers); } Location = classDeclaration.GetLocation (); } @@ -383,7 +385,8 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray @ /// /// The interface declaration that triggered the change. /// The root binding context of the current compilation. - Binding (InterfaceDeclarationSyntax interfaceDeclaration, RootContext context) + /// If the struct should validate the members from the declarations. Defaults to true. + Binding (InterfaceDeclarationSyntax interfaceDeclaration, RootContext context, bool validateMembers = true) { context.SemanticModel.GetSymbolData ( declaration: interfaceDeclaration, @@ -402,11 +405,11 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray @ // we do not init the constructors, we use the default empty array GetMembers (interfaceDeclaration, context.SemanticModel, PropertySkip, Property.TryCreate, - out properties); + out properties, validateMembers); GetMembers (interfaceDeclaration, context.SemanticModel, Skip, Event.TryCreate, - out events); + out events, validateMembers); GetMembers (interfaceDeclaration, context.SemanticModel, Skip, Method.TryCreate, - out methods); + out methods, validateMembers); // models are a special case, we need to be able to retrieve the parent properties and methods to be added to the // wrapper classes. We will do that by accessing the parents, getting their symbol info and creating the @@ -444,14 +447,14 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray @ /// /// The declaration syntax whose change we want to calculate. /// The root binding context of the current compilation. + /// If the struct should validate the members from the declarations. Defaults to true. /// A code change or null if it could not be calculated. public static Binding? FromDeclaration (BaseTypeDeclarationSyntax baseTypeDeclarationSyntax, - RootContext context) + RootContext context, bool validateMembers = true) => baseTypeDeclarationSyntax switch { - EnumDeclarationSyntax enumDeclarationSyntax => new Binding (enumDeclarationSyntax, context), - InterfaceDeclarationSyntax interfaceDeclarationSyntax => new Binding (interfaceDeclarationSyntax, - context), - ClassDeclarationSyntax classDeclarationSyntax => new Binding (classDeclarationSyntax, context), + EnumDeclarationSyntax enumDeclarationSyntax => new Binding (enumDeclarationSyntax, context, validateMembers), + InterfaceDeclarationSyntax interfaceDeclarationSyntax => new Binding (interfaceDeclarationSyntax, context, validateMembers), + ClassDeclarationSyntax classDeclarationSyntax => new Binding (classDeclarationSyntax, context, validateMembers), _ => null }; diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.cs index c98d3228a40b..9fc2b823072f 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Binding.cs @@ -315,14 +315,14 @@ delegate bool TryCreateDelegate (T declaration, RootContext context, where TR : struct; static void GetMembers (TypeDeclarationSyntax baseDeclarationSyntax, RootContext context, - SkipDelegate skip, TryCreateDelegate tryCreate, out ImmutableArray members) + SkipDelegate skip, TryCreateDelegate tryCreate, out ImmutableArray members, bool validateMembers) where T : MemberDeclarationSyntax where TR : struct { var bucket = ImmutableArray.CreateBuilder (); var declarations = baseDeclarationSyntax.Members.OfType (); foreach (var declaration in declarations) { - if (skip (declaration, context.SemanticModel)) + if (validateMembers && skip (declaration, context.SemanticModel)) continue; if (tryCreate (declaration, context, out var change)) bucket.Add (change.Value); diff --git a/src/rgen/Microsoft.Macios.Transformer/DataModel/Binding.Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/DataModel/Binding.Transformer.cs index b18734773df6..8c5a31541bca 100644 --- a/src/rgen/Microsoft.Macios.Transformer/DataModel/Binding.Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/DataModel/Binding.Transformer.cs @@ -265,15 +265,15 @@ internal Binding (InterfaceDeclarationSyntax interfaceDeclarationSyntax, INamedT // strong dictionaries are a little different, we will get all the properties with no filter since the // properties from a strong dictionary do not have any attribute GetMembers (interfaceDeclarationSyntax, context, static (_, _) => false, Property.TryCreate, - out properties); + out properties, true); } else { GetMembers (interfaceDeclarationSyntax, context, Skip, Property.TryCreate, - out properties); + out properties, true); } // methods are a little diff, in the old SDK style, both methods and constructors are methods, we will get // all exported methods and then filter accordingly GetMembers (interfaceDeclarationSyntax, context, Skip, Method.TryCreate, - out ImmutableArray allMethods); + out ImmutableArray allMethods, true); methods = [.. allMethods.Where (m => !m.IsConstructor)]; constructors = allMethods.Where (m => m.IsConstructor) .Select (m => m.ToConstructor ()) diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/ClassAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/ClassAnalyzerTests.cs new file mode 100644 index 000000000000..da597e79a4ca --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/ClassAnalyzerTests.cs @@ -0,0 +1,823 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Xamarin.Tests; +using Xamarin.Utils; +using Xunit; + +namespace Microsoft.Macios.Bindings.Analyzer.Tests; + +public class ClassAnalyzerTests : BaseGeneratorWithAnalyzerTestClass { + + class TestDataClassAnalyzerWarnings : IEnumerable { + public IEnumerator GetEnumerator () + { + // not partial class + /* + yield return [ +@" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public class TestClass{ +}", + "RBI0001", + DiagnosticSeverity.Error, + "The binding type 'TestNamespace.TestClass' must be declared partial" + ]; + */ + + // duplicate selector, 2 properties + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint Count { get; set; } + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint SecondCount { get; set; } +}", + "RBI0034", + DiagnosticSeverity.Warning, + "The selector 'count' used by 'SecondCount' is already used by 'Count'" + ]; + + // duplicate selector, 2 static properties + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public static virtual partial nuint Count { get; set; } + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public static virtual partial nuint SecondCount { get; set; } +}", + "RBI0034", + DiagnosticSeverity.Warning, + "The selector 'count' used by 'SecondCount' is already used by 'Count'" + ]; + + // duplicate selector, property and method + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint Count { get; set; } + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint GetCount (); +}", + "RBI0034", + DiagnosticSeverity.Warning, + "The selector 'count' used by 'GetCount' is already used by 'Count'" + ]; + + // duplicate selector, 2 methods + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint GetCount (); + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint SecondGetCount (); +}", + "RBI0034", + DiagnosticSeverity.Warning, + "The selector 'count' used by 'SecondGetCount' is already used by 'GetCount'" + ]; + + // duplicate selector, 2 static methods + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public static virtual partial nuint GetCount (); + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public static virtual partial nuint SecondGetCount (); +}", + "RBI0034", + DiagnosticSeverity.Warning, + "The selector 'count' used by 'SecondGetCount' is already used by 'GetCount'" + ]; + + // duplicate strong delegate from removing Weak and setting the strong name + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""delegate"", + ArgumentSemantic.Weak, + Flags = Property.WeakDelegate, + StrongDelegateType = typeof (INSUserActivityDelegate))] + public virtual partial NSObject? WeakDelegate { get; set; } + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""delegateSecond"", + ArgumentSemantic.Weak, + Flags = Property.WeakDelegate, + StrongDelegateType = typeof (INSUserActivityDelegate), + StrongDelegateName = ""Delegate"" + )] + public virtual partial NSObject? WeakSecondDelegate { get; set; } +}", + "RBI0033", + DiagnosticSeverity.Error, + "The weak delegate 'WeakSecondDelegate' strong delegate 'Delegate' is already used by 'WeakDelegate'" + ]; + + // duplicate strong delegate from both setting the strong name + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""delegate"", + ArgumentSemantic.Weak, + Flags = Property.WeakDelegate, + StrongDelegateType = typeof (INSUserActivityDelegate) + StrongDelegateName = ""Delegate"" + )] + public virtual partial NSObject? OtherWeakDelegate { get; set; } + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""delegateSecond"", + ArgumentSemantic.Weak, + Flags = Property.WeakDelegate, + StrongDelegateType = typeof (INSUserActivityDelegate), + StrongDelegateName = ""Delegate"" + )] + public virtual partial NSObject? WeakSecondDelegate { get; set; } +}", + "RBI0033", + DiagnosticSeverity.Error, + "The weak delegate 'WeakSecondDelegate' strong delegate 'Delegate' is already used by 'OtherWeakDelegate'" + ]; + + // empty field selector + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + [Field ("""")] + public static partial int FormatRGBA16Int { get; } +}", + "RBI0018", + DiagnosticSeverity.Error, + "An export property selector must not contain any whitespace" + ]; + + // field selector with space + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + [Field (""my field"")] + public static partial int FormatRGBA16Int { get; } +}", + "RBI0019", + DiagnosticSeverity.Error, + "An export property selector must not contain any whitespace" + ]; + + // not static field + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + [Field (""FormatRGBA16Int"")] + public partial int FormatRGBA16Int { get; } +}", + "RBI0030", + DiagnosticSeverity.Error, + "Field properties must be declared static" + ]; + + // not static field + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + [Field (""FormatRGBA16Int"")] + public static int FormatRGBA16Int { get; } +}", + "RBI0031", + DiagnosticSeverity.Error, + "Exported properties must be declared partial" + ]; + + // not partial property + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""attributedStringByInflectingString"")] + public virtual NSAttributedString AttributedStringByInflectingString { get; set; } +}", + "RBI0031", + DiagnosticSeverity.Error, + "Exported properties must be declared partial" + ]; + + // property invalid selector empty + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export ("""")] + public virtual partial NSAttributedString AttributedStringByInflectingString { get; set; } +}", + "RBI0018", + DiagnosticSeverity.Error, + "An export property selector must not contain any whitespace" + ]; + + // property invalid selector has space + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""attributedStringByInflectingString "")] + public virtual partial NSAttributedString AttributedStringByInflectingString { get; set; } +}", + "RBI0019", + DiagnosticSeverity.Error, + "An export property selector must not contain any whitespace" + ]; + + // property invalid selector has extra ':' + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""attributedStringByInflectingString"")] + public virtual partial NSAttributedString AttributedStringByInflectingString { + [Export (""isAttributedStringByInflectingString:"")] + get; + set; + } +}", + "RBI0029", + DiagnosticSeverity.Error, + "There is a mismatch between the arguments of 'AttributedStringByInflectingString' (found 0) and the selector 'isAttributedStringByInflectingString:' (found 1)" + ]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + public async Task ClassAnalyzerWarnings (ApplePlatform platform, string inputText, string diagnosticId, DiagnosticSeverity severity, string diagnosticMessage) + { + var (compilation, _) = CreateCompilation (platform, sources: inputText); + var diagnostics = await RunAnalyzer (new BindingTypeSemanticAnalyzer (), compilation); + + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == diagnosticId).ToArray (); + Assert.Single (analyzerDiagnotics); + VerifyDiagnosticMessage (analyzerDiagnotics [0], diagnosticId, + severity, diagnosticMessage); + } + + class TestDataClassAnalyzerSuccess : IEnumerable { + public IEnumerator GetEnumerator () + { + // duplicate selector, 2 properties, one is skipped from registration + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint Count { get; set; } + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"", Flags = Property.SkipRegistration)] + public virtual partial nuint SecondCount { get; set; } +}", + "RBI0034", + ]; + + // duplicate selector, 2 properties, one is static the other is instance + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public static virtual partial nuint Count { get; set; } + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"", Flags = Property.SkipRegistration)] + public virtual partial nuint SecondCount { get; set; } +}", + "RBI0034", + ]; + + // duplicate selector, 2 methods one is static the other is instance + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public static virtual partial nuint GetCount (); + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint SecondGetCount (); +}", + "RBI0034", + ]; + + // duplicate selector, 2 methods one is skipped + yield return [ + @" +#pragma warning disable APL0003 + +using System; +using System.Runtime.Versioning; +using AVFoundation; +using CoreGraphics; +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using nfloat = System.Runtime.InteropServices.NFloat; + +namespace TestNamespace; + +[SupportedOSPlatform (""macos"")] +[SupportedOSPlatform (""ios"")] +[SupportedOSPlatform (""tvos"")] +[SupportedOSPlatform (""maccatalyst13.1"")] +[BindingType] +public partial class TestClass{ + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"", Flags = Method.SkipRegistration)] + public virtual partial nuint GetCount (); + + [SupportedOSPlatform (""ios"")] + [SupportedOSPlatform (""tvos"")] + [SupportedOSPlatform (""macos"")] + [SupportedOSPlatform (""maccatalyst13.1"")] + [Export (""count"")] + public virtual partial nuint SecondGetCount (); +}", + "RBI0034", + ]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + public async Task ClassAnalyzerSuccess (ApplePlatform platform, string inputText, string diagnosticId) + { + var (compilation, _) = CreateCompilation (platform, sources: inputText); + var diagnostics = await RunAnalyzer (new BindingTypeSemanticAnalyzer (), compilation); + + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == diagnosticId).ToArray (); + // ensure that the error is not present + Assert.Empty (analyzerDiagnotics); + } +}