diff --git a/src/Makefile.generator b/src/Makefile.generator
index a88c56ec1d50..50f3353b9d12 100644
--- a/src/Makefile.generator
+++ b/src/Makefile.generator
@@ -70,6 +70,6 @@ $(RSP_DIR)/dotnet/%-defines-dotnet.rsp: frameworks.sources Makefile.generator ge
$(Q) ./generate-defines.csharp $@.tmp '$(filter-out $(DOTNET_REMOVED_$(shell echo $* | tr a-z A-Z)_FRAMEWORKS),$($(shell echo $* | tr a-z A-Z)_FRAMEWORKS))'
$(Q) mv $@.tmp $@
-$(DOTNET_BUILD_DIR)/Xamarin.Apple.BindingAttributes.dll: bgen/Attributes.cs Makefile.generator | $(DOTNET_BUILD_DIR)
- $(Q_DOTNET_BUILD) $(SYSTEM_CSC) $(DOTNET_FLAGS) -out:$@ $<
+$(DOTNET_BUILD_DIR)/Xamarin.Apple.BindingAttributes.dll: bgen/Attributes.cs bgen/PlatformName.cs Makefile.generator | $(DOTNET_BUILD_DIR)
+ $(Q_DOTNET_BUILD) $(SYSTEM_CSC) $(DOTNET_FLAGS) -out:$@ bgen/Attributes.cs bgen/PlatformName.cs
diff --git a/src/Makefile.rgenerator b/src/Makefile.rgenerator
index e3bd9f2b997a..61bbed1d2067 100644
--- a/src/Makefile.rgenerator
+++ b/src/Makefile.rgenerator
@@ -1,6 +1,7 @@
# Roslyn code generator
ROSLYN_GENERATOR=$(DOTNET_BUILD_DIR)/common/rgen/Microsoft.Macios.Generator.dll
ROSLYN_GENERATOR_FILES := $(wildcard rgen/Microsoft.Macios.Generator/*.cs)
+ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/*/*.cs)
$(ROSLYN_GENERATOR): Makefile.rgenerator $(ROSLYN_GENERATOR_FILES)
$(Q_DOTNET_BUILD) $(DOTNET) publish rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj $(DOTNET_BUILD_VERBOSITY) /p:Configuration=Debug /p:IntermediateOutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/obj/common/rgen)/ /p:OutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/bin/common/rgen/)/
diff --git a/src/bgen/Attributes.cs b/src/bgen/Attributes.cs
index 0096d35b1d5f..25ad9f46cd6b 100644
--- a/src/bgen/Attributes.cs
+++ b/src/bgen/Attributes.cs
@@ -883,15 +883,6 @@ public class NoMethodAttribute : Attribute {
}
#if NET
-public enum PlatformName : byte {
- None,
- MacOSX,
- iOS,
- WatchOS,
- TvOS,
- MacCatalyst,
-}
-
public enum AvailabilityKind {
Introduced,
Deprecated,
diff --git a/src/bgen/Extensions/PlatformNameExtensions.cs b/src/bgen/Extensions/PlatformNameExtensions.cs
index 982e5eb1bd8f..ea0b3914cac9 100644
--- a/src/bgen/Extensions/PlatformNameExtensions.cs
+++ b/src/bgen/Extensions/PlatformNameExtensions.cs
@@ -1,132 +1,74 @@
-using System;
-using System.IO;
-using ObjCRuntime;
-using Xamarin.Utils;
+using System.Diagnostics.CodeAnalysis;
public static class PlatformNameExtensions {
- public static string GetApplicationClassName (this PlatformName currentPlatform)
+ public static bool TryGetApplicationClassName (this PlatformName currentPlatform, [NotNullWhen (true)] out string? className)
{
switch (currentPlatform) {
case PlatformName.iOS:
case PlatformName.WatchOS:
case PlatformName.TvOS:
case PlatformName.MacCatalyst:
- return "UIApplication";
+ className = "UIApplication";
+ return true;
case PlatformName.MacOSX:
- return "NSApplication";
+ className = "NSApplication";
+ return true;
default:
- throw new BindingException (1047, currentPlatform);
+ className = null;
+ return false;
}
}
- public static int GetXamcoreVersion (this PlatformName currentPlatform)
- {
-#if NET
- return 4;
-#else
- switch (currentPlatform) {
- case PlatformName.MacOSX:
- case PlatformName.iOS:
- return 2;
- case PlatformName.TvOS:
- case PlatformName.WatchOS:
- return 3;
- default:
- return 4;
- }
-#endif
- }
-
- public static string GetCoreImageMap (this PlatformName currentPlatform)
+ public static bool TryGetCoreImageMap (this PlatformName currentPlatform, [NotNullWhen (true)] out string? coreImageMap)
{
switch (currentPlatform) {
case PlatformName.iOS:
case PlatformName.WatchOS:
case PlatformName.TvOS:
case PlatformName.MacCatalyst:
- return "CoreImage";
+ coreImageMap = "CoreImage";
+ return true;
case PlatformName.MacOSX:
- return "Quartz";
+ coreImageMap = "Quartz";
+ return true;
default:
- throw new BindingException (1047, currentPlatform);
+ coreImageMap = null;
+ return false;
}
}
- public static string GetCoreServicesMap (this PlatformName currentPlatform)
+ public static bool TryGetCoreServicesMap (this PlatformName currentPlatform, [NotNullWhen (true)] out string? coreServicesMap)
{
switch (currentPlatform) {
case PlatformName.iOS:
case PlatformName.WatchOS:
case PlatformName.TvOS:
case PlatformName.MacCatalyst:
- return "MobileCoreServices";
- case PlatformName.MacOSX:
- return "CoreServices";
- default:
- throw new BindingException (1047, currentPlatform);
- }
- }
-
- public static string GetPDFKitMap (this PlatformName currentPlatform)
- {
- switch (currentPlatform) {
- case PlatformName.iOS:
- case PlatformName.MacCatalyst:
- return "PDFKit";
- case PlatformName.MacOSX:
- return "Quartz";
- default:
- throw new BindingException (1047, currentPlatform);
- }
- }
-
- public static ApplePlatform AsApplePlatform (this PlatformName platform)
- {
- switch (platform) {
- case PlatformName.iOS:
- return ApplePlatform.iOS;
- case PlatformName.TvOS:
- return ApplePlatform.TVOS;
- case PlatformName.MacCatalyst:
- return ApplePlatform.MacCatalyst;
+ coreServicesMap = "MobileCoreServices";
+ return true;
case PlatformName.MacOSX:
- return ApplePlatform.MacOSX;
- case PlatformName.WatchOS:
- return ApplePlatform.WatchOS;
- case PlatformName.None:
- return ApplePlatform.None;
+ coreServicesMap = "CoreServices";
+ return true;
default:
- throw new ArgumentOutOfRangeException (nameof (platform), platform, $"Unknown platform: {platform}");
+ coreServicesMap = null;
+ return false;
}
}
- static string GetSdkRoot (this PlatformName currentPlatform)
+ public static bool TryGetPDFKitMap (this PlatformName currentPlatform, [NotNullWhen (true)] out string? pdfKitMap)
{
switch (currentPlatform) {
case PlatformName.iOS:
- case PlatformName.WatchOS:
- case PlatformName.TvOS:
case PlatformName.MacCatalyst:
- var sdkRoot = Environment.GetEnvironmentVariable ("MD_MTOUCH_SDK_ROOT");
- if (string.IsNullOrEmpty (sdkRoot))
- sdkRoot = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current";
- return sdkRoot;
+ pdfKitMap = "PDFKit";
+ return true;
case PlatformName.MacOSX:
- var macSdkRoot = Environment.GetEnvironmentVariable ("XamarinMacFrameworkRoot");
- if (string.IsNullOrEmpty (macSdkRoot))
- macSdkRoot = "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current";
- return macSdkRoot;
+ pdfKitMap = "Quartz";
+ return true;
default:
- throw new BindingException (1047, currentPlatform);
+ pdfKitMap = null;
+ return false;
}
}
-
- public static string GetPath (this PlatformName currentPlatform, params string [] paths)
- {
- var fullPaths = new string [paths.Length + 1];
- fullPaths [0] = currentPlatform.GetSdkRoot ();
- Array.Copy (paths, 0, fullPaths, 1, paths.Length);
- return Path.Combine (fullPaths);
- }
}
diff --git a/src/bgen/Extensions/PlatformNameExtensionsBgen.cs b/src/bgen/Extensions/PlatformNameExtensionsBgen.cs
new file mode 100644
index 000000000000..45e1a29ba443
--- /dev/null
+++ b/src/bgen/Extensions/PlatformNameExtensionsBgen.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+using Xamarin.Utils;
+
+public static class PlatformNameExtensionsBgen {
+
+ // wrapper that allows us to use the same code for rgen and bgen
+ public static string GetApplicationClassName (this PlatformName currentPlatform)
+ {
+ if (currentPlatform.TryGetApplicationClassName (out var applicationClassName))
+ return applicationClassName;
+ throw new BindingException (1047, currentPlatform);
+ }
+
+ public static string GetCoreImageMap (this PlatformName currentPlatform)
+ {
+ if (currentPlatform.TryGetCoreImageMap (out var coreImageMap))
+ return coreImageMap;
+ throw new BindingException (1047, currentPlatform);
+ }
+
+ public static string GetCoreServicesMap (this PlatformName currentPlatform)
+ {
+ if (currentPlatform.TryGetCoreServicesMap (out var coreServicesMap))
+ return coreServicesMap;
+ throw new BindingException (1047, currentPlatform);
+ }
+
+ public static string GetPDFKitMap (this PlatformName currentPlatform)
+ {
+ if (currentPlatform.TryGetPDFKitMap (out var pdfKitMap))
+ return pdfKitMap;
+ throw new BindingException (1047, currentPlatform);
+ }
+
+ public static int GetXamcoreVersion (this PlatformName currentPlatform)
+ {
+#if NET
+ return 4;
+#else
+ switch (currentPlatform) {
+ case PlatformName.MacOSX:
+ case PlatformName.iOS:
+ return 2;
+ case PlatformName.TvOS:
+ case PlatformName.WatchOS:
+ return 3;
+ default:
+ return 4;
+ }
+#endif
+ }
+
+ public static ApplePlatform AsApplePlatform (this PlatformName platform)
+ {
+ switch (platform) {
+ case PlatformName.iOS:
+ return ApplePlatform.iOS;
+ case PlatformName.TvOS:
+ return ApplePlatform.TVOS;
+ case PlatformName.MacCatalyst:
+ return ApplePlatform.MacCatalyst;
+ case PlatformName.MacOSX:
+ return ApplePlatform.MacOSX;
+ case PlatformName.WatchOS:
+ return ApplePlatform.WatchOS;
+ case PlatformName.None:
+ return ApplePlatform.None;
+ default:
+ throw new ArgumentOutOfRangeException (nameof (platform), platform, $"Unknown platform: {platform}");
+ }
+ }
+
+ static string GetSdkRoot (this PlatformName currentPlatform)
+ {
+ switch (currentPlatform) {
+ case PlatformName.iOS:
+ case PlatformName.WatchOS:
+ case PlatformName.TvOS:
+ case PlatformName.MacCatalyst:
+ var sdkRoot = Environment.GetEnvironmentVariable ("MD_MTOUCH_SDK_ROOT");
+ if (string.IsNullOrEmpty (sdkRoot))
+ sdkRoot = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current";
+ return sdkRoot;
+ case PlatformName.MacOSX:
+ var macSdkRoot = Environment.GetEnvironmentVariable ("XamarinMacFrameworkRoot");
+ if (string.IsNullOrEmpty (macSdkRoot))
+ macSdkRoot = "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current";
+ return macSdkRoot;
+ default:
+ throw new BindingException (1047, currentPlatform);
+ }
+ }
+
+ public static string GetPath (this PlatformName currentPlatform, params string [] paths)
+ {
+ var fullPaths = new string [paths.Length + 1];
+ fullPaths [0] = currentPlatform.GetSdkRoot ();
+ Array.Copy (paths, 0, fullPaths, 1, paths.Length);
+ return Path.Combine (fullPaths);
+ }
+}
diff --git a/src/bgen/PlatformName.cs b/src/bgen/PlatformName.cs
new file mode 100644
index 000000000000..c17caa5d6bf4
--- /dev/null
+++ b/src/bgen/PlatformName.cs
@@ -0,0 +1,8 @@
+public enum PlatformName : byte {
+ None,
+ MacOSX,
+ iOS,
+ WatchOS,
+ TvOS,
+ MacCatalyst,
+}
diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs
index fe4905a77980..16717f554311 100644
--- a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs
+++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs
@@ -1,3 +1,5 @@
+using ObjCBindings;
+
namespace Microsoft.Macios.Bindings.Analyzer.Sample;
// If you don't see warnings, build the Analyzers Project.
diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj
index bc9520bfbacf..a02eefcf4af5 100644
--- a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj
+++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj
@@ -3,6 +3,7 @@
net$(BundledNETCoreAppTargetFrameworkVersion)
enable
+ APL0003
@@ -11,9 +12,15 @@
+
+ external\BindginTypeAttribute.cs
+
external\Attributes.cs
+
+ external\PlatformName.cs
+
diff --git a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj
index f21144e77c4c..e10b90a1b8fd 100644
--- a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj
+++ b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj
@@ -4,6 +4,7 @@
net$(BundledNETCoreAppTargetFrameworkVersion)
enable
Microsoft.Macios.Generator.Sample
+ APL0003
@@ -11,9 +12,15 @@
+
+ external\BindingTypeAttribute.cs
+
external\Attributes.cs
+
+ external\PlatformName.cs
+
diff --git a/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs b/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs
index 28d28329ae21..7c2427c1473e 100644
--- a/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs
+++ b/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs
@@ -1,3 +1,5 @@
+using ObjCBindings;
+
namespace Microsoft.Macios.Generator.Sample;
// This code will not compile until you build the project with the Source Generators
diff --git a/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs
new file mode 100644
index 000000000000..62ac3b632b01
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs
@@ -0,0 +1,9 @@
+namespace Microsoft.Macios.Generator;
+
+///
+/// Contains all the names of the attributes that are used by the binding generator.
+///
+public static class AttributesNames {
+
+ public static readonly string BindingAttribute = "ObjCBindings.BindingTypeAttribute";
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
index fcc630f160ce..8b7d54e8533f 100644
--- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
+++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
@@ -1,16 +1,26 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
+using Microsoft.Macios.Generator.Context;
+using Microsoft.Macios.Generator.Emitters;
namespace Microsoft.Macios.Generator;
///
-/// A sample source generator that creates a custom report based on class properties. The target class should be annotated with the 'Generators.ReportAttribute' attribute.
-/// When using the source code as a baseline, an incremental source generator is preferable because it reduces the performance overhead.
+/// A sample source generator that creates a custom report based on class properties. The target class should be
+/// annotated with the 'Generators.ReportAttribute' attribute.
+/// When using the source code as a baseline, an incremental source generator is preferable because it reduces
+/// the performance overhead.
///
[Generator]
public class BindingSourceGeneratorGenerator : IIncrementalGenerator {
+ ///
public void Initialize (IncrementalGeneratorInitializationContext context)
{
// Add the binding generator attributes to the compilation. This are only available when the
@@ -19,6 +29,140 @@ public void Initialize (IncrementalGeneratorInitializationContext context)
context.RegisterPostInitializationOutput (ctx => ctx.AddSource (
fileName, SourceText.From (content, Encoding.UTF8)));
}
+
+ // binding can use different 'types'. To be able to generate the code we are going to add a different
+ // function for each of the 'types' we are interested in generating that will be added to the compiler
+ // pipeline
+ AddPipeline (context);
+ AddPipeline (context);
+ AddPipeline (context);
+ }
+
+ ///
+ /// Generic method that adds a provider and a code generator to the pipeline.
+ ///
+ /// The compilation context
+ /// The base type declaration that we are going to generate.
+ static void AddPipeline (IncrementalGeneratorInitializationContext context) where T : BaseTypeDeclarationSyntax
+ {
+ var provider = context.SyntaxProvider
+ .CreateSyntaxProvider (
+ static (s, _) => s is T,
+ (ctx, _) => GetDeclarationForSourceGen (ctx))
+ .Where (t => t.BindingAttributeFound)
+ .Select ((t, _) => t.Declaration);
+
+ context.RegisterSourceOutput (context.CompilationProvider.Combine (provider.Collect ()),
+ ((ctx, t) => GenerateCode (ctx, t.Left, t.Right)));
+ }
+
+ ///
+ /// Generic method that can be used to filter/match a BaseTypeDeclarationSyntax with the BindingTypeAttribute.
+ /// Because our generator is focused only on Enum, Interface and Class declarations we can use a generic method
+ /// that will match the type + the presence of the attribute.
+ ///
+ /// Context used by the generator.
+ /// The BaseTypeDeclarationSyntax we are interested in.
+ /// A tuple that contains the BaseTypeDeclaration that was processed and a boolean that states if it should be processed or not.
+ static (T Declaration, bool BindingAttributeFound) GetDeclarationForSourceGen (GeneratorSyntaxContext context)
+ where T : BaseTypeDeclarationSyntax
+ {
+ var classDeclarationSyntax = Unsafe.As (context.Node);
+
+ // Go through all attributes of the class.
+ foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
+ foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) {
+ if (context.SemanticModel.GetSymbolInfo (attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
+ continue; // if we can't get the symbol, ignore it
+
+ string attributeName = attributeSymbol.ContainingType.ToDisplayString ();
+
+ // Check the full name of the [Binding] attribute.
+ if (attributeName == AttributesNames.BindingAttribute)
+ return (classDeclarationSyntax, true);
+ }
+
+ return (classDeclarationSyntax, false);
+ }
+
+ ///
+ /// Collect the using statements from the class declaration root syntaxt tree and add them to the string builder
+ /// that will be used to generate the code. This way we ensure that we have all the namespaces needed by the
+ /// generated code.
+ ///
+ /// 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)
+ {
+ // 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.
+ var usingDirectives = tree.GetRoot ()
+ .DescendantNodes ()
+ .OfType ()
+ .Select (d => d.Name.ToString ()).ToArray ();
+ 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) {
+ if (string.IsNullOrEmpty (ns))
+ continue;
+ sb.AppendLine ($"using {ns};");
+ }
+ }
+
+ ///
+ /// Generic method that allows to call a emitter for ta type that will emit the binding code. All code generation
+ /// is very similar. Get create a tabbed string builder to write the code with the needed using statemens from
+ /// the original syntax tree and we pass it to the emitter that will generate the code.
+ ///
+ /// The generator context.
+ /// The compilation unit.
+ /// The base type declarations marked by the BindingTypeAttribute.
+ /// The type of type declaration.
+ static void GenerateCode (SourceProductionContext context, Compilation compilation,
+ ImmutableArray baseTypeDeclarations) where T : BaseTypeDeclarationSyntax
+ {
+ var rootContext = new RootBindingContext (compilation);
+ foreach (var baseTypeDeclarationSyntax in baseTypeDeclarations) {
+ var semanticModel = compilation.GetSemanticModel (baseTypeDeclarationSyntax.SyntaxTree);
+ if (semanticModel.GetDeclaredSymbol (baseTypeDeclarationSyntax) is not INamedTypeSymbol namedTypeSymbol)
+ continue;
+
+ // init sb and add all the using statements from the base type declaration
+ var sb = new TabbedStringBuilder (new ());
+ // let people know this is generated code
+ sb.AppendLine ("// ");
+
+ // enable nullable!
+ sb.AppendLine ();
+ 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)) {
+ if (emitter.TryEmit (out var diagnostics)) {
+ // only add file when we do generate code
+ var code = sb.ToString ();
+ context.AddSource ($"{emitter.SymbolName}.g.cs", SourceText.From (code, Encoding.UTF8));
+ } else {
+ // add to the diagnostics and continue to the next possible candidate
+ foreach (Diagnostic diagnostic in diagnostics) {
+ context.ReportDiagnostic (diagnostic);
+ }
+ }
+
+ } else {
+ // we don't have a emitter for this type, so we can't generate the code, add a diagnostic letting the
+ // user we do not support what he is trying to do
+ continue;
+ }
+
+ }
}
}
diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs
new file mode 100644
index 000000000000..8278c6437e6f
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs
@@ -0,0 +1,16 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Macios.Generator.Context;
+
+class ClassBindingContext : SymbolBindingContext {
+ public string RegisterName { get; init; }
+
+ public ClassBindingContext (RootBindingContext context, SemanticModel semanticModel,
+ INamedTypeSymbol symbol, ClassDeclarationSyntax declarationSyntax)
+ : base (context, semanticModel, symbol, declarationSyntax)
+ {
+ RegisterName =
+ symbol.Name; //TODO: placeholder -> should this be extracted from the BindingTypeAttribute
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs b/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs
new file mode 100644
index 000000000000..0805f661df89
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Macios.Generator.Context;
+
+static class ContextFactory {
+ public static bool TryCreate (RootBindingContext context, SemanticModel semanticModel,
+ INamedTypeSymbol symbol, T declarationSyntax, [NotNullWhen (true)] out ISymbolBindingContext? bindingContext) where T : BaseTypeDeclarationSyntax
+ {
+ bindingContext = declarationSyntax switch {
+ ClassDeclarationSyntax c => Unsafe.As> (
+ new ClassBindingContext (context, semanticModel, symbol, c)),
+ EnumDeclarationSyntax => new SymbolBindingContext (context, semanticModel, symbol, declarationSyntax),
+ _ => null
+ };
+ return bindingContext is not null;
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs
new file mode 100644
index 000000000000..a9bc1f3c242f
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs
@@ -0,0 +1,19 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Macios.Generator.Context;
+
+///
+/// Interface that represents a symbol binding context. We use an interface to allow the usage of a coveriance type,
+/// because it removes the need to a cast.
+///
+/// The base type declaration whose context we have
+interface ISymbolBindingContext where T : BaseTypeDeclarationSyntax {
+ T DeclarationSyntax { get; }
+ string Namespace { get; }
+ string SymbolName { get; }
+ RootBindingContext RootBindingContext { get; init; }
+ SemanticModel SemanticModel { get; init; }
+ INamedTypeSymbol Symbol { get; init; }
+ bool IsStatic { get; }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs
new file mode 100644
index 000000000000..64159cb0657b
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Macios.Generator.Context;
+
+///
+/// Shared context through the entire code generation. This context allows to collect data that will be
+/// later use to generate the Trampoline.g.cs file. once all classed are processed.
+///
+/// The class also provides a number or properties that will allow to determine the platform we are binding and access
+/// to the current compilation.
+///
+class RootBindingContext {
+ readonly Dictionary _libraries = new ();
+
+ public PlatformName CurrentPlatform { get; set; }
+ public Compilation Compilation { get; set; }
+ public bool BindThirdPartyLibrary { get; set; }
+
+ 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;
+ }
+ }
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs
new file mode 100644
index 000000000000..b3d0c24cfbb8
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs
@@ -0,0 +1,36 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Macios.Generator.Context;
+
+class SymbolBindingContext {
+
+ public RootBindingContext RootBindingContext { get; init; }
+ public SemanticModel SemanticModel { get; init; }
+ public INamedTypeSymbol Symbol { get; init; }
+
+ public bool IsStatic => Symbol.IsStatic;
+
+ public SymbolBindingContext (RootBindingContext rootBindingContext,
+ SemanticModel semanticModel, INamedTypeSymbol symbol)
+ {
+ RootBindingContext = rootBindingContext;
+ SemanticModel = semanticModel;
+ Symbol = symbol;
+ }
+
+}
+
+class SymbolBindingContext : SymbolBindingContext, ISymbolBindingContext where T : BaseTypeDeclarationSyntax {
+
+ public T DeclarationSyntax { get; }
+ public string Namespace => Symbol.ContainingNamespace.ToDisplayString ();
+ public string SymbolName => Symbol.Name;
+
+ public SymbolBindingContext (RootBindingContext rootBindingContext,
+ SemanticModel semanticModel, INamedTypeSymbol symbol, T declarationSyntax)
+ : base (rootBindingContext, semanticModel, symbol)
+ {
+ DeclarationSyntax = declarationSyntax;
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs
new file mode 100644
index 000000000000..0d56a01a1c10
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs
@@ -0,0 +1,24 @@
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+using Microsoft.Macios.Generator.Context;
+
+namespace Microsoft.Macios.Generator.Emitters;
+
+class ClassEmitter (ClassBindingContext context, TabbedStringBuilder builder) : ICodeEmitter {
+ public string SymbolName => context.SymbolName;
+
+ public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics)
+ {
+
+ builder.AppendLine ();
+ diagnostics = null;
+ // add the namespace and the class declaration
+ using (var namespaceBlock = builder.CreateBlock ($"namespace {context.Namespace}", true)) {
+ using (var classBlock = namespaceBlock.CreateBlock ($"public partial class {SymbolName}", true)) {
+ classBlock.AppendLine ("// TODO: add binding code here");
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs
new file mode 100644
index 000000000000..7d9237ce35b5
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs
@@ -0,0 +1,23 @@
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Macios.Generator.Context;
+
+namespace Microsoft.Macios.Generator.Emitters;
+
+///
+/// Returns the emitter that is related to the provided declaration type.
+///
+static class EmitterFactory {
+ public static bool TryCreate (ISymbolBindingContext context, TabbedStringBuilder builder,
+ [NotNullWhen (true)] out ICodeEmitter? emitter)
+ where T : BaseTypeDeclarationSyntax
+ {
+ emitter = context switch {
+ ClassBindingContext classContext => new ClassEmitter (classContext, builder),
+ ISymbolBindingContext enumContext => new EnumEmitter (enumContext, builder),
+ ISymbolBindingContext interfaceContext => new InterfaceEmitter (interfaceContext, builder),
+ _ => null
+ };
+ return emitter is not null;
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs
new file mode 100644
index 000000000000..b946986eac9f
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs
@@ -0,0 +1,19 @@
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Macios.Generator.Context;
+
+namespace Microsoft.Macios.Generator.Emitters;
+
+class EnumEmitter (ISymbolBindingContext context, TabbedStringBuilder builder)
+ : ICodeEmitter {
+
+ public string SymbolName => $"{context.SymbolName}Extensions";
+
+ public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics)
+ {
+ diagnostics = null;
+ return true;
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs
new file mode 100644
index 000000000000..d78584e82bb1
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs
@@ -0,0 +1,13 @@
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Macios.Generator.Emitters;
+
+///
+/// Interface to be implemented by all those classes that know how to emit code for a binding.
+///
+interface ICodeEmitter {
+ public string SymbolName { get; }
+ bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics);
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs
new file mode 100644
index 000000000000..23f77d0c4d76
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs
@@ -0,0 +1,16 @@
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Macios.Generator.Context;
+
+namespace Microsoft.Macios.Generator.Emitters;
+
+class InterfaceEmitter (ISymbolBindingContext context, TabbedStringBuilder builder) : ICodeEmitter {
+ public string SymbolName { get; } = string.Empty;
+ public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics)
+ {
+ diagnostics = null;
+ return true;
+ }
+}
diff --git a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj
index f9c73ed0396a..7da28f54684d 100644
--- a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj
+++ b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj
@@ -22,5 +22,20 @@
+
+
+ <_Parameter1>Microsoft.Macios.Generator.Tests
+
+
+
+
+
+ external\PlatformName.cs
+
+
+ external\PlatformNameExtensions.cs
+
+
+
diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs
new file mode 100644
index 000000000000..530279b8644c
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Microsoft.Macios.Generator;
+
+
+///
+/// String builder wrapper that keeps track of the current indentation level by abusing the IDisposable pattern. Rather
+/// than dispose data, what the IDisposable pattern allows is to create a new block with an increased indentation, that
+/// way we do not need to keep track of the current indentation level.
+///
+/// var classBlock = new TabbedStringBuilder (sb);
+/// classBlock.AppendLine ("public static NSString? GetConstant (this {enumSymbol.Name} self)");
+/// // open a new {} block, no need to keep track of the indentation, the new block has it
+/// using (var getConstantBlock = classBlock.CreateBlock (isBlock: true)) {
+/// // write the contents of the method here.
+/// }
+///
+///
+class TabbedStringBuilder : IDisposable {
+ readonly StringBuilder sb;
+ readonly uint tabCount;
+ readonly bool isBlock;
+ bool disposed;
+
+ ///
+ /// Created a new tabbed string builder that will use the given sb to write code.
+ ///
+ /// The string builder to be used to write code.
+ /// The original tab size.
+ /// States if we are creating a {} block.
+ public TabbedStringBuilder (StringBuilder builder, uint currentCount = 0, bool block = false)
+ {
+ sb = builder;
+ isBlock = block;
+ if (isBlock) {
+ // increase by 1 because we are in a block
+ tabCount = currentCount;
+ WriteTabs ().Append ('{').AppendLine ();
+ tabCount++;
+ } else {
+ tabCount = currentCount;
+ }
+ }
+
+ StringBuilder WriteTabs () => sb.Append ('\t', (int) tabCount);
+
+ ///
+ /// Append a new empty line to the string builder using the correct tab size.
+ ///
+ /// The current tabbed string builder.
+ public TabbedStringBuilder AppendLine ()
+ {
+ sb.AppendLine ();
+ return this;
+ }
+
+ ///
+ /// Append conteng, but do not add a \n
+ ///
+ /// The content to append.
+ /// The current builder.
+ public TabbedStringBuilder Append (string line)
+ {
+ if (string.IsNullOrWhiteSpace (line)) {
+ sb.Append (line);
+ } else {
+ WriteTabs ().Append (line);
+ }
+ return this;
+ }
+
+ ///
+ /// Append conteng, but do not add a \n
+ ///
+ /// The content to append.
+ /// The current builder.
+ public TabbedStringBuilder Append (ReadOnlySpan span)
+ {
+ if (span.IsWhiteSpace ()) {
+ sb.Append (span);
+ } else {
+ WriteTabs ().Append (span);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Append a new tabbed line.
+ ///
+ /// The line to append.
+ /// The current builder.
+ public TabbedStringBuilder AppendLine (string line)
+ {
+ if (string.IsNullOrWhiteSpace (line)) {
+ sb.AppendLine (line);
+ } else {
+ WriteTabs ().AppendLine (line);
+ }
+ return this;
+ }
+
+ ///
+ /// Append a new tabbed lien from the span.
+ ///
+ /// The line to append.
+ /// The current builder.
+ public TabbedStringBuilder AppendLine (ReadOnlySpan span)
+ {
+ if (span.IsWhiteSpace ()) {
+ sb.Append (span).AppendLine ();
+ } else {
+ WriteTabs ().Append (span).AppendLine ();
+ }
+
+ return this;
+ }
+
+ public TabbedStringBuilder Append (ref DefaultInterpolatedStringHandler handler)
+ {
+ WriteTabs ().Append (handler.ToStringAndClear ());
+ return this;
+ }
+
+ public TabbedStringBuilder AppendLine (ref DefaultInterpolatedStringHandler handler)
+ {
+ WriteTabs ().Append (handler.ToStringAndClear ()).AppendLine ();
+ return this;
+ }
+
+ ///
+ /// Append a new raw literal by prepending the correct indentation.
+ ///
+ /// The raw string to append.
+ /// The current builder.
+ public TabbedStringBuilder AppendRaw (string rawString)
+ {
+ // we will split the raw string in lines and then append them so that the
+ // tabbing is correct
+ var span = rawString.AsSpan ();
+ var lines = rawString.AsSpan ().Split ('\n');
+ var count = 0;
+ foreach (var range in lines) {
+ if (count > 0)
+ AppendLine ();
+ var line = rawString.AsSpan (range);
+ Append (line);
+ count++;
+ }
+ return this;
+ }
+
+ ///
+ /// Append the generated code attribute to the current string builder. Added for convenience.
+ ///
+ /// If the binding is Optimizable or not.
+ /// The current builder.
+ public TabbedStringBuilder AppendGeneratedCodeAttribute (bool optimizable = true)
+ {
+ if (optimizable) {
+ const string attr = "[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]";
+ AppendLine (attr);
+ } else {
+ const string attr = "[BindingImpl (BindingImplOptions.GeneratedCode)]";
+ AppendLine (attr);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Append a EditorBrowsable attribute. Added for convenience.
+ ///
+ /// The current builder.
+ public TabbedStringBuilder AppendEditorBrowsableAttribute ()
+ {
+ const string attr = "[EditorBrowsable (EditorBrowsableState.Never)]";
+ AppendLine (attr);
+ return this;
+ }
+
+ ///
+ /// Create a bew empty block.
+ ///
+ /// If it is a block that uses {} or not.
+ /// The new bloc.
+ public TabbedStringBuilder CreateBlock (bool block) => CreateBlock (string.Empty, block);
+
+ ///
+ /// Create a new block with the given line. This method can be used to write if/else statements.
+ ///
+ /// The new line to append
+ /// If the new line should considered a block.
+ /// The current builder.
+ public TabbedStringBuilder CreateBlock (string line, bool block)
+ {
+ if (!string.IsNullOrEmpty (line)) {
+ WriteTabs ().AppendLine (line);
+ }
+
+ return new TabbedStringBuilder (sb, tabCount, block);
+ }
+
+ ///
+ /// Return the string builder as a string.
+ ///
+ ///
+ public override string ToString ()
+ {
+ Dispose ();
+ return sb.ToString ();
+ }
+
+ ///
+ /// Does not really dispose anything, it just closes the current block.
+ ///
+ public void Dispose ()
+ {
+ if (disposed || !isBlock) return;
+
+ disposed = true;
+ sb.Append ('\t', (int) tabCount - 1);
+ sb.Append ('}').AppendLine ();
+ }
+}
diff --git a/tests/generator/PlatformNameExtensionsTests.cs b/tests/generator/PlatformNameExtensionsTests.cs
index e955c69ab010..6daef60a5042 100644
--- a/tests/generator/PlatformNameExtensionsTests.cs
+++ b/tests/generator/PlatformNameExtensionsTests.cs
@@ -17,7 +17,10 @@ public class PlatformNameExtensions {
[TestCase (PlatformName.MacCatalyst, "UIApplication")]
[TestCase (PlatformName.MacOSX, "NSApplication")]
public void GetApplicationClassNameTest (PlatformName platformName, string expected)
- => Assert.AreEqual (expected, platformName.GetApplicationClassName ());
+ {
+ Assert.True (platformName.TryGetApplicationClassName (out var applicationClassName));
+ Assert.AreEqual (expected, applicationClassName);
+ }
[TestCase (PlatformName.MacOSX, 2)]
[TestCase (PlatformName.iOS, 2)]
diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs
index 48cba5be3fbd..67c715043299 100644
--- a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs
+++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Xamarin.Tests;
@@ -27,10 +28,11 @@ public class Examples {
var compilation = CreateCompilation (nameof (CompareGeneratedCode), platform, inputText);
var diagnostics = await RunAnalyzer (new BindingTypeSemanticAnalyzer (), compilation);
- Assert.Single (diagnostics);
+ var analyzerDiagnotics = diagnostics
+ .Where (d => d.Id == BindingTypeSemanticAnalyzer.DiagnosticId).ToArray ();
+ Assert.Single (analyzerDiagnotics);
// verify the diagnostic message
- var location = diagnostics [0].Location;
- VerifyDiagnosticMessage (diagnostics [0], BindingTypeSemanticAnalyzer.DiagnosticId,
+ VerifyDiagnosticMessage (analyzerDiagnotics [0], BindingTypeSemanticAnalyzer.DiagnosticId,
DiagnosticSeverity.Error, "The binding type 'Test.Examples' must declared as a partial class");
}
}
diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs
index a101ccbbd214..fd8738e7d3e3 100644
--- a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs
+++ b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs
@@ -1,5 +1,4 @@
using System.Linq;
-using Microsoft.CodeAnalysis.CSharp;
using Xamarin.Tests;
using Xamarin.Utils;
using Xunit;
@@ -9,36 +8,52 @@ namespace Microsoft.Macios.Generator.Tests;
// Unit test that ensures that all the generator attributes are correctly added in the compilation initialization
public class BindingSourceGeneratorGeneratorTests : BaseGeneratorTestClass {
- const string SampleBindingType = @"
+ const string usingImportInput = @"
+using System;
+using Foundation;
+using ObjCBindings;
namespace TestNamespace;
[BindingType (Name = ""AVAudioPCMBuffer"")]
-interface AVAudioPcmBuffer : AVAudioBuffer {
+public partial class AVAudioPcmBuffer : AVAudioBuffer {
+}
+";
+
+ const string usingImportOutput = @"//
+
+#nullable enable
+
+using System;
+using Foundation;
+using ObjCBindings;
+
+namespace TestNamespace
+{
+ public partial class AVAudioPcmBuffer
+ {
+ // TODO: add binding code here
+ }
}
";
[Theory]
- [PlatformInlineData (ApplePlatform.iOS)]
- [PlatformInlineData (ApplePlatform.TVOS)]
- [PlatformInlineData (ApplePlatform.MacOSX)]
- [PlatformInlineData (ApplePlatform.MacCatalyst)]
- public void AttributesAreNotPresent (ApplePlatform platform)
+ [PlatformInlineData (ApplePlatform.iOS, usingImportInput, usingImportOutput)]
+ [PlatformInlineData (ApplePlatform.TVOS, usingImportInput, usingImportOutput)]
+ [PlatformInlineData (ApplePlatform.MacOSX, usingImportInput, usingImportOutput)]
+ [PlatformInlineData (ApplePlatform.MacCatalyst, usingImportInput, usingImportOutput)]
+ public void CorrectUsingImports (ApplePlatform platform, string input, string expectedOutput)
{
// We need to create a compilation with the required source code.
- var compilation = CreateCompilation (nameof (AttributesAreNotPresent),
- platform, SampleBindingType);
+ var compilation = CreateCompilation (nameof (CorrectUsingImports),
+ platform, usingImportInput);
// Run generators and retrieve all results.
var runResult = _driver.RunGenerators (compilation).GetRunResult ();
+ Assert.Empty (runResult.Diagnostics);
// ensure that we do have all the needed attributes present
- var expectedGeneratedAttributes = new [] {
- "BindingTypeAttribute.g.cs",
- };
-
- foreach (string generatedAttribute in expectedGeneratedAttributes) {
- var generatedFile = runResult.GeneratedTrees.SingleOrDefault (t => t.FilePath.EndsWith (generatedAttribute));
- Assert.Null (generatedFile);
- }
+ var generatedFile = runResult.GeneratedTrees.SingleOrDefault (t => t.FilePath.EndsWith ("AVAudioPcmBuffer.g.cs"));
+ Assert.NotNull (generatedFile);
+ Assert.Equal (expectedOutput, generatedFile.GetText ().ToString ());
}
}
diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs
new file mode 100644
index 000000000000..0333e5b138d0
--- /dev/null
+++ b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs
@@ -0,0 +1,209 @@
+using System.Text;
+using Xunit;
+
+namespace Microsoft.Macios.Generator.Tests;
+
+public class TabbedStringBuilderTests {
+ StringBuilder sb;
+
+ public TabbedStringBuilderTests ()
+ {
+ sb = new ();
+ }
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void ConstructorNotBlockTest (uint tabCount, string expectedTabs)
+ {
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ block.AppendLine ("Test");
+ result = block.ToString ();
+ }
+ Assert.Equal ($"{expectedTabs}Test\n", result);
+ }
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void ConstructorBlockTest (uint tabCount, string expectedTabs)
+ {
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount, true)) {
+ block.AppendLine ("Test");
+ result = block.ToString ();
+ }
+ Assert.Equal ($"{expectedTabs}{{\n{expectedTabs}\tTest\n{expectedTabs}}}\n", result);
+ }
+
+ [Theory]
+ [InlineData (0)]
+ [InlineData (1)]
+ [InlineData (5)]
+ public void AppendLineTest (uint tabCount)
+ {
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ block.AppendLine ();
+ result = block.ToString ();
+ }
+ // an empty line should have not tabs
+ Assert.Equal ("\n", result);
+ }
+
+ [Theory]
+ [InlineData ("// test comment", 0, "")]
+ [InlineData ("var t = 1;", 1, "\t")]
+ [InlineData ("Console.WriteLine (\"1\");", 5, "\t\t\t\t\t")]
+ public void AppendLineStringTest (string line, uint tabCount, string expectedTabs)
+ {
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount, true)) {
+ block.AppendLine (line);
+ result = block.ToString ();
+ }
+ Assert.Equal ($"{expectedTabs}{{\n{expectedTabs}\t{line}\n{expectedTabs}}}\n", result);
+ }
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void AppendInterpolatedLineTest (uint tabCount, string expectedTabs)
+ {
+ string result;
+ var val1 = "Hello";
+ var val2 = "World";
+ var val3 = '!';
+ var line = "Hello World!";
+ var expected = $"{expectedTabs}{{\n{expectedTabs}\t{line}\n{expectedTabs}}}\n";
+ using (var block = new TabbedStringBuilder (sb, tabCount, true)) {
+ block.AppendLine ($"{val1} {val2}{val3}");
+ result = block.ToString ();
+ }
+ Assert.Equal (expected, result);
+ }
+
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void AppendRawTest (uint tabCount, string expectedTabs)
+ {
+ var input = @"
+## Raw string
+Because we are using a raw string we expected:
+ 1. The string to be split in lines
+ 2. All lines should have the right indentation
+ - This means nested one
+ 3. And all lines should have the correct tabs
+";
+ var expected = $@"
+{expectedTabs}## Raw string
+{expectedTabs}Because we are using a raw string we expected:
+{expectedTabs} 1. The string to be split in lines
+{expectedTabs} 2. All lines should have the right indentation
+{expectedTabs} - This means nested one
+{expectedTabs} 3. And all lines should have the correct tabs
+";
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ block.AppendRaw (input);
+ result = block.ToString ();
+ }
+ Assert.Equal (expected, result);
+ }
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void AppendGeneratedCodeAttributeTest (uint tabCount, string expectedTabs)
+ {
+ var expected = $"{expectedTabs}[BindingImpl (BindingImplOptions.GeneratedCode)]\n";
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ block.AppendGeneratedCodeAttribute (false);
+ result = block.ToString ();
+ }
+ Assert.Equal (expected, result);
+ }
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void AppendGeneratedCodeAttributeOptimizableTest (uint tabCount, string expectedTabs)
+ {
+ var expected = $"{expectedTabs}[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]\n";
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ block.AppendGeneratedCodeAttribute ();
+ result = block.ToString ();
+ }
+ Assert.Equal (expected, result);
+ }
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void AppendEditorBrowsableAttributeTest (uint tabCount, string expectedTabs)
+ {
+ var expected = $"{expectedTabs}[EditorBrowsable (EditorBrowsableState.Never)]\n";
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ block.AppendEditorBrowsableAttribute ();
+ result = block.ToString ();
+ }
+ Assert.Equal (expected, result);
+ }
+
+ [Theory]
+ [InlineData (0, "")]
+ [InlineData (1, "\t")]
+ [InlineData (5, "\t\t\t\t\t")]
+ public void CreateEmptyBlockTest (uint tabCount, string expectedTabs)
+ {
+ var blockContent = "// the test";
+ var expected = $@"{expectedTabs}{{
+{expectedTabs}{"\t"}{blockContent}
+{expectedTabs}}}
+";
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ using (var nested = block.CreateBlock (true)) {
+ nested.AppendLine (blockContent);
+ }
+ result = block.ToString ();
+ }
+ Assert.Equal (expected, result);
+ }
+
+ [Theory]
+ [InlineData (0, "", "if (true)")]
+ [InlineData (1, "\t", "using (var t = new StringBuilder)")]
+ [InlineData (5, "\t\t\t\t\t", "fixed (*foo)")]
+ public void CreateBlockTest (uint tabCount, string expectedTabs, string blockType)
+ {
+ var blockContent = "// the test";
+ var expected = $@"{expectedTabs}{blockType}
+{expectedTabs}{{
+{expectedTabs}{"\t"}{blockContent}
+{expectedTabs}}}
+";
+ string result;
+ using (var block = new TabbedStringBuilder (sb, tabCount)) {
+ using (var nested = block.CreateBlock (blockType, true)) {
+ nested.AppendLine (blockContent);
+ }
+ result = block.ToString ();
+ }
+ Assert.Equal (expected, result);
+ }
+
+}