From fa0c0eea03110498565e6cb674707397b6e4db3f Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Fri, 18 Oct 2024 13:48:15 -0400 Subject: [PATCH] Implement the analyzer for smart enums. --- .../Microsoft.Macios.Binding.Common.csproj | 27 ++ .../BindingTypeCodeFixProvider.cs | 2 +- .../BindingTypeSemanticAnalyzer.cs | 64 ++--- .../DiagnosticInfo.cs | 17 ++ .../IBindingTypeAnalyzerExtensions.cs | 42 +++ .../Extensions/PlatformNameExtensions.cs | 16 ++ .../IBindingTypeAnalyzer.cs | 12 + .../Microsoft.Macios.Bindings.Analyzer.csproj | 20 ++ .../Resources.Designer.cs | 80 ++++-- .../Resources.resx | 52 ++++ .../SmartEnumSemanticAnalyzer.cs | 142 ++++++++++ .../Attributes/FieldData.cs | 22 +- .../AttributesNames.cs | 4 +- .../BindingSourceGeneratorGenerator.cs | 18 +- .../Context/RootBindingContext.cs | 23 +- .../Context/SymbolBindingContext.cs | 1 + .../Emitters/ClassEmitter.cs | 3 + .../Emitters/EnumEmitter.cs | 2 + .../Emitters/ICodeEmitter.cs | 2 + .../Emitters/InterfaceEmitter.cs | 2 + .../Extensions/CompilationExtensions.cs | 26 ++ .../Microsoft.Macios.Generator.csproj | 8 +- src/rgen/rgen.sln | 6 + .../BindingTypeSemanticAnalyzerTests.cs | 4 +- .../SmartEnumSemanticAnalyzerTests.cs | 255 ++++++++++++++++++ .../BaseGeneratorTestClass.cs | 3 +- .../BindingSourceGeneratorGeneratorTests.cs | 2 +- .../Microsoft.Macios.Generator.Tests.csproj | 10 +- .../Data/ExpectedAVCaptureDeviceTypeEnum.cs | 4 +- .../ExpectedAVCaptureSystemPressureLevel.cs | 3 +- .../Data/ExpectedCustomLibraryEnum.cs | 6 +- .../ExpectedMacOSAVMediaCharacteristics.cs | 6 +- .../Data/ExpectediOSAVMediaCharacteristics.cs | 6 +- 33 files changed, 773 insertions(+), 117 deletions(-) create mode 100644 src/rgen/Microsoft.Macios.Binding.Common/Microsoft.Macios.Binding.Common.csproj create mode 100644 src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs create mode 100644 src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/IBindingTypeAnalyzerExtensions.cs create mode 100644 src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/PlatformNameExtensions.cs create mode 100644 src/rgen/Microsoft.Macios.Bindings.Analyzer/IBindingTypeAnalyzer.cs create mode 100644 src/rgen/Microsoft.Macios.Bindings.Analyzer/SmartEnumSemanticAnalyzer.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs create mode 100644 tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/SmartEnumSemanticAnalyzerTests.cs diff --git a/src/rgen/Microsoft.Macios.Binding.Common/Microsoft.Macios.Binding.Common.csproj b/src/rgen/Microsoft.Macios.Binding.Common/Microsoft.Macios.Binding.Common.csproj new file mode 100644 index 00000000000..1698ed678a9 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Binding.Common/Microsoft.Macios.Binding.Common.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + + + + external\ApplePlatform.cs + + + external\SdkVersions.cs + + + external\TargetFramework.cs + + + external\Frameworks.cs + + + external\PlatformName.cs + + + + diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs index 17b8a298360..036b08041c1 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs @@ -20,7 +20,7 @@ namespace Microsoft.Macios.Bindings.Analyzer; public class BindingTypeCodeFixProvider : CodeFixProvider { // Specify the diagnostic IDs of analyzers that are expected to be linked. public sealed override ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create (BindingTypeSemanticAnalyzer.DiagnosticId); + ImmutableArray.Create (BindingTypeSemanticAnalyzer.RBI0001.Id); // If you don't need the 'fix all' behaviour, return null. public override FixAllProvider? GetFixAllProvider () => null; diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs index fd5df71a0e8..2e535572d9f 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Macios.Bindings.Analyzer.Extensions; namespace Microsoft.Macios.Bindings.Analyzer; @@ -12,23 +13,20 @@ namespace Microsoft.Macios.Bindings.Analyzer; /// pattern. /// [DiagnosticAnalyzer (LanguageNames.CSharp)] -public class BindingTypeSemanticAnalyzer : DiagnosticAnalyzer { - internal const string DiagnosticId = "RBI0001"; - static readonly LocalizableString Title = new LocalizableResourceString (nameof (Resources.RBI0001Title), - Resources.ResourceManager, typeof (Resources)); - static readonly LocalizableString MessageFormat = - new LocalizableResourceString (nameof (Resources.RBI0001MessageFormat), Resources.ResourceManager, - typeof (Resources)); - static readonly LocalizableString Description = - new LocalizableResourceString (nameof (Resources.RBI0001Description), Resources.ResourceManager, - typeof (Resources)); - const string Category = "Usage"; +public class BindingTypeSemanticAnalyzer : DiagnosticAnalyzer, IBindingTypeAnalyzer { - static readonly DiagnosticDescriptor RBI0001 = new (DiagnosticId, Title, MessageFormat, Category, - DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create (RBI0001); + internal static readonly DiagnosticDescriptor RBI0001 = new ( + "RBI0001", + new LocalizableResourceString (nameof (Resources.RBI0001Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0001MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0001Description), Resources.ResourceManager, typeof (Resources)) + ); + + public override ImmutableArray SupportedDiagnostics { get; } = [RBI0001]; public override void Initialize (AnalysisContext context) { @@ -38,35 +36,17 @@ public override void Initialize (AnalysisContext context) } void AnalysisContext (SyntaxNodeAnalysisContext context) - { - // only care about classes - if (context.Node is not ClassDeclarationSyntax classDeclarationNode) - return; + => this.AnalyzeBindingType (context); - var classSymbol = context.SemanticModel.GetDeclaredSymbol (classDeclarationNode); - if (classSymbol is null) - return; + public ImmutableArray Analyze (PlatformName _, ClassDeclarationSyntax declarationNode, INamedTypeSymbol symbol) + { + if (declarationNode.Modifiers.Any (x => x.IsKind (SyntaxKind.PartialKeyword))) + return []; - var boundAttributes = classSymbol.GetAttributes (); - if (boundAttributes.Length == 0) { - return; - } + var diagnostic = Diagnostic.Create (RBI0001, + declarationNode.Identifier.GetLocation (), // point to where the 'class' keyword is used + symbol.ToDisplayString ()); + return [diagnostic]; - // the c# syntax is a a list of lists of attributes. That is why we need to iterate through the list of lists - foreach (var attributeData in boundAttributes) { - // based on the type use the correct parser to retrieve the data - var attributeType = attributeData.AttributeClass?.ToDisplayString (); - switch (attributeType) { - case "ObjCBindings.BindingTypeAttribute": - // validate that the class is partial, else we need to report an error - if (!classDeclarationNode.Modifiers.Any (x => x.IsKind (SyntaxKind.PartialKeyword))) { - var diagnostic = Diagnostic.Create (RBI0001, - classDeclarationNode.Identifier.GetLocation (), // point to where the 'class' keyword is used - classSymbol.ToDisplayString ()); - context.ReportDiagnostic (diagnostic); - } - break; - } - } } } diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs new file mode 100644 index 00000000000..95e9f8c3ab6 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Bindings.Analyzer; + +public struct DiagnosticInfo ( + string id, + LocalizableResourceString title, + LocalizableResourceString messageFormat, + LocalizableResourceString description, + string category) { + + public string Id { get; } = id; + public LocalizableResourceString Title { get; } = title; + public LocalizableResourceString MessageFormat { get; } = messageFormat; + public LocalizableResourceString Description { get; } = description; + public string Category { get; } = category; +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/IBindingTypeAnalyzerExtensions.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/IBindingTypeAnalyzerExtensions.cs new file mode 100644 index 00000000000..a08eb31c692 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/IBindingTypeAnalyzerExtensions.cs @@ -0,0 +1,42 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Macios.Generator; +using Microsoft.Macios.Generator.Extensions; +using Diagnostic = Microsoft.CodeAnalysis.Diagnostic; + +namespace Microsoft.Macios.Bindings.Analyzer.Extensions; + +public static class BindingTypeAnalyzerExtensions { + public static void AnalyzeBindingType (this IBindingTypeAnalyzer self, SyntaxNodeAnalysisContext context) where T : BaseTypeDeclarationSyntax + { + // calculate the current compilation platform name + if (context.Node is not T declarationNode) + return; + + var declaredSymbol = context.SemanticModel.GetDeclaredSymbol (declarationNode); + if (declaredSymbol is null) + return; + + var boundAttributes = declaredSymbol.GetAttributes (); + if (boundAttributes.Length == 0) { + // do nothing since our generator only cares about declared types with the BindingType attribute + return; + } + + // the c# syntax is a a list of lists of attributes. That is why we need to iterate through the list of lists + foreach (var attributeData in boundAttributes) { + // based on the type use the correct parser to retrieve the data + var attributeType = attributeData.AttributeClass?.ToDisplayString (); + switch (attributeType) { + case AttributesNames.BindingAttribute: + // validate that the class is partial, else we need to report an error + var diagnostics= self.Analyze (context.Compilation.GetCurrentPlatform (), + declarationNode, declaredSymbol); + foreach (var diagnostic in diagnostics) + context.ReportDiagnostic (diagnostic); + break; + } + } + } +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/PlatformNameExtensions.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/PlatformNameExtensions.cs new file mode 100644 index 00000000000..2564975dfbe --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/PlatformNameExtensions.cs @@ -0,0 +1,16 @@ +using Xamarin.Utils; + +namespace Microsoft.Macios.Bindings.Analyzer.Extensions; + +public static class PlatformNameExtensions { + + public static ApplePlatform ToApplePlatform (this PlatformName platformName) + => platformName switch { + PlatformName.iOS => ApplePlatform.iOS, + PlatformName.MacCatalyst => ApplePlatform.MacCatalyst, + PlatformName.MacOSX => ApplePlatform.MacOSX, + PlatformName.TvOS => ApplePlatform.TVOS, + _ => ApplePlatform.None, + }; + +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/IBindingTypeAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/IBindingTypeAnalyzer.cs new file mode 100644 index 00000000000..42e7759bf5f --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/IBindingTypeAnalyzer.cs @@ -0,0 +1,12 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Macios.Bindings.Analyzer; + +/// +/// Interface to be implemented by those analyzer that will be looking at BindingTypes. +/// +public interface IBindingTypeAnalyzer where T : BaseTypeDeclarationSyntax { + ImmutableArray Analyze (PlatformName platformName, T declarationNode, INamedTypeSymbol symbol); +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj index d48d7668221..3c9ecfb6775 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj @@ -18,6 +18,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -43,4 +44,23 @@ + + + external\AttributesNames.cs + + + external\FieldData.cs + + + Extensions\CompilationExtensions.cs + + + Extensions\TypeSymbolExtensions.cs + + + + + + + diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs index bfe0d3152b0..157059a7c12 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs @@ -45,51 +45,99 @@ internal static System.Globalization.CultureInfo Culture { } } - internal static string AB0001Description { + internal static string RBI0001CodeFixTitle { get { - return ResourceManager.GetString("AB0001Description", resourceCulture); + return ResourceManager.GetString("RBI0001CodeFixTitle", resourceCulture); } } - internal static string AB0001MessageFormat { + internal static string RBI0001Description { get { - return ResourceManager.GetString("AB0001MessageFormat", resourceCulture); + return ResourceManager.GetString("RBI0001Description", resourceCulture); } } - internal static string AB0001Title { + internal static string RBI0001MessageFormat { get { - return ResourceManager.GetString("AB0001Title", resourceCulture); + return ResourceManager.GetString("RBI0001MessageFormat", resourceCulture); } } - internal static string RBI0001CodeFixTitle { + internal static string RBI0001Title { get { - return ResourceManager.GetString("RBI0001CodeFixTitle", resourceCulture); + return ResourceManager.GetString("RBI0001Title", resourceCulture); } } - internal static string RBI0001Description { + internal static string RBI0002Description { get { - return ResourceManager.GetString("RBI0001Description", resourceCulture); + return ResourceManager.GetString("RBI0002Description", resourceCulture); } } - internal static string RBI0001MessageFormat { + internal static string RBI0002MessageFormat { get { - return ResourceManager.GetString("RBI0001MessageFormat", resourceCulture); + return ResourceManager.GetString("RBI0002MessageFormat", resourceCulture); } } - internal static string RBI0001Title { + internal static string RBI0002Title { get { - return ResourceManager.GetString("RBI0001Title", resourceCulture); + return ResourceManager.GetString("RBI0002Title", resourceCulture); + } + } + + internal static string RBI0003Description { + get { + return ResourceManager.GetString("RBI0003Description", resourceCulture); + } + } + + internal static string RBI0003MessageFormat { + get { + return ResourceManager.GetString("RBI0003MessageFormat", resourceCulture); + } + } + + internal static string RBI0003Title { + get { + return ResourceManager.GetString("RBI0003Title", resourceCulture); + } + } + + internal static string RBI0004Description { + get { + return ResourceManager.GetString("RBI0004Description", resourceCulture); + } + } + + internal static string RBI0004MessageFormat { + get { + return ResourceManager.GetString("RBI0004MessageFormat", resourceCulture); + } + } + + internal static string RBI0004Title { + get { + return ResourceManager.GetString("RBI0004Title", resourceCulture); + } + } + + internal static string RBI0005Description { + get { + return ResourceManager.GetString("RBI0005Description", resourceCulture); + } + } + + internal static string RBI0005MessageFormat { + get { + return ResourceManager.GetString("RBI0005MessageFormat", resourceCulture); } } - internal static string AB0002Description { + internal static string RBI0005Title { get { - return ResourceManager.GetString("AB0002Description", resourceCulture); + return ResourceManager.GetString("RBI0005Title", resourceCulture); } } } diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx index 2f228a310c3..3bc741ecf5b 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx @@ -34,4 +34,56 @@ Binding type declaration must be partial The title of the diagnostic. + + + In order for the code to be generated a smart enum value has to have a backing field. + An optional longer localizable description of the diagnostic. + + + The enum value '{0}' must be tagged with a FieldAttribute. + The format-able message the diagnostic displays. + + + Smart enum values must be tagged with an FieldAttribute + The title of the diagnostic. + + + + In order for the code to be generated the backing filed of a smart enum value cannot be an empty string. + An optional longer localizable description of the diagnostic. + + + The enum value '{0}' backing field is an empty string. + The format-able message the diagnostic displays. + + + Smart enum backing field cannot be an empty string. + The title of the diagnostic. + + + + Smart enum backing field for a non Apple framework must provide a library name. + An optional longer localizable description of the diagnostic. + + + The enum value '{0}' backing field must provide a library name. + The format-able message the diagnostic displays. + + + Non Apple framework bindings must provide a library name, + The title of the diagnostic. + + + + Fields of known Apple frameworks should not provide a LibraryName. + An optional longer localizable description of the diagnostic. + + + The backing Field of '{0}' should not provide a LibraryName + The format-able message the diagnostic displays. + + + Do not provide the LibraryName for known Apple frameworks + The title of the diagnostic. + diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/SmartEnumSemanticAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/SmartEnumSemanticAnalyzer.cs new file mode 100644 index 00000000000..7899c23ce2b --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/SmartEnumSemanticAnalyzer.cs @@ -0,0 +1,142 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Macios.Bindings.Analyzer.Extensions; +using Microsoft.Macios.Generator; +using Microsoft.Macios.Generator.Attributes; +using Microsoft.Macios.Generator.Extensions; + +namespace Microsoft.Macios.Bindings.Analyzer; + + +/// +/// Analyzer to ensure that all enum values in an SmartEnum contains a Field attribute. +/// +[DiagnosticAnalyzer (LanguageNames.CSharp)] +public class SmartEnumSemanticAnalyzer : DiagnosticAnalyzer, IBindingTypeAnalyzer { + // All enum values must have a Field attribute + internal static readonly DiagnosticDescriptor RBI0002 = new ( + "RBI0002", + new LocalizableResourceString (nameof (Resources.RBI0002Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0002MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0002Description), Resources.ResourceManager, typeof (Resources)) + ); + + // All Field symbols cannot be empty or white space + internal static readonly DiagnosticDescriptor RBI0003 = new ( + "RBI0003", + new LocalizableResourceString (nameof (Resources.RBI0003Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0003MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0003Description), Resources.ResourceManager, typeof (Resources)) + ); + + // If not an apple framework, we should provide the library path + internal static readonly DiagnosticDescriptor RBI0004 = new ( + "RBI0004", + new LocalizableResourceString (nameof (Resources.RBI0004Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0004MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0004Description), Resources.ResourceManager, typeof (Resources)) + ); + + // if apple framework, the library path should be empty + internal static readonly DiagnosticDescriptor RBI0005 = new ( + "RBI0005", + new LocalizableResourceString (nameof (Resources.RBI0005Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0005MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0005Description), Resources.ResourceManager, typeof (Resources)) + ); + + public override ImmutableArray SupportedDiagnostics { get; } = [RBI0002, RBI0003, RBI0004, RBI0005]; + + public override void Initialize (AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution (); + context.RegisterSyntaxNodeAction (AnalysisContext, SyntaxKind.EnumDeclaration); + } + + void AnalysisContext (SyntaxNodeAnalysisContext context) + => this.AnalyzeBindingType (context); + + public ImmutableArray Analyze (PlatformName platformName, EnumDeclarationSyntax declarationNode, INamedTypeSymbol symbol) + { + // we want to ensure several things: + // 1. All enum values are marked with a Field attribute + // 2. All Field attributes have a symbol name + // 3. If the Field attribute is not from a known apple library, the library name is set + // 4. If the Field attribute is from a known apple library the lib should be null + + // based on the platform decide if we are dealing with a known apple framework, we want all, not just the + // ones that are part of the simulator + var appleFrameworks = Frameworks.GetFrameworks (platformName.ToApplePlatform (), false); + var isAppleFramework = appleFrameworks.Find (symbol.ContainingNamespace.Name) is not null; + + // bucket with all the diagnostics we have found + var bucket = ImmutableArray.CreateBuilder (); + + var members = symbol.GetMembers ().OfType ().ToArray (); + foreach (var fieldSymbol in members) { + var attributes = fieldSymbol.GetAttributeData (); + if (attributes.Count == 0) { + // 1. All enum values are marked with a Field attribute, therefore add a diagnostic + bucket.Add (Diagnostic.Create (RBI0002, + fieldSymbol.Locations.First (), + fieldSymbol.ToDisplayString ())); + continue; + } + + // Get all the FieldAttribute, parse it and add the data to the result + if (attributes.TryGetValue (AttributesNames.FieldAttribute, out var fieldAttrData)) { + var fieldSyntax = fieldAttrData.ApplicationSyntaxReference?.GetSyntax (); + if (fieldSyntax is null) { + // if we cant get the syntax reference, we have a bug + continue; + } + + if (FieldData.TryParse (fieldSyntax, fieldAttrData, out var fieldData)) { + // only provide diagnostics if we managed to parse the FieldData, else we have a bug in the + // analyzer + if (string.IsNullOrWhiteSpace (fieldData.SymbolName)) { + // 2. All Field attributes have a symbol name, therefore add a diagnostic + bucket.Add (Diagnostic.Create (RBI0003, + fieldSyntax.GetLocation (), + fieldSymbol.ToDisplayString ())); + } + + if (!isAppleFramework) { + if (string.IsNullOrWhiteSpace (fieldData.LibraryName)) { + bucket.Add (Diagnostic.Create (RBI0004, + fieldSyntax.GetLocation (), + fieldSymbol.ToDisplayString ())); + } + } else { + if (fieldData.LibraryName is not null) { + bucket.Add (Diagnostic.Create (RBI0005, + fieldSyntax.GetLocation (), + fieldSymbol.ToDisplayString ())); + } + } + } else { + // report but msg + } + } + } + + return bucket.ToImmutable (); + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs b/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs index 9eab92394f6..933cc0f082c 100644 --- a/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs +++ b/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs @@ -5,7 +5,7 @@ namespace Microsoft.Macios.Generator.Attributes; record FieldData { public string SymbolName { get; } - public string? LibraryName { get; } + public string? LibraryName { get; private set; } FieldData (string symbolName, string? libraryName = null) { @@ -22,14 +22,30 @@ public static bool TryParse (SyntaxNode attributeSyntax, AttributeData attribute switch (count) { case 1: data = new((string) attributeData.ConstructorArguments [0].Value!); - return true; + break; case 2: data = new((string) attributeData.ConstructorArguments [0].Value!, (string) attributeData.ConstructorArguments [1].Value!); - return true; + break; default: // 0 should not be an option.. return false; } + + if (attributeData.NamedArguments.Length == 0) + return true; + + // LibraryName can be a param value + foreach (var (name, value) in attributeData.NamedArguments) { + switch (name) { + case "LibraryName": + data.LibraryName = (string) value.Value!; + break; + default: + data = null; + return false; + } + } + return true; } } diff --git a/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs index da040c6610a..ca2766bb79e 100644 --- a/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs +++ b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs @@ -5,6 +5,6 @@ namespace Microsoft.Macios.Generator; /// public static class AttributesNames { - public static readonly string BindingAttribute = "ObjCBindings.BindingTypeAttribute"; - public static readonly string FieldAttribute = "Foundation.FieldAttribute"; + public const string BindingAttribute = "ObjCBindings.BindingTypeAttribute"; + public const string FieldAttribute = "Foundation.FieldAttribute"; } diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 4911e79b5c1..7292f2e24d1 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -92,7 +93,9 @@ static void AddPipeline (IncrementalGeneratorInitializationContext context) w /// /// Root syntax tree of the base type declaration. /// String builder that will be used for the generated code. - static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) + /// The context of the code being generated. Used to keep track of the current added statemnts + static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb, ICodeEmitter emitter) + where T : BaseTypeDeclarationSyntax { // collect all using from the syntax tree, add them to a hash to make sure that we don't have duplicates // and add those usings that we do know we need for bindings. @@ -103,7 +106,14 @@ static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) var usingDirectivesToKeep = new HashSet (usingDirectives) { // add the using statements that we know we need and print them to the sb }; - foreach (var ns in usingDirectivesToKeep) { + + // add those using statements needed by the emitter + foreach (var ns in emitter.UsingStatements) { + usingDirectivesToKeep.Add (ns); + } + + // add them sorted so that we have testeable generated code + foreach (var ns in usingDirectivesToKeep.OrderBy (s => s)) { if (string.IsNullOrEmpty (ns)) continue; sb.AppendLine ($"using {ns};"); @@ -138,12 +148,14 @@ static void GenerateCode (SourceProductionContext context, Compilation compil sb.AppendLine ("#nullable enable"); sb.AppendLine (); - CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); // delegate semantic model and syntax tree analysis to the emitter who will generate the code and knows // best if (ContextFactory.TryCreate (rootContext, semanticModel, namedTypeSymbol, baseTypeDeclarationSyntax, out var symbolBindingContext) && EmitterFactory.TryCreate (symbolBindingContext, sb, out var emitter)) { + + CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb, emitter); + if (emitter.TryEmit(out var diagnostics)) { // only add file when we do generate code var code = sb.ToString (); diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs index 0cbdf36637d..35df7c2036f 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Extensions; namespace Microsoft.Macios.Generator.Context; @@ -22,27 +23,7 @@ class RootBindingContext { public RootBindingContext (Compilation compilation) { Compilation = compilation; - CurrentPlatform = PlatformName.None; - // use the reference assembly to determine what platform we are binding - foreach (var referencedAssemblyName in compilation.ReferencedAssemblyNames) { - switch (referencedAssemblyName.Name) { - case "Microsoft.iOS": - CurrentPlatform = PlatformName.iOS; - break; - case "Microsoft.MacCatalyst": - CurrentPlatform = PlatformName.MacCatalyst; - break; - case "Microsoft.macOS": - CurrentPlatform = PlatformName.MacOSX; - break; - case "Microsoft.tvOS": - CurrentPlatform = PlatformName.TvOS; - break; - default: - CurrentPlatform = PlatformName.None; - break; - } - } + CurrentPlatform = compilation.GetCurrentPlatform (); } // TODO: clean code coming from the old generator diff --git a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs index b3d0c24cfbb..79e03a7d730 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs index 0d56a01a1c1..bef0cc6e4a5 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -8,6 +9,8 @@ namespace Microsoft.Macios.Generator.Emitters; class ClassEmitter (ClassBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { public string SymbolName => context.SymbolName; + public IEnumerable UsingStatements => []; + public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs index 63c7468823e..91a91047d78 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -12,6 +13,7 @@ class EnumEmitter (ISymbolBindingContext context, TabbedS : ICodeEmitter { public string SymbolName => $"{context.SymbolName}Extensions"; + public IEnumerable UsingStatements => ["System"]; void Emit (TabbedStringBuilder classBlock, (IFieldSymbol Symbol, FieldData FieldData) enumField, int index) { diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs index d78584e82bb..6ae7b9b10f0 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -10,4 +11,5 @@ namespace Microsoft.Macios.Generator.Emitters; interface ICodeEmitter { public string SymbolName { get; } bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics); + IEnumerable UsingStatements { get; } } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs index 23f77d0c4d7..65329736ad2 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -8,6 +9,7 @@ namespace Microsoft.Macios.Generator.Emitters; class InterfaceEmitter (ISymbolBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { public string SymbolName { get; } = string.Empty; + public IEnumerable UsingStatements => []; public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diagnostics = null; diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs new file mode 100644 index 00000000000..bcf6edeb23a --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Generator.Extensions; + +public static class CompilationExtensions { + + public static PlatformName GetCurrentPlatform (this Compilation self) + { + // use the reference assembly to determine what platform we are binding + foreach (var referenceAssembly in self.ReferencedAssemblyNames) { + switch (referenceAssembly.Name) { + case "Microsoft.iOS": + return PlatformName.iOS; + case "Microsoft.MacCatalyst": + return PlatformName.MacCatalyst; + case "Microsoft.macOS": + return PlatformName.MacOSX; + case "Microsoft.tvOS": + return PlatformName.TvOS; + } + } + + return PlatformName.None; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj index ae6c5d6544c..d6dc11ca906 100644 --- a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj +++ b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj @@ -27,15 +27,15 @@ <_Parameter1>Microsoft.Macios.Generator.Tests - - - external\PlatformName.cs - external\PlatformNameExtensions.cs + + + + diff --git a/src/rgen/rgen.sln b/src/rgen/rgen.sln index 68499203954..701dc87108a 100644 --- a/src/rgen/rgen.sln +++ b/src/rgen/rgen.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Generator. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Bindings.Analyzer.Tests", "..\..\tests\rgen\Microsoft.Macios.Bindings.Analyzer.Tests\Microsoft.Macios.Bindings.Analyzer.Tests.csproj", "{1AC4A248-CC98-4392-8690-4E2CAF6E194B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Binding.Common", "Microsoft.Macios.Binding.Common\Microsoft.Macios.Binding.Common.csproj", "{536758BC-2A88-4B79-ABB1-6B39494A5FE6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +44,9 @@ Global {1AC4A248-CC98-4392-8690-4E2CAF6E194B}.Debug|Any CPU.Build.0 = Debug|Any CPU {1AC4A248-CC98-4392-8690-4E2CAF6E194B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1AC4A248-CC98-4392-8690-4E2CAF6E194B}.Release|Any CPU.Build.0 = Release|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs index 67c71504329..2800e2632da 100644 --- a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs @@ -29,10 +29,10 @@ public class Examples { var compilation = CreateCompilation (nameof (CompareGeneratedCode), platform, inputText); var diagnostics = await RunAnalyzer (new BindingTypeSemanticAnalyzer (), compilation); var analyzerDiagnotics = diagnostics - .Where (d => d.Id == BindingTypeSemanticAnalyzer.DiagnosticId).ToArray (); + .Where (d => d.Id == BindingTypeSemanticAnalyzer.RBI0001.Id).ToArray (); Assert.Single (analyzerDiagnotics); // verify the diagnostic message - VerifyDiagnosticMessage (analyzerDiagnotics [0], BindingTypeSemanticAnalyzer.DiagnosticId, + VerifyDiagnosticMessage (analyzerDiagnotics [0], BindingTypeSemanticAnalyzer.RBI0001.Id, DiagnosticSeverity.Error, "The binding type 'Test.Examples' must declared as a partial class"); } } diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/SmartEnumSemanticAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/SmartEnumSemanticAnalyzerTests.cs new file mode 100644 index 00000000000..623955b204f --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/SmartEnumSemanticAnalyzerTests.cs @@ -0,0 +1,255 @@ +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 SmartEnumSemanticAnalyzerTests : BaseGeneratorWithAnalyzerTestClass { + + [Theory] + [PlatformInlineData (ApplePlatform.iOS)] + [PlatformInlineData (ApplePlatform.TVOS)] + [PlatformInlineData (ApplePlatform.MacOSX)] + [PlatformInlineData (ApplePlatform.MacCatalyst)] + public async Task SmartEnumMustHaveFieldAttribute (ApplePlatform platform) + { + const string inputText = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // missing field attribute, should be an error + Shutdown, +} +"; + + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer (), compilation); + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0002.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + // verify the diagnostic message + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0002.Id, + DiagnosticSeverity.Error, "The enum value 'AVFoundation.AVCaptureSystemPressureExampleLevel.Shutdown' must be tagged with a FieldAttribute."); + } + + [Theory] + [PlatformInlineData (ApplePlatform.iOS)] + [PlatformInlineData (ApplePlatform.TVOS)] + [PlatformInlineData (ApplePlatform.MacOSX)] + [PlatformInlineData (ApplePlatform.MacCatalyst)] + public async Task SmartEnumSymbolMustBeCorrect (ApplePlatform platform) + { + const string inputText = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // empty field, this should be an error + [Field ("" "")] + Shutdown, +}"; + + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer (), compilation); + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0003.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0003.Id, + DiagnosticSeverity.Error, "The enum value 'AVFoundation.AVCaptureSystemPressureExampleLevel.Shutdown' backing field is an empty string."); + } + + const string AppleFrameworkLib = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // do not do this with apple frameworks + [Field (""AVCaptureSystemPressureLevelShutdown"", ""/path/to/not/needed/lib"")] + Shutdown, +}"; + + const string AppleFrameworkLibNamedParameter = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // do not do this with apple frameworks + [Field (""AVCaptureSystemPressureLevelShutdown"", LibraryName = ""/path/to/not/needed/lib"")] + Shutdown, +}"; + + [Theory] + [PlatformInlineData (ApplePlatform.iOS, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.iOS, AppleFrameworkLibNamedParameter)] + [PlatformInlineData (ApplePlatform.TVOS, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.TVOS, AppleFrameworkLibNamedParameter)] + [PlatformInlineData (ApplePlatform.MacOSX, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.MacOSX, AppleFrameworkLibNamedParameter)] + [PlatformInlineData (ApplePlatform.MacCatalyst, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.MacCatalyst, AppleFrameworkLibNamedParameter)] + public async Task SmartEnumAppleFrameworkNotLibrary (ApplePlatform platform, string inputText) + { + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer(), compilation); + + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0005.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0005.Id, + DiagnosticSeverity.Warning, "The backing Field of 'AVFoundation.AVCaptureSystemPressureExampleLevel.Shutdown' should not provide a LibraryName"); + } + + const string CustomLibraryMissingLibraryName = @" +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace CustomLibrary; + +[BindingType] +public enum CustomLibraryEnum { + [Field (""None"", ""/path/to/customlibrary.framework"")] + None, + [Field (""Medium"", ""/path/to/customlibrary.framework"")] + Medium, + // missing lib, this is an error + [Field (""High"")] + High, +} +"; + const string CustomLibraryEmptyLibraryName = @" +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace CustomLibrary; + +[BindingType] +public enum CustomLibraryEnum { + [Field (""None"", ""/path/to/customlibrary.framework"")] + None, + [Field (""Medium"", ""/path/to/customlibrary.framework"")] + Medium, + // empty lib, this is an error + [Field (""High"", "" "")] + High, +} +"; + + const string CustomLibraryEmptyLibraryNameParameter = @" +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace CustomLibrary; + +[BindingType] +public enum CustomLibraryEnum { + [Field (""None"", ""/path/to/customlibrary.framework"")] + None, + [Field (""Medium"", ""/path/to/customlibrary.framework"")] + Medium, + // empty lib, this is an error + [Field (""High"", LibraryName = "" "")] + High, +} +"; + + [Theory] + [PlatformInlineData (ApplePlatform.iOS, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.iOS, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.iOS, CustomLibraryEmptyLibraryNameParameter)] + [PlatformInlineData (ApplePlatform.TVOS, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.TVOS, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.TVOS, CustomLibraryEmptyLibraryNameParameter)] + [PlatformInlineData (ApplePlatform.MacOSX, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.MacOSX, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.MacOSX, CustomLibraryEmptyLibraryNameParameter)] + [PlatformInlineData (ApplePlatform.MacCatalyst, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.MacCatalyst, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.MacCatalyst, CustomLibraryEmptyLibraryNameParameter)] + public async Task SmartEnumThirdPartyLibrary (ApplePlatform platform, string inputText) + { + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer(), compilation); + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0004.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0004.Id, + DiagnosticSeverity.Error, "The enum value 'CustomLibrary.CustomLibraryEnum.High' backing field must provide a library name."); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs index 96a853256d2..a183d4206fc 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs @@ -59,7 +59,8 @@ protected Compilation CreateCompilation (string name, ApplePlatform platform, pa var parseOptions = new CSharpParseOptions (LanguageVersion.Latest, DocumentationMode.None, preprocessorSymbols: platformDefines[platform]);; var trees = sources.Select (s => CSharpSyntaxTree.ParseText (s, parseOptions)); - var options = new CSharpCompilationOptions (OutputKind.NetModule); + var options = new CSharpCompilationOptions (OutputKind.NetModule) + .WithAllowUnsafe (true); return CSharpCompilation.Create (name, trees, references, options); } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs index 74372899eb4..99279577131 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs @@ -23,9 +23,9 @@ public partial class AVAudioPcmBuffer : AVAudioBuffer { #nullable enable -using System; using Foundation; using ObjCBindings; +using System; namespace TestNamespace { diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj b/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj index 3a72deb0825..066aa4b25bd 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj @@ -20,6 +20,7 @@ + @@ -36,21 +37,12 @@ external\ExecutionHelper.cs - - external\ApplePlatform.cs - - - external\TargetFramework.cs - external\StringUtils.cs external\Execution.cs - - external\SdkVersions.cs - external\Cache.cs diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs index 000852a0889..c748e4e3671 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs @@ -2,10 +2,10 @@ #nullable enable -using System; using Foundation; -using ObjCRuntime; using ObjCBindings; +using ObjCRuntime; +using System; namespace AVFoundation; diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureSystemPressureLevel.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureSystemPressureLevel.cs index 621b8f445c6..83444b481be 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureSystemPressureLevel.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureSystemPressureLevel.cs @@ -3,8 +3,9 @@ #nullable enable using Foundation; -using ObjCRuntime; using ObjCBindings; +using ObjCRuntime; +using System; namespace AVFoundation; diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedCustomLibraryEnum.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedCustomLibraryEnum.cs index a30c3a66075..1dc530f0b0c 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedCustomLibraryEnum.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedCustomLibraryEnum.cs @@ -2,11 +2,11 @@ #nullable enable -using System; -using System.Runtime.Versioning; using Foundation; -using ObjCRuntime; using ObjCBindings; +using ObjCRuntime; +using System; +using System.Runtime.Versioning; namespace CustomLibrary; diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedMacOSAVMediaCharacteristics.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedMacOSAVMediaCharacteristics.cs index d9e3a0db096..3ea93bd507d 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedMacOSAVMediaCharacteristics.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedMacOSAVMediaCharacteristics.cs @@ -2,11 +2,11 @@ #nullable enable -using System; -using System.Runtime.Versioning; using Foundation; -using ObjCRuntime; using ObjCBindings; +using ObjCRuntime; +using System; +using System.Runtime.Versioning; namespace AVFoundation; diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectediOSAVMediaCharacteristics.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectediOSAVMediaCharacteristics.cs index cc0a4e975aa..0d121407e33 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectediOSAVMediaCharacteristics.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectediOSAVMediaCharacteristics.cs @@ -2,11 +2,11 @@ #nullable enable -using System; -using System.Runtime.Versioning; using Foundation; -using ObjCRuntime; using ObjCBindings; +using ObjCRuntime; +using System; +using System.Runtime.Versioning; namespace AVFoundation;