Skip to content
Merged
70 changes: 37 additions & 33 deletions src/Controls/src/BindingSourceGen/BindingCodeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,11 @@

namespace Microsoft.Maui.Controls.BindingSourceGen;

public sealed class BindingCodeWriter
public static class BindingCodeWriter
{
public static string GeneratedCodeAttribute => $"[GeneratedCodeAttribute(\"{typeof(BindingCodeWriter).Assembly.FullName}\", \"{typeof(BindingCodeWriter).Assembly.GetName().Version}\")]";

public string GenerateCode()
{
if (_bindings.Count == 0)
{
return string.Empty;
}

return DoGenerateCode();
}

private string DoGenerateCode() => $$"""
public static string GenerateCommonCode() => $$"""
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a .NET MAUI source generator.
Expand All @@ -35,7 +25,7 @@ namespace System.Runtime.CompilerServices

{{GeneratedCodeAttribute}}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
internal sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
Expand All @@ -52,16 +42,11 @@ public InterceptsLocationAttribute(string filePath, int line, int column)

namespace Microsoft.Maui.Controls.Generated
{
using System;
using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
using Microsoft.Maui.Controls.Internals;

{{GeneratedCodeAttribute}}
file static class GeneratedBindableObjectExtensions
internal static partial class GeneratedBindableObjectExtensions
{
{{GenerateBindingMethods(indent: 2)}}

private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableProperty)
=> mode == BindingMode.OneWayToSource
|| mode == BindingMode.TwoWay
Expand All @@ -72,27 +57,48 @@ private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableP
}
""";

private readonly List<SetBindingInvocationDescription> _bindings = new();
private static string GenerateBindingCode(string bindingMethodBody) => $$"""
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a .NET MAUI source generator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable

public void AddBinding(SetBindingInvocationDescription binding)
namespace Microsoft.Maui.Controls.Generated
{
using System;
using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
using Microsoft.Maui.Controls.Internals;

internal static partial class GeneratedBindableObjectExtensions
{
{{bindingMethodBody}}
}
}
""";


public static string GenerateBinding(SetBindingInvocationDescription binding, uint id)
{
if (!binding.NullableContextEnabled)
{
var referenceTypesConditionalAccessTransformer = new ReferenceTypesConditionalAccessTransformer();
binding = referenceTypesConditionalAccessTransformer.Transform(binding);
}
_bindings.Add(binding);

var bindingMethod = GenerateBindingMethod(binding, id);
return GenerateBindingCode(bindingMethod);
}

private string GenerateBindingMethods(int indent)
private static string GenerateBindingMethod(SetBindingInvocationDescription binding, uint id)
{
using var builder = new BindingInterceptorCodeBuilder(indent);

for (int i = 0; i < _bindings.Count; i++)
{
builder.AppendSetBindingInterceptor(id: i + 1, _bindings[i]);
}

using var builder = new BindingInterceptorCodeBuilder(indent: 2);
builder.AppendSetBindingInterceptor(id: id, binding: binding);
return builder.ToString();
}

Expand All @@ -113,10 +119,8 @@ public BindingInterceptorCodeBuilder(int indent = 0)
_indentedTextWriter = new IndentedTextWriter(_stringWriter, "\t") { Indent = indent };
}

public void AppendSetBindingInterceptor(int id, SetBindingInvocationDescription binding)
public void AppendSetBindingInterceptor(uint id, SetBindingInvocationDescription binding)
{
AppendBlankLine();

AppendLine(GeneratedCodeAttribute);
AppendInterceptorAttribute(binding.Location);
Append($"public static void SetBinding{id}");
Expand Down
22 changes: 10 additions & 12 deletions src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
)
.WithTrackingName(TrackingNames.BindingsWithDiagnostics);


context.RegisterSourceOutput(bindingsWithDiagnostics, (spc, bindingWithDiagnostic) =>
{
foreach (var diagnostic in bindingWithDiagnostic.Diagnostics)
Expand All @@ -27,20 +26,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var bindings = bindingsWithDiagnostics
.Where(static binding => !binding.HasDiagnostics)
.Select(static (binding, t) => binding.Value)
.WithTrackingName(TrackingNames.Bindings)
.Collect();

.WithTrackingName(TrackingNames.Bindings);

context.RegisterSourceOutput(bindings, (spc, bindings) =>
context.RegisterPostInitializationOutput(spc =>
{
var codeWriter = new BindingCodeWriter();

foreach (var binding in bindings)
{
codeWriter.AddBinding(binding);
}
spc.AddSource("GeneratedBindableObjectExtensionsCommon.g.cs", BindingCodeWriter.GenerateCommonCode());
});

spc.AddSource("GeneratedBindableObjectExtensions.g.cs", codeWriter.GenerateCode());
context.RegisterImplementationSourceOutput(bindings, (spc, binding) =>
{
var fileName = $"{binding.Location.FilePath}-GeneratedBindableObjectExtensions-{binding.Location.Line}-{binding.Location.Column}.g.cs";
var sanitizedFileName = fileName.Replace('/', '-').Replace('\\', '-').Replace(':', '-');
var code = BindingCodeWriter.GenerateBinding(binding, (uint)Math.Abs(binding.Location.GetHashCode()));
spc.AddSource(sanitizedFileName, code);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Microsoft.Maui.Controls.BindingSourceGen;

internal static class BindingGenerationUtilities
public static class BindingGenerationUtilities
{
internal static bool IsTypeNullable(ITypeSymbol typeInfo, bool enabledNullable)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/BindingSourceGen/GeneratorDataModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public interface IPathPart : IEquatable<IPathPart>
public string? PropertyName { get; }
}

internal sealed record Result<T>(T? OptionalValue, EquatableArray<DiagnosticInfo> Diagnostics)
public sealed record Result<T>(T? OptionalValue, EquatableArray<DiagnosticInfo> Diagnostics)
{
public bool HasDiagnostics => Diagnostics.Length > 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal static void CodeIsEqual(string expectedCode, string actualCode)
{
Assert.Equal(expectedLine, actualLine);
}

Assert.Equal(expectedLines.Count(), actualLines.Count());
}

internal static void BindingsAreEqual(SetBindingInvocationDescription expectedBinding, CodeGeneratorResult codeGeneratorResult)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,9 @@ namespace BindingSourceGen.UnitTests;
public class BindingCodeWriterTests
{
[Fact]
public void BuildsWholeDocument()
public void BuildsCommonCode()
{
var codeWriter = new BindingCodeWriter();
codeWriter.AddBinding(new SetBindingInvocationDescription(
Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30),
SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsValueType: false, IsNullable: false, IsGenericParameter: false),
PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsValueType: false, IsNullable: false, IsGenericParameter: false),
Path: new EquatableArray<IPathPart>([
new MemberAccess("A"),
new ConditionalAccess(new MemberAccess("B")),
new ConditionalAccess(new MemberAccess("C")),
]),
SetterOptions: new(IsWritable: true, AcceptsNullValue: false),
NullableContextEnabled: true));

var code = codeWriter.GenerateCode();
var code = BindingCodeWriter.GenerateCommonCode();
AssertExtensions.CodeIsEqual(
$$"""
//------------------------------------------------------------------------------
Expand All @@ -42,30 +29,75 @@ namespace System.Runtime.CompilerServices

{{BindingCodeWriter.GeneratedCodeAttribute}}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
internal sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
FilePath = filePath;
Line = line;
Column = column;
}

public string FilePath { get; }
public int Line { get; }
public int Column { get; }
}
}

namespace Microsoft.Maui.Controls.Generated
{
using System.CodeDom.Compiler;

{{BindingCodeWriter.GeneratedCodeAttribute}}
internal static partial class GeneratedBindableObjectExtensions
{
private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableProperty)
=> mode == BindingMode.OneWayToSource
|| mode == BindingMode.TwoWay
|| (mode == BindingMode.Default
&& (bindableProperty.DefaultBindingMode == BindingMode.OneWayToSource
|| bindableProperty.DefaultBindingMode == BindingMode.TwoWay));
}
}
""",
code);
}

[Fact]
public void BuildsWholeBinding()
{
var code = BindingCodeWriter.GenerateBinding(new SetBindingInvocationDescription(
Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30),
SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsValueType: false, IsNullable: false, IsGenericParameter: false),
PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsValueType: false, IsNullable: false, IsGenericParameter: false),
Path: new EquatableArray<IPathPart>([
new MemberAccess("A"),
new ConditionalAccess(new MemberAccess("B")),
new ConditionalAccess(new MemberAccess("C")),
]),
SetterOptions: new(IsWritable: true, AcceptsNullValue: false),
NullableContextEnabled: true), id: 1);

AssertExtensions.CodeIsEqual(
$$"""
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a .NET MAUI source generator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable

namespace Microsoft.Maui.Controls.Generated
{
using System;
using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
using Microsoft.Maui.Controls.Internals;

{{BindingCodeWriter.GeneratedCodeAttribute}}
file static class GeneratedBindableObjectExtensions
internal static partial class GeneratedBindableObjectExtensions
{

{{BindingCodeWriter.GeneratedCodeAttribute}}
Expand Down Expand Up @@ -115,13 +147,6 @@ public static void SetBinding1(
};
bindableObject.SetBinding(bindableProperty, binding);
}

private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableProperty)
=> mode == BindingMode.OneWayToSource
|| mode == BindingMode.TwoWay
|| (mode == BindingMode.Default
&& (bindableProperty.DefaultBindingMode == BindingMode.OneWayToSource
|| bindableProperty.DefaultBindingMode == BindingMode.TwoWay));
}
}
""",
Expand Down Expand Up @@ -482,5 +507,4 @@ public static void SetBinding1(
""",
code);
}

}
Loading