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 5a77c43cfbf..50e69ddd6a5 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 @@ -63,8 +63,8 @@ Generator/Extensions/CompilationExtensions.cs - - Generator/Extensions/TypeSymbolExtensions.cs + + Generator/Extensions/TypeSymbolExtensions.Core.cs Generator/Extensions/TypedConstantExtensions.cs diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Core.cs similarity index 59% rename from src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.cs rename to src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Core.cs index 7a2766ff02e..57eaf428816 100644 --- a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.cs +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Core.cs @@ -1,19 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; -using Microsoft.Macios.Generator.Attributes; -using Microsoft.Macios.Generator.Availability; namespace Microsoft.Macios.Generator.Extensions; -static class TypeSymbolExtensions { +static partial class TypeSymbolExtensions { /// /// Retrieve a dictionary with the attribute data of all the attributes attached to a symbol. Because /// an attribute can appear more than once, the valus are a collection of attribute data. @@ -71,74 +68,6 @@ public static ImmutableArray GetParents (this ISymbol symbol) return [.. result]; } - /// - /// Return the symbol availability WITHOUT taking into account the parent symbols availability. - /// - /// The symbols whose availability attributes we want to retrieve. - /// The symbol availability WITHOUT taking into account the parent symbols. - /// This is a helper method, you probably don't want to use it. - static SymbolAvailability GetAvailabilityForSymbol (this ISymbol symbol) - { - //get the attribute of the symbol and look for the Supported and Unsupported attributes and - // add the different platforms to the result hashsets - var builder = SymbolAvailability.CreateBuilder (); - var boundAttributes = symbol.GetAttributes (); - if (boundAttributes.Length == 0) { - // no attrs in the symbol, therefore the symbol is supported in all platforms - return builder.ToImmutable (); - } - - foreach (var attributeData in boundAttributes) { - var attrName = attributeData.AttributeClass?.ToDisplayString (); - if (string.IsNullOrEmpty (attrName)) - continue; - // we only care in this case about the support and unsupported attrs, ignore any other - switch (attrName) { - case AttributesNames.SupportedOSPlatformAttribute: - if (SupportedOSPlatformData.TryParse (attributeData, out var supportedPlatform)) { - builder.Add (supportedPlatform.Value); - } - - break; - case AttributesNames.UnsupportedOSPlatformAttribute: - if (UnsupportedOSPlatformData.TryParse (attributeData, out var unsupportedPlatform)) { - builder.Add (unsupportedPlatform.Value); - } - - break; - case AttributesNames.ObsoletedOSPlatformAttribute: - if (ObsoletedOSPlatformData.TryParse (attributeData, out var obsoletedOsPlatform)) { - builder.Add (obsoletedOsPlatform.Value); - } - - break; - default: - continue; - } - } - - return builder.ToImmutable (); - } - - /// - /// Returns the symbol availability taking into account the parent symbols availability. - /// - /// That means that the attributes used on the current symbol are merged with the attributes used - /// in all the symbol parents following the correct child-parent order. - /// - /// The symbol whose availability we want to retrieve. - /// A symbol availability structure for the symbol. - public static SymbolAvailability GetSupportedPlatforms (this ISymbol symbol) - { - var availability = GetAvailabilityForSymbol (symbol); - // get the parents and return the merge - foreach (var parent in GetParents (symbol)) { - availability = availability.MergeWithParent (GetAvailabilityForSymbol (parent)); - } - - return availability; - } - public static bool HasAttribute (this ISymbol symbol, string attribute) { var boundAttributes = symbol.GetAttributes (); @@ -156,56 +85,6 @@ public static bool HasAttribute (this ISymbol symbol, string attribute) return false; } - public static bool IsSmartEnum (this ITypeSymbol symbol) - { - // a type is a smart enum if its type is a enum one AND it was decorated with the - // binding type attribute - return symbol.TypeKind == TypeKind.Enum - && symbol.HasAttribute (AttributesNames.BindingAttribute); - } - - public static BindingTypeData GetBindingData (this ISymbol symbol) - { - var boundAttributes = symbol.GetAttributes (); - if (boundAttributes.Length == 0) { - // no attrs in the symbol, therefore the symbol is supported in all platforms - return default; - } - - // we are looking for the basic BindingAttribute attr - foreach (var attributeData in boundAttributes) { - var attrName = attributeData.AttributeClass?.ToDisplayString (); - if (string.IsNullOrEmpty (attrName) || attrName != AttributesNames.BindingAttribute) - continue; - if (BindingTypeData.TryParse (attributeData, out var bindingData)) { - return bindingData.Value; - } - } - - return default; - } - - public static BindingTypeData GetBindingData (this ISymbol symbol) where T : Enum - { - var boundAttributes = symbol.GetAttributes (); - if (boundAttributes.Length == 0) { - // no attrs in the symbol, therefore the symbol is supported in all platforms - return default; - } - - var targetAttrName = AttributesNames.GetBindingTypeAttributeName (); - foreach (var attributeData in boundAttributes) { - var attrName = attributeData.AttributeClass?.ToDisplayString (); - if (string.IsNullOrEmpty (attrName) || attrName != targetAttrName) - continue; - if (BindingTypeData.TryParse (attributeData, out var bindingData)) { - return bindingData.Value; - } - } - - return default; - } - delegate string? GetAttributeNames (); delegate bool TryParse (AttributeData data, [NotNullWhen (true)] out T? value) where T : struct; @@ -235,28 +114,6 @@ public static BindingTypeData GetBindingData (this ISymbol symbol) where T return null; } - /// - /// Retrieve the data of an export attribute on a symbol. - /// - /// The tagged symbol. - /// Enum type used in the attribute. - /// The data of the export attribute if present or null if it was not found. - /// If the passed enum is unknown or not supported as an enum for the export attribute, null will be - /// returned. - public static ExportData? GetExportData (this ISymbol symbol) where T : Enum - => GetAttribute> (symbol, AttributesNames.GetExportAttributeName, ExportData.TryParse); - - /// - /// Retrieve the data of a field attribute on a symbol. - /// - /// The tagged symbol. - /// Enum type used in the attribute. - /// The data of the export attribute if present or null if it was not found. - /// If the passed enum is unknown or not supported as an enum for the field attribute, null will be - /// returned. - public static FieldData? GetFieldData (this ISymbol symbol) where T : Enum - => GetAttribute> (symbol, AttributesNames.GetFieldAttributeName, FieldData.TryParse); - /// /// Returns if a type is blittable or not. /// diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Generator.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Generator.cs new file mode 100644 index 00000000000..70d64b65039 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Generator.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Attributes; +using Microsoft.Macios.Generator.Availability; + +namespace Microsoft.Macios.Generator.Extensions; + +static partial class TypeSymbolExtensions { + + /// + /// Return the symbol availability WITHOUT taking into account the parent symbols availability. + /// + /// The symbols whose availability attributes we want to retrieve. + /// The symbol availability WITHOUT taking into account the parent symbols. + /// This is a helper method, you probably don't want to use it. + static SymbolAvailability GetAvailabilityForSymbol (this ISymbol symbol) + { + //get the attribute of the symbol and look for the Supported and Unsupported attributes and + // add the different platforms to the result hashsets + var builder = SymbolAvailability.CreateBuilder (); + var boundAttributes = symbol.GetAttributes (); + if (boundAttributes.Length == 0) { + // no attrs in the symbol, therefore the symbol is supported in all platforms + return builder.ToImmutable (); + } + + foreach (var attributeData in boundAttributes) { + var attrName = attributeData.AttributeClass?.ToDisplayString (); + if (string.IsNullOrEmpty (attrName)) + continue; + // we only care in this case about the support and unsupported attrs, ignore any other + switch (attrName) { + case AttributesNames.SupportedOSPlatformAttribute: + if (SupportedOSPlatformData.TryParse (attributeData, out var supportedPlatform)) { + builder.Add (supportedPlatform.Value); + } + + break; + case AttributesNames.UnsupportedOSPlatformAttribute: + if (UnsupportedOSPlatformData.TryParse (attributeData, out var unsupportedPlatform)) { + builder.Add (unsupportedPlatform.Value); + } + + break; + case AttributesNames.ObsoletedOSPlatformAttribute: + if (ObsoletedOSPlatformData.TryParse (attributeData, out var obsoletedOsPlatform)) { + builder.Add (obsoletedOsPlatform.Value); + } + + break; + default: + continue; + } + } + + return builder.ToImmutable (); + } + + /// + /// Returns the symbol availability taking into account the parent symbols availability. + /// + /// That means that the attributes used on the current symbol are merged with the attributes used + /// in all the symbol parents following the correct child-parent order. + /// + /// The symbol whose availability we want to retrieve. + /// A symbol availability structure for the symbol. + public static SymbolAvailability GetSupportedPlatforms (this ISymbol symbol) + { + var availability = GetAvailabilityForSymbol (symbol); + // get the parents and return the merge + foreach (var parent in GetParents (symbol)) { + availability = availability.MergeWithParent (GetAvailabilityForSymbol (parent)); + } + + return availability; + } + + public static bool IsSmartEnum (this ITypeSymbol symbol) + { + // a type is a smart enum if its type is a enum one AND it was decorated with the + // binding type attribute + return symbol.TypeKind == TypeKind.Enum + && symbol.HasAttribute (AttributesNames.BindingAttribute); + } + + public static BindingTypeData GetBindingData (this ISymbol symbol) + { + var boundAttributes = symbol.GetAttributes (); + if (boundAttributes.Length == 0) { + // no attrs in the symbol, therefore the symbol is supported in all platforms + return default; + } + + // we are looking for the basic BindingAttribute attr + foreach (var attributeData in boundAttributes) { + var attrName = attributeData.AttributeClass?.ToDisplayString (); + if (string.IsNullOrEmpty (attrName) || attrName != AttributesNames.BindingAttribute) + continue; + if (BindingTypeData.TryParse (attributeData, out var bindingData)) { + return bindingData.Value; + } + } + + return default; + } + + public static BindingTypeData GetBindingData (this ISymbol symbol) where T : Enum + { + var boundAttributes = symbol.GetAttributes (); + if (boundAttributes.Length == 0) { + // no attrs in the symbol, therefore the symbol is supported in all platforms + return default; + } + + var targetAttrName = AttributesNames.GetBindingTypeAttributeName (); + foreach (var attributeData in boundAttributes) { + var attrName = attributeData.AttributeClass?.ToDisplayString (); + if (string.IsNullOrEmpty (attrName) || attrName != targetAttrName) + continue; + if (BindingTypeData.TryParse (attributeData, out var bindingData)) { + return bindingData.Value; + } + } + + return default; + } + + /// + /// Retrieve the data of an export attribute on a symbol. + /// + /// The tagged symbol. + /// Enum type used in the attribute. + /// The data of the export attribute if present or null if it was not found. + /// If the passed enum is unknown or not supported as an enum for the export attribute, null will be + /// returned. + public static ExportData? GetExportData (this ISymbol symbol) where T : Enum + => GetAttribute> (symbol, AttributesNames.GetExportAttributeName, ExportData.TryParse); + + /// + /// Retrieve the data of a field attribute on a symbol. + /// + /// The tagged symbol. + /// Enum type used in the attribute. + /// The data of the export attribute if present or null if it was not found. + /// If the passed enum is unknown or not supported as an enum for the field attribute, null will be + /// returned. + public static FieldData? GetFieldData (this ISymbol symbol) where T : Enum + => GetAttribute> (symbol, AttributesNames.GetFieldAttributeName, FieldData.TryParse); +} diff --git a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs new file mode 100644 index 00000000000..fc767cd8abd --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Macios.Transformer; + +static class AttributesNames { + public const string AbstractAttribute = "AbstractAttribute"; + public const string AdvancedAttribute = "AdvancedAttribute"; + public const string BackingFieldTypeAttribute = "BackingFieldTypeAttribute"; + public const string BaseTypeAttribute = "BaseTypeAttribute"; + public const string BindAttribute = "BindAttribute"; + public const string CategoryAttribute = "CategoryAttribute"; + public const string CoreImageFilterAttribute = "CoreImageFilterAttribute"; + public const string DefaultCtorVisibilityAttribute = "DefaultCtorVisibilityAttribute"; + public const string DeprecatedAttribute = "DeprecatedAttribute"; + public const string DesignatedDefaultCtorAttribute = "DesignatedDefaultCtorAttribute"; + public const string DisableDefaultCtorAttribute = "DisableDefaultCtorAttribute"; + public const string DisposeAttribute = "DisposeAttribute"; + public const string ErrorDomainAttribute = "ErrorDomainAttribute"; + public const string FieldAttribute = "Foundation.FieldAttribute"; + public const string AdviceAttribute = "Foundation.AdviceAttribute"; + public const string ModelAttribute = "Foundation.ModelAttribute"; + public const string ProtocolAttribute = "Foundation.ProtocolAttribute"; + public const string InternalAttribute = "InternalAttribute"; + public const string IntroducedAttribute = "IntroducedAttribute"; + public const string MacCatalystAttribute = "MacCatalystAttribute"; + public const string NoiOSAttribute = "NoiOSAttribute"; + public const string NoMacAttribute = "NoMacAttribute"; + public const string NoMacCatalystAttribute = "NoMacCatalystAttribute"; + public const string NoTVAttribute = "NoTVAttribute"; + public const string iOSAttribute = "ObjCRuntime.iOSAttribute"; + public const string MacAttribute = "ObjCRuntime.MacAttribute"; + public const string NativeAttribute = "ObjCRuntime.NativeAttribute"; + public const string NativeNameAttribute = "ObjCRuntime.NativeNameAttribute"; + public const string ObsoletedAttribute = "ObsoletedAttribute"; + public const string PartialAttribute = "PartialAttribute"; + public const string SealedAttribute = "SealedAttribute"; + public const string StaticAttribute = "StaticAttribute"; + public const string StrongDictionaryAttribute = "StrongDictionaryAttribute"; + public const string SyntheticAttribute = "SyntheticAttribute"; + public const string EditorBrowsableAttribute = "System.ComponentModel.EditorBrowsableAttribute"; + public const string ExperimentalAttribute = "System.Diagnostics.CodeAnalysis.ExperimentalAttribute"; + public const string FlagsAttribute = "System.FlagsAttribute"; + public const string ObsoleteAttribute = "System.ObsoleteAttribute"; + public const string ThreadSafeAttribute = "ThreadSafeAttribute"; + public const string TVAttribute = "TVAttribute"; + public const string UnavailableAttribute = "UnavailableAttribute"; +} diff --git a/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj b/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj index 34893e627f2..3d0ac820d62 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj +++ b/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj @@ -15,4 +15,38 @@ + + + <_Parameter1>Microsoft.Macios.Transformer.Tests + + + + + + DictionaryComparer.cs + + + Attributes/ObsoletedOSPlatformData.cs + + + Attributes/SupportedOSPlatformData.cs + + + Attributes/UnsupportedOSPlatformData.cs + + + Availability/*.cs + + + Extensions/StringExtensions.cs + + + Extensions/TypeSymbolExtensions.Core.cs + + + + + + + diff --git a/src/rgen/Microsoft.Macios.Transformer/Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/Transformer.cs index c98314a37d6..fa653a299ab 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Transformer.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text; using Marille; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.Extensions; using Microsoft.Macios.Transformer.Extensions; using Microsoft.Macios.Transformer.Workers; @@ -17,19 +18,31 @@ namespace Microsoft.Macios.Transformer; /// to be able to process the different transformations per binding type. /// class Transformer { - string destinationDirectory; + readonly string destinationDirectory; readonly Compilation compilation; - readonly ImmutableArray sources; - HashSet? namespaceFilter; + readonly HashSet? namespaceFilter; + readonly Dictionary> transformers = new (); - internal Transformer (string destination, Compilation compilationResult, ImmutableArray syntaxTrees, - IEnumerable? namespaces = null) + internal Transformer (string destination, Compilation compilationResult, IEnumerable? namespaces = null) { destinationDirectory = destination; compilation = compilationResult; - sources = syntaxTrees; if (namespaces is not null) namespaceFilter = new HashSet (namespaces); + + ITransformer<(string Path, string SymbolName)> [] defaultTransformers = [ + new CategoryTransformer (destinationDirectory), + new ClassTransformer (destinationDirectory), + new ProtocolTransformer (destinationDirectory), + new SmartEnumTransformer (destinationDirectory), + new StrongDictionaryTransformer (destinationDirectory), + new CopyTransformer (destinationDirectory), + new ErrorDomainTransformer (destinationDirectory), + ]; + // init the dict of transformers to access them via the name of the class + foreach (var transformer in defaultTransformers) { + transformers.Add (transformer.GetType ().Name, transformer); + } } internal async Task CreateHub () @@ -38,25 +51,71 @@ internal async Task CreateHub () var hub = new Hub (); // use as many threads as the system allows - var topicConfiguration = new TopicConfiguration { Mode = ChannelDeliveryMode.AtLeastOnceSync }; + var configuration = new TopicConfiguration { Mode = ChannelDeliveryMode.AtLeastOnceSync }; + foreach (var (topicName, transformer) in transformers) { + await hub.CreateAsync (topicName, configuration, transformer, transformer); + } - // create the channels, because the app is alreay asyc there is nothing to deal with threads. - var categories = new CategoryTransformer (destinationDirectory); - await hub.CreateAsync (nameof (CategoryTransformer), topicConfiguration, categories, categories); + return hub; + } - var classes = new ClassTransformer (destinationDirectory); - await hub.CreateAsync (nameof (ClassTransformer), topicConfiguration, classes, classes); + internal static string? SelectTopic (INamedTypeSymbol symbol) + { + // get the attrs, based on those return the correct topic to use + var attrs = symbol.GetAttributeData (); + if (symbol.TypeKind == TypeKind.Enum) { + // simplest case, an error domain + if (attrs.ContainsKey (AttributesNames.ErrorDomainAttribute)) + return nameof (ErrorDomainTransformer); + + // in this case, we need to check if the enum is a smart enum. + // Smart enum: One of the enum members contains a FieldAttribute. Does NOT have to be ALL + var enumMembers = symbol.GetMembers ().OfType ().ToArray (); + foreach (var enumField in enumMembers) { + var fieldAttrs = enumField.GetAttributeData (); + if (fieldAttrs.ContainsKey (AttributesNames.FieldAttribute)) { + return nameof (SmartEnumTransformer); + } + } - var protocols = new ProtocolTransformer (destinationDirectory); - await hub.CreateAsync (nameof (ProtocolTransformer), topicConfiguration, protocols, protocols); + // we have either a native enum of a regular enum, we will use the copy worker + return nameof (CopyTransformer); + } - var smartEnums = new SmartEnumTransformer (destinationDirectory); - await hub.CreateAsync (nameof (SmartEnumTransformer), topicConfiguration, smartEnums, smartEnums); + if (attrs.ContainsKey (AttributesNames.BaseTypeAttribute)) { + // if can be a class or a protocol, check if the protocol attribute is present + if (attrs.ContainsKey (AttributesNames.ProtocolAttribute) || + attrs.ContainsKey (AttributesNames.ModelAttribute)) + return nameof (ProtocolTransformer); + if (attrs.ContainsKey (AttributesNames.CategoryAttribute)) + return nameof (CategoryTransformer); + return nameof (ClassTransformer); + } - var strongDictionaries = new StrongDictionaryTransformer (destinationDirectory); - await hub.CreateAsync (nameof (StrongDictionaryTransformer), topicConfiguration, strongDictionaries, strongDictionaries); + if (attrs.ContainsKey (AttributesNames.StrongDictionaryAttribute)) + return nameof (StrongDictionaryTransformer); - return hub; + return null; + } + + internal bool Skip (SyntaxTree syntaxTree, ISymbol symbol, [NotNullWhen (false)] out string? outputDirectory) + { + outputDirectory = null; + var symbolNamespace = symbol.ContainingNamespace.ToString (); + if (symbolNamespace is null) + // skip we could not retrieve the namespace + return true; + + if (namespaceFilter is not null && !namespaceFilter.Contains (symbolNamespace)) { + // TODO we could do this better by looking at the tree + Console.WriteLine ( + $"Skipping {symbol.Name} because namespace {symbolNamespace} was not included in the transformation"); + // filtered out + return true; + } + outputDirectory = Path.Combine (destinationDirectory, symbolNamespace); + // If the syntax tree comes from the output directory, we skip it because this is a manual binding + return syntaxTree.FilePath.StartsWith (outputDirectory); } internal async Task Execute () @@ -74,10 +133,10 @@ internal async Task Execute () var hub = await CreateHub (); // with the hub created, loop over the syntax trees and create the messages to be sent to the hub - foreach (var tree in sources) { + foreach (var tree in compilation.SyntaxTrees) { var model = compilation.GetSemanticModel (tree); // the bindings have A LOT of interfaces, we cannot get a symbol for the entire tree - var declarations = tree.GetRoot () + var declarations = (await tree.GetRootAsync ()) .DescendantNodes () .OfType ().ToArray (); @@ -91,31 +150,17 @@ internal async Task Execute () continue; } - var namespaceName = symbol.ContainingNamespace.ToString (); - if (namespaceName is null) - // skip we could not retrieve the namespace - continue; - - if (namespaceFilter is not null && !namespaceFilter.Contains (namespaceName)) { - // TODO we could do this better by looking at the tree - Console.WriteLine ($"Skipping {symbol.Name} because namespace {namespaceName} was not included in the transformation"); - // filtered out + if (Skip (tree, symbol, out var outputDirectory)) + // matched the filter continue; - } // create the destination directory if needed, this is the only location we should be creating directories - var currentDirectory = Path.Combine (destinationDirectory, namespaceName); - Directory.CreateDirectory (currentDirectory); - - var message = (tree.FilePath, symbol.Name); - // push the message to the hub, we are doing this to all channels, this will need to be filered in the - // future - Console.WriteLine ($"Publishing message to all channels: {message}"); - await hub.PublishAsync (nameof (CategoryTransformer), message); - await hub.PublishAsync (nameof (ClassTransformer), message); - await hub.PublishAsync (nameof (ProtocolTransformer), message); - await hub.PublishAsync (nameof (SmartEnumTransformer), message); - await hub.PublishAsync (nameof (StrongDictionaryTransformer), message); + Directory.CreateDirectory (outputDirectory); + + var topicName = SelectTopic (symbol); + if (topicName is not null && transformers.TryGetValue (topicName, out var transformer)) { + await hub.PublishAsync (topicName, transformer.CreateMessage (tree, symbol)); + } } } @@ -124,8 +169,8 @@ internal async Task Execute () await hub.CloseAllAsync (); } - - public static Task Execute (string destinationDirectory, string rspFile, string workingDirectory, string sdkDirectory) + public static Task Execute (string destinationDirectory, string rspFile, string workingDirectory, + string sdkDirectory) { Console.WriteLine ("Executing transformation"); // the transformation works as follows. We first need to parse the rsp file to create a compilation @@ -135,13 +180,13 @@ public static Task Execute (string destinationDirectory, string rspFile, string rspFile, workingDirectory, sdkDirectory); // add NET to the preprocessor directives - var preprocesorDirectives = parseResult.ParseOptions.PreprocessorSymbolNames.ToList (); - preprocesorDirectives.Add ("NET"); + var preprocessorDirectives = parseResult.ParseOptions.PreprocessorSymbolNames.ToList (); + preprocessorDirectives.Add ("NET"); // fixing the parsing options, we must have an issue in the rsp var updatedParseOptions = parseResult.ParseOptions .WithLanguageVersion (LanguageVersion.Latest) - .WithPreprocessorSymbols (preprocesorDirectives) + .WithPreprocessorSymbols (preprocessorDirectives) .WithDocumentationMode (DocumentationMode.None); var references = parseResult.GetReferences (workingDirectory, sdkDirectory); @@ -154,7 +199,7 @@ public static Task Execute (string destinationDirectory, string rspFile, string options: parseResult.CompilationOptions); var diagnostics = compilation.GetDiagnostics (); - Console.WriteLine ($"Diganostics legth {diagnostics.Length}"); + Console.WriteLine ($"Diagnostics length {diagnostics.Length}"); // collect all the compilation errors, ignoring the warnings, if any error is found, we throw an exception var errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error).ToArray (); if (errors.Length > 0) { @@ -162,12 +207,11 @@ public static Task Execute (string destinationDirectory, string rspFile, string foreach (var resultError in errors) { sb.AppendLine ($"{resultError}"); } - Console.WriteLine (sb); throw new Exception ($"Error during workspace compilation: {sb}"); } // create a new transformer with the compilation result and the syntax trees - var transformer = new Transformer (destinationDirectory, compilation, parsedSource); + var transformer = new Transformer (destinationDirectory, compilation); return transformer.Execute (); } } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs index 1c1e7ab391e..86f1adb02c0 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs @@ -2,10 +2,13 @@ // Licensed under the MIT License. using Marille; +using Microsoft.CodeAnalysis; namespace Microsoft.Macios.Transformer.Workers; -public class CategoryTransformer (string destinationDirectory) : IWorker<(string Path, string SymbolName)>, IErrorWorker<(string Path, string Example)> { +public class CategoryTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + + public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { @@ -13,15 +16,20 @@ public class CategoryTransformer (string destinationDirectory) : IWorker<(string return Task.Delay (10); } - public void Dispose () { } - - public ValueTask DisposeAsync () => ValueTask.CompletedTask; - - public Task ConsumeAsync ((string Path, string Example) message, Exception exception, + public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { return Task.CompletedTask; } - public bool UseBackgroundThread { get => true; } + public (string Path, string SymbolName) CreateMessage (SyntaxTree treeNode, ISymbol symbol) + { + return (treeNode.FilePath, symbol.Name); + } + + public void Dispose () { } + + public ValueTask DisposeAsync () => ValueTask.CompletedTask; + + } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs index e3785556735..1cf6367e9da 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs @@ -2,26 +2,32 @@ // Licensed under the MIT License. using Marille; +using Microsoft.CodeAnalysis; namespace Microsoft.Macios.Transformer.Workers; -public class ClassTransformer (string destinationDirectory) : IWorker<(string Path, string SymbolName)>, IErrorWorker<(string Path, string Example)> { +public class ClassTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + public ValueTask DisposeAsync () => ValueTask.CompletedTask; public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { Console.WriteLine ($"ClassTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); return Task.Delay (10); } - public Task ConsumeAsync ((string Path, string Example) message, Exception exception, + public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { return Task.CompletedTask; } + public (string Path, string SymbolName) CreateMessage (SyntaxTree treeNode, ISymbol symbol) + { + return (treeNode.FilePath, symbol.Name); + } + public void Dispose () { } - public ValueTask DisposeAsync () => ValueTask.CompletedTask; public bool UseBackgroundThread { get => true; } } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/CopyTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/CopyTransformer.cs new file mode 100644 index 00000000000..fcff0f2a384 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/CopyTransformer.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Marille; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Transformer.Workers; + +public class CopyTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + + public bool UseBackgroundThread { get => true; } + + public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) + { + Console.WriteLine ($"CopyTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + return Task.Delay (10); + } + + public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, + CancellationToken token = new CancellationToken ()) + { + return Task.CompletedTask; + } + + public (string Path, string SymbolName) CreateMessage (SyntaxTree treeNode, ISymbol symbol) + { + return (treeNode.FilePath, symbol.Name); + } + + public void Dispose () { } + + public ValueTask DisposeAsync () => ValueTask.CompletedTask; + +} diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/ErrorDomainTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/ErrorDomainTransformer.cs new file mode 100644 index 00000000000..73c2c579cb9 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/ErrorDomainTransformer.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Marille; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Transformer.Workers; + + +public class ErrorDomainTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + + public bool UseBackgroundThread { get => true; } + + public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) + { + Console.WriteLine ($"CopyTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + return Task.Delay (10); + } + + public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, + CancellationToken token = new CancellationToken ()) + { + return Task.CompletedTask; + } + + public (string Path, string SymbolName) CreateMessage (SyntaxTree treeNode, ISymbol symbol) + { + return (treeNode.FilePath, symbol.Name); + } + + public void Dispose () { } + + public ValueTask DisposeAsync () => ValueTask.CompletedTask; + +} diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/ITransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/ITransformer.cs new file mode 100644 index 00000000000..9f586727759 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/ITransformer.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Marille; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Transformer.Workers; + +interface ITransformer : IWorker, IErrorWorker where T : struct { + + T CreateMessage (SyntaxTree treeNode, ISymbol symbol); +} diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs index 07f71cf4e1f..7032f8e853b 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs @@ -2,26 +2,32 @@ // Licensed under the MIT License. using Marille; +using Microsoft.CodeAnalysis; namespace Microsoft.Macios.Transformer.Workers; -public class ProtocolTransformer (string destinationDirectory) : IWorker<(string Path, string SymbolName)>, IErrorWorker<(string Path, string Example)> { +public class ProtocolTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { Console.WriteLine ($"ProtocolTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); return Task.Delay (10); } - public Task ConsumeAsync ((string Path, string Example) message, Exception exception, + public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { return Task.CompletedTask; } + public (string Path, string SymbolName) CreateMessage (SyntaxTree treeNode, ISymbol symbol) + { + return (treeNode.FilePath, symbol.Name); + } + public void Dispose () { } public ValueTask DisposeAsync () => ValueTask.CompletedTask; - public bool UseBackgroundThread { get => true; } } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs index b5f13dc964f..f04d83c4131 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs @@ -2,10 +2,13 @@ // Licensed under the MIT License. using Marille; +using Microsoft.CodeAnalysis; namespace Microsoft.Macios.Transformer.Workers; -public class SmartEnumTransformer (string destinationDirectory) : IWorker<(string Path, string SymbolName)>, IErrorWorker<(string Path, string Example)> { +public class SmartEnumTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + + public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { @@ -13,15 +16,19 @@ public class SmartEnumTransformer (string destinationDirectory) : IWorker<(strin return Task.Delay (10); } - public Task ConsumeAsync ((string Path, string Example) message, Exception exception, + public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { return Task.CompletedTask; } + public (string Path, string SymbolName) CreateMessage (SyntaxTree treeNode, ISymbol symbol) + { + return (treeNode.FilePath, symbol.Name); + } + public void Dispose () { } public ValueTask DisposeAsync () => ValueTask.CompletedTask; - public bool UseBackgroundThread { get => true; } } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs index fbfcc1c7dc5..287ba445a5b 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs @@ -2,26 +2,32 @@ // Licensed under the MIT License. using Marille; +using Microsoft.CodeAnalysis; namespace Microsoft.Macios.Transformer.Workers; -public class StrongDictionaryTransformer (string destinationDirectory) : IWorker<(string Path, string SymbolName)>, IErrorWorker<(string Path, string Example)> { +public class StrongDictionaryTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { Console.WriteLine ($"StrongDictionaryTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); return Task.Delay (10); } - public Task ConsumeAsync ((string Path, string Example) message, Exception exception, + public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { return Task.CompletedTask; } + public (string Path, string SymbolName) CreateMessage (SyntaxTree treeNode, ISymbol symbol) + { + return (treeNode.FilePath, symbol.Name); + } + public void Dispose () { } public ValueTask DisposeAsync () => ValueTask.CompletedTask; - public bool UseBackgroundThread { get => true; } } diff --git a/src/rgen/rgen.sln b/src/rgen/rgen.sln index 12549b0432a..cf035050472 100644 --- a/src/rgen/rgen.sln +++ b/src/rgen/rgen.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# +# Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Generator", "Microsoft.Macios.Generator\Microsoft.Macios.Generator.csproj", "{8E9CF45D-E836-447E-9290-03A9CACE2704}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Generator.Sample", "Microsoft.Macios.Generator.Sample\Microsoft.Macios.Generator.Sample.csproj", "{AD0A1FDC-350F-47E2-AA9D-A6F32793C130}" @@ -18,6 +18,7 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Transformer", "Microsoft.Macios.Transformer\Microsoft.Macios.Transformer.csproj", "{D05D2AAA-71C9-49C9-B344-2F8C251E60DB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Transformer.Tests", "..\..\tests\rgen\Microsoft.Macios.Transformer.Tests\Microsoft.Macios.Transformer.Tests.csproj", "{BE23E467-7971-4439-BEE8-220B77F40027}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Bindings.CodeFixers", "Microsoft.Macios.Bindings.CodeFixers\Microsoft.Macios.Bindings.CodeFixers.csproj", "{4986D2E4-89B0-43A3-9879-93ED236C265D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Bindings.CodeFixers.Tests", "..\..\tests\rgen\Microsoft.Macios.Bindings.CodeFixers.Tests\Microsoft.Macios.Bindings.CodeFixers.Tests.csproj", "{E7928D64-8E45-40BF-B393-732FF20D35E7}" diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/BaseTransformerTestClass.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/BaseTransformerTestClass.cs new file mode 100644 index 00000000000..2ec6c8e0a22 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/BaseTransformerTestClass.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xamarin.Tests; +using Xamarin.Utils; + +namespace Microsoft.Macios.Transformer.Tests; + +/// +/// Base class that allows to test the transformer. +/// +public class BaseTransformerTestClass { + + // list of the defines for each platform, this is passed to the parser to ensure that + // we are testing the platforms as if they were being compiled. + readonly Dictionary platformDefines = new () { + { ApplePlatform.iOS, new [] { "__IOS__" } }, + { ApplePlatform.TVOS, new [] { "__TVOS__" } }, + { ApplePlatform.MacOSX, new [] { "__MACOS__" } }, + { ApplePlatform.MacCatalyst, new [] { "__MACCATALYST__" } }, + }; + + protected Compilation CreateCompilation (ApplePlatform platform, [CallerMemberName] string name = "", params (string Source, string Path) [] sources) + { + // get the dotnet bcl and fully load it for the test. + var references = Directory.GetFiles (Configuration.DotNetBclDir, "*.dll") + .Select (assembly => MetadataReference.CreateFromFile (assembly)).ToList (); + + // get the dll for the current platform, this is needed because that way we will get the attributes that + // are used in the old dlls that are needed to test the transformer. + var targetFramework = TargetFramework.GetTargetFramework (platform, isDotNet: true); + var platformDll = Configuration.GetBaseLibrary (targetFramework); + if (!string.IsNullOrEmpty (platformDll)) { + references.Add (MetadataReference.CreateFromFile (platformDll)); + } else { + throw new InvalidOperationException ($"Could not find platform dll for {platform}"); + } + // include the bgen attributes to the compilation, otherwise the transformer will not work. + var sourcesList = sources.ToList (); + if (Configuration.TryGetRootPath (out var rootPath)) { + var oldVersionAttrs = Path.Combine (rootPath, "src", "ObjCRuntime", "PlatformAvailability.cs"); + sourcesList.Add ((File.ReadAllText (oldVersionAttrs), oldVersionAttrs)); + + var oldBgenAttrs = Path.Combine (rootPath, "src", "bgen", "Attributes.cs"); + sourcesList.Add ((File.ReadAllText (oldBgenAttrs), oldBgenAttrs)); + } + + var parseOptions = new CSharpParseOptions (LanguageVersion.Latest, DocumentationMode.None, preprocessorSymbols: ["COREBUILD"]); + var trees = sourcesList.Select ( + s => CSharpSyntaxTree.ParseText (s.Source, parseOptions, s.Path)) + .ToImmutableArray (); + + var options = new CSharpCompilationOptions (OutputKind.NetModule) + .WithAllowUnsafe (true); + + return CSharpCompilation.Create (name, trees, references, options); + } + +} diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/Microsoft.Macios.Transformer.Tests.csproj b/tests/rgen/Microsoft.Macios.Transformer.Tests/Microsoft.Macios.Transformer.Tests.csproj index e676a0a7b31..7ee72ccb941 100644 --- a/tests/rgen/Microsoft.Macios.Transformer.Tests/Microsoft.Macios.Transformer.Tests.csproj +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/Microsoft.Macios.Transformer.Tests.csproj @@ -19,40 +19,32 @@ - + + - - external\Configuration.cs - - - external\ConfigurationXUnit.cs - - - external\Profile.cs - - - external\ExecutionHelper.cs - - - external\ApplePlatform.cs - - - external\TargetFramework.cs - - - external\StringUtils.cs - - - external\Execution.cs - - - external\SdkVersions.cs - - - external\Cache.cs - + + external\Configuration.cs + + + external\ConfigurationXUnit.cs + + + external\Profile.cs + + + external\ExecutionHelper.cs + + + external\StringUtils.cs + + + external\Execution.cs + + + external\Cache.cs + diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/TransformerTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/TransformerTests.cs new file mode 100644 index 00000000000..3d573705138 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/TransformerTests.cs @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Transformer.Workers; +using Xamarin.Tests; +using Xamarin.Utils; + +namespace Microsoft.Macios.Transformer.Tests; + +public class TransformerTests : BaseTransformerTestClass, IDisposable { + string targetDirectory; + + public TransformerTests () + { + targetDirectory = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); + Directory.CreateDirectory (targetDirectory); + } + + + class TestDataSkipTests : IEnumerable { + public IEnumerator GetEnumerator () + { + const string sampleCode = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[NoTV] +[MacCatalyst (13, 1)] +[DisableDefaultCtor] +[Abstract] +[BaseType (typeof (NSObject))] +interface UIFeedbackGenerator : UIInteraction { + + [iOS (17, 5), MacCatalyst (17, 5)] + [Static] + [Export (""feedbackGeneratorForView:"")] + UIFeedbackGenerator GetFeedbackGenerator (UIView forView); + + [Export (""prepare"")] + void Prepare (); +} +"; + // correct path, not namespaces, do not skip and destination + yield return [(sampleCode, "/some/random/path.cs"), null!, false, "Test"]; + + // correct path, namespaces, do not skip and destination + yield return [(sampleCode, "/some/random/path.cs"), new [] { "Test" }, false, "Test"]; + + // correct path, namespaces, do not skip and destination + yield return [(sampleCode, "/some/random/path.cs"), new [] { "UIKit" }, true, null!]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + public void SkipTests (ApplePlatform platform, (string Source, string Path) source, string []? targetNamespaces, + bool expectedResult, string? expectedDestination) + { + // create a compilation used to create the transformer + var compilation = CreateCompilation (platform, sources: source); + var syntaxTree = compilation.SyntaxTrees.FirstOrDefault (); + Assert.NotNull (syntaxTree); + + var semanticModel = compilation.GetSemanticModel (syntaxTree); + Assert.NotNull (semanticModel); + + var declaration = syntaxTree.GetRoot () + .DescendantNodes ().OfType () + .FirstOrDefault (); + Assert.NotNull (declaration); + + var symbol = semanticModel.GetDeclaredSymbol (declaration); + Assert.NotNull (symbol); + + var transformer = new Transformer (targetDirectory, compilation, targetNamespaces); + if (expectedDestination is not null) + expectedDestination = Path.Combine (targetDirectory, expectedDestination); + Assert.Equal (expectedResult, transformer.Skip (syntaxTree, symbol, out var destination)); + Assert.Equal (expectedDestination, destination); + } + + class TestDataSelectTopicTests : IEnumerable { + public IEnumerator GetEnumerator () + { + const string path = "/path/to/source.cs"; + const string errorDomain = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[NoTV, NoMacCatalyst, iOS (18, 2)] +[ErrorDomain (""UIApplicationCategoryDefaultErrorDomain"")] +[Native] +public enum UIApplicationCategoryDefaultErrorCode : long { + RateLimited = 1, +} +"; + + yield return [(errorDomain, path), nameof (ErrorDomainTransformer)]; + + const string smartEnum = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +public enum UIAccessibilityTraits : long { + [Field (""UIAccessibilityTraitNone"")] + None, + + [Field (""UIAccessibilityTraitButton"")] + Button, + + [Field (""UIAccessibilityTraitLink"")] + Link, + + [Field (""UIAccessibilityTraitHeader"")] + Header, + + [Field (""UIAccessibilityTraitSearchField"")] + SearchField, +} +"; + + yield return [(smartEnum, path), nameof (SmartEnumTransformer)]; + + const string normalEnum = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +public enum UIAccessibilityTraits : long { + None, + Button, + Link, + Header, + SearchField, +} +"; + + yield return [(normalEnum, path), nameof (CopyTransformer)]; + + const string category = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[TV (18, 0), iOS (18, 0), MacCatalyst (18, 0)] +[Category] +[BaseType (typeof (NSObject))] +interface NSObject_UIAccessibilityHitTest { + [Export (""accessibilityHitTest:withEvent:"")] + [return: NullAllowed] + NSObject AccessibilityHitTest (CGPoint point, [NullAllowed] UIEvent withEvent); +} +"; + + yield return [(category, path), nameof (CategoryTransformer)]; + + const string baseType = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[TV (18, 0), iOS (18, 0), MacCatalyst (18, 0)] +[BaseType (typeof (NSObject))] +interface UIZoomTransitionInteractionContext { + [Export (""location"")] + CGPoint Location { get; } + + [Export (""velocity"")] + CGVector Velocity { get; } + + [Export (""willBegin"")] + bool WillBegin { get; } +} +"; + + yield return [(baseType, path), nameof (ClassTransformer)]; + + const string protocol = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[NoTV, NoMacCatalyst, iOS (18, 0)] +[Protocol (BackwardsCompatibleCodeGeneration = false), Model] +[BaseType (typeof (NSObject))] +interface UITextFormattingViewControllerDelegate { + [Abstract] + [Export (""textFormattingViewController:didChangeValue:"")] + void DidChangeValue (UITextFormattingViewController viewController, UITextFormattingViewControllerChangeValue changeValue); + + [Export (""textFormattingViewController:shouldPresentFontPicker:"")] + bool ShouldPresentFontPicker (UITextFormattingViewController viewController, UIFontPickerViewController fontPicker); + + [Export (""textFormattingViewController:shouldPresentColorPicker:"")] + bool ShouldPresentColorPicker (UITextFormattingViewController viewController, UIColorPickerViewController colorPicker); + + [Export (""textFormattingDidFinish:"")] + void TextFormattingDidFinish (UITextFormattingViewController viewController); +} +"; + + yield return [(protocol, path), nameof (ProtocolTransformer)]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + public void SelectTopicTests (ApplePlatform platform, (string Source, string Path) source, string expectedTopic) + { + // create a compilation used to create the transformer + var compilation = CreateCompilation (platform, sources: source); + var syntaxTree = compilation.SyntaxTrees.FirstOrDefault (); + Assert.NotNull (syntaxTree); + + var semanticModel = compilation.GetSemanticModel (syntaxTree); + Assert.NotNull (semanticModel); + + var declaration = syntaxTree.GetRoot () + .DescendantNodes ().OfType () + .FirstOrDefault (); + Assert.NotNull (declaration); + + var symbol = semanticModel.GetDeclaredSymbol (declaration); + Assert.NotNull (symbol); + + // there is not need for a transformer, we are just testing the topic selection + Assert.Equal (expectedTopic, Transformer.SelectTopic (symbol)); + } + + public void Dispose () + { + // remove the temporary directory + Directory.Delete (targetDirectory, recursive: true); + } +} diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/UnitTest1.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/UnitTest1.cs deleted file mode 100644 index 327ca91936b..00000000000 --- a/tests/rgen/Microsoft.Macios.Transformer.Tests/UnitTest1.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -namespace Microsoft.Macios.Transformer.Tests; - -public class UnitTest1 { - [Fact] - public void Test1 () - { - Assert.True (true); - } -}