diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 16fbcd89..118be6c9 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -12,6 +12,8 @@ variables:
PathToCommunityToolkitUnitTestCsproj: 'src/CommunityToolkit.Maui.Markup.UnitTests/CommunityToolkit.Maui.Markup.UnitTests.csproj'
PathToCommunityToolkitBenchmarkCsproj: 'src/CommunityToolkit.Maui.Markup.Benchmarks/CommunityToolkit.Maui.Markup.Benchmarks.csproj'
PathToCommunityToolkitSourceGeneratorsCsproj: 'src/CommunityToolkit.Maui.Markup.SourceGenerators/CommunityToolkit.Maui.Markup.SourceGenerators.csproj'
+ PathToCommunityToolkitSourceGeneratorsUnitTestCsproj: 'src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj'
+ PathToCommunityToolkitSourceGeneratorsBenchmarkCsproj: 'src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks.csproj'
CommunityToolkitSampleApp_Xcode_Version: '16'
CommunityToolkitLibrary_Xcode_Version: '16'
ShouldCheckDependencies: true
@@ -190,6 +192,11 @@ jobs:
script: 'dotnet build -c Release $(PathToCommunityToolkitUnitTestCsproj)'
# test
+ - task: CmdLine@2
+ displayName: 'Run Source Generators Unit Tests'
+ inputs:
+ script: 'dotnet test -c Release $(PathToCommunityToolkitSourceGeneratorsUnitTestCsproj) --settings ".runsettings" --collect "XPlat code coverage" --logger trx --results-directory $(Agent.TempDirectory)'
+
- task: CmdLine@2
displayName: 'Run Unit Tests'
inputs:
@@ -306,10 +313,15 @@ jobs:
script: dotnet --info
- task: CmdLine@2
- displayName: 'Run Benchmarks'
+ displayName: 'Run Library Benchmarks'
inputs:
script : 'dotnet run --project $(PathToCommunityToolkitBenchmarkCsproj) -c Release -- -a $(Build.ArtifactStagingDirectory)'
+ - task: CmdLine@2
+ displayName: 'Run Source Generator Benchmarks'
+ inputs:
+ script : 'dotnet run --project $(PathToCommunityToolkitSourceGeneratorsBenchmarkCsproj) -c Release -- -a $(Build.ArtifactStagingDirectory)'
+
# publish the Benchmark Results
- task: PublishBuildArtifacts@1
condition: eq(variables['Agent.OS'], 'Windows_NT') # Only run this step on Windows
diff --git a/samples/CommunityToolkit.Maui.Markup.Sample.sln b/samples/CommunityToolkit.Maui.Markup.Sample.sln
index 14dd228a..d55ee66f 100644
--- a/samples/CommunityToolkit.Maui.Markup.Sample.sln
+++ b/samples/CommunityToolkit.Maui.Markup.Sample.sln
@@ -20,7 +20,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{A919
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.SourceGenerators", "..\src\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.csproj", "{533792FE-99CD-4B5B-A8B2-51A8BE3852A5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Markup.Benchmarks", "..\src\CommunityToolkit.Maui.Markup.Benchmarks\CommunityToolkit.Maui.Markup.Benchmarks.csproj", "{8C1B7D06-75D7-40AC-9FDB-344BF5FCCD5E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.Benchmarks", "..\src\CommunityToolkit.Maui.Markup.Benchmarks\CommunityToolkit.Maui.Markup.Benchmarks.csproj", "{8C1B7D06-75D7-40AC-9FDB-344BF5FCCD5E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests", "..\src\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj", "{26D5485B-68EA-454A-BBB7-11C6FF9B1B98}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{6F9076FC-4329-417E-80ED-0B57CBBC4BDC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{16554827-15BF-471E-B979-11A9D16A058B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks", "..\src\CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks\CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks.csproj", "{1CB34564-4764-4102-AD84-1C11960C97E9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -50,12 +58,24 @@ Global
{8C1B7D06-75D7-40AC-9FDB-344BF5FCCD5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C1B7D06-75D7-40AC-9FDB-344BF5FCCD5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C1B7D06-75D7-40AC-9FDB-344BF5FCCD5E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26D5485B-68EA-454A-BBB7-11C6FF9B1B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {26D5485B-68EA-454A-BBB7-11C6FF9B1B98}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26D5485B-68EA-454A-BBB7-11C6FF9B1B98}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {26D5485B-68EA-454A-BBB7-11C6FF9B1B98}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1CB34564-4764-4102-AD84-1C11960C97E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1CB34564-4764-4102-AD84-1C11960C97E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1CB34564-4764-4102-AD84-1C11960C97E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1CB34564-4764-4102-AD84-1C11960C97E9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A15CD688-94E8-483B-8B87-A173B2DD0E40} = {A919D3AA-043D-441B-9DF5-18ED84B7FC08}
+ {F45A2C29-DDD2-49A8-B4D9-57150F80AD36} = {16554827-15BF-471E-B979-11A9D16A058B}
+ {8C1B7D06-75D7-40AC-9FDB-344BF5FCCD5E} = {6F9076FC-4329-417E-80ED-0B57CBBC4BDC}
+ {26D5485B-68EA-454A-BBB7-11C6FF9B1B98} = {16554827-15BF-471E-B979-11A9D16A058B}
+ {1CB34564-4764-4102-AD84-1C11960C97E9} = {6F9076FC-4329-417E-80ED-0B57CBBC4BDC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks.csproj b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks.csproj
new file mode 100644
index 00000000..9c5fb88b
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks.csproj
@@ -0,0 +1,22 @@
+
+
+ $(NetVersion)
+ Exe
+
+
+ AnyCPU
+ pdbonly
+ true
+ true
+ true
+ Release
+ false
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/Program.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/Program.cs
new file mode 100644
index 00000000..1fb96b75
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/Program.cs
@@ -0,0 +1,12 @@
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Running;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks;
+public class Program
+{
+ public static void Main(string[] args)
+ {
+ var config = DefaultConfig.Instance;
+ var summary = BenchmarkRunner.Run(config, args);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs
new file mode 100644
index 00000000..e9dd9702
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs
@@ -0,0 +1,22 @@
+using BenchmarkDotNet.Attributes;
+using CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks;
+
+[MemoryDiagnoser]
+public class TextAlignmentExtensionsGeneratorBenchmarks
+{
+ readonly TextAlignmentExtensionsGeneratorTests textAlignmentExtensionsGeneratorTests = new();
+
+ [Benchmark]
+ public Task VerifyGeneratedSource_WhenClassIsGeneric()
+ {
+ return textAlignmentExtensionsGeneratorTests.VerifyGeneratedSource_WhenClassIsGeneric();
+ }
+
+ [Benchmark]
+ public Task VerifyGeneratedSource_WhenClassImplementsITextAlignmentInterface()
+ {
+ return textAlignmentExtensionsGeneratorTests.VerifyGeneratedSource_WhenClassImplementsITextAlignmentInterface();
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj
new file mode 100644
index 00000000..c458156f
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ $(NetVersion)
+ false
+ true
+ $(BaseIntermediateOutputPath)\GF
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
new file mode 100644
index 00000000..a3a2d378
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -0,0 +1,308 @@
+using System.Runtime.InteropServices;
+using NUnit.Framework;
+using static CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.CSharpSourceGeneratorVerifier;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+public class TextAlignmentExtensionsGeneratorTests
+{
+ static readonly string assemblyVersion = typeof(TextAlignmentExtensionsGenerator).Assembly.GetName().Version?.ToString() ?? throw new InvalidOleVariantTypeException("Assembly name cannot be null");
+ static readonly string textAlignmentExtensionsGeneratorFullName = typeof(TextAlignmentExtensionsGenerator).Assembly.FullName ?? throw new InvalidOleVariantTypeException("Assembly fullname cannot be null");
+
+ [Test]
+ public async Task VerifyGeneratedSource_WhenClassImplementsITextAlignmentInterface()
+ {
+ // Arrange
+ const string source = /* language=C#-test */ """
+using Microsoft.Maui;
+namespace MyNamespace;
+
+public class MyClass : ITextAlignment
+{
+ public TextAlignment HorizontalTextAlignment { get; set; } = TextAlignment.Center;
+ public TextAlignment VerticalTextAlignment { get; set; } = TextAlignment.Center;
+}
+""";
+
+ // Act // Assert
+ await VerifySourceGeneratorAsync(
+ source,
+ GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
+ new("MyClass", "public", "MyNamespace", string.Empty, string.Empty, string.Empty)),
+ []);
+ }
+
+ [Test]
+ public async Task VerifyGeneratedSource_WhenClassIsGeneric()
+ {
+ // Arrange
+ const string source = /* language=C#-test */ """
+using System;
+using Microsoft.Maui;
+namespace MyNamespace;
+
+public class GenericClass : Microsoft.Maui.ITextAlignment
+ where T : IDisposable, new()
+ where U : class
+{
+ public TextAlignment HorizontalTextAlignment { get; set; } = TextAlignment.Center;
+ public TextAlignment VerticalTextAlignment { get; set; } = TextAlignment.Center;
+}
+""";
+
+ // Act // Assert
+ await VerifySourceGeneratorAsync(
+ source,
+ GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
+ new("GenericClass", "public", "MyNamespace", "", "", "where T : IDisposable, new() where U : class")),
+ []);
+ }
+
+ static string GenerateSourceCode(string fullClassName, TextAlignmentClassMetadata textAlignmentClassMetadata) =>
+ /* language=C#-test */ $$"""
+//
+// See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator
+
+#nullable enable
+#pragma warning disable
+
+using System;
+using Microsoft.Maui;
+using Microsoft.Maui.Controls;
+
+namespace CommunityToolkit.Maui.Markup
+{
+ ///
+ /// Extension Methods for
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("{{fullClassName}}", "{{assemblyVersion}}")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ {{textAlignmentClassMetadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
+ {
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextStart{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextCenterHorizontal{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Center;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextEnd{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextTop{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.VerticalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextCenterVertical{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.VerticalTextAlignment = TextAlignment.Center;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextBottom{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.VerticalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// = =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextCenter{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ => textAlignmentControl.TextCenterHorizontal{{textAlignmentClassMetadata.GenericTypeParameters}}().TextCenterVertical{{textAlignmentClassMetadata.GenericTypeParameters}}();
+ }
+
+
+ // The extensions in these sub-namespaces are designed to be used together with the extensions in the parent namespace.
+ // Keep them in a single file for better maintainability
+
+ namespace LeftToRight
+ {
+ ///
+ /// Extension Methods for
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("{{fullClassName}}", "{{assemblyVersion}}")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ {{textAlignmentClassMetadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
+ {
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextLeft{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextRight{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+ }
+ }
+
+ // The extensions in these sub-namespaces are designed to be used together with the extensions in the parent namespace.
+ // Keep them in a single file for better maintainability
+ namespace RightToLeft
+ {
+ ///
+ /// Extension methods for
+ ///
+ {{textAlignmentClassMetadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
+ {
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextLeft{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextRight{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+ }
+ }
+}
+""";
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier+Test.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier+Test.cs
new file mode 100644
index 00000000..21e265d4
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier+Test.cs
@@ -0,0 +1,49 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+public static partial class CSharpAnalyzerVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ class Test : CSharpAnalyzerTest
+ {
+ public Test(params Type[] assembliesUnderTest)
+ {
+#if NET8_0
+ ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net80;
+#else
+#error ReferenceAssemblies must be updated to current version of .NET
+#endif
+ List typesForAssembliesUnderTest =
+ [
+ typeof(Microsoft.Maui.Controls.Xaml.Extensions), // Microsoft.Maui.Controls.Xaml
+ typeof(MauiApp),// Microsoft.Maui.Hosting
+ typeof(Application), // Microsoft.Maui.Controls
+ ];
+ typesForAssembliesUnderTest.AddRange(assembliesUnderTest);
+
+ foreach (Type type in typesForAssembliesUnderTest)
+ {
+ TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(type.Assembly.Location));
+ }
+
+ SolutionTransforms.Add((solution, projectId) =>
+ {
+ ArgumentNullException.ThrowIfNull(solution);
+
+ if (solution.GetProject(projectId) is not Project project)
+ {
+ throw new ArgumentException("Invalid ProjectId");
+ }
+
+ var compilationOptions = project.CompilationOptions ?? throw new InvalidOperationException($"{nameof(project.CompilationOptions)} cannot be null");
+ compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings));
+ solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);
+
+ return solution;
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier.cs
new file mode 100644
index 00000000..6a1770f1
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier.cs
@@ -0,0 +1,34 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+public static partial class CSharpAnalyzerVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ ///
+ public static DiagnosticResult Diagnostic()
+ => CSharpAnalyzerVerifier.Diagnostic();
+
+ ///
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ => CSharpAnalyzerVerifier.Diagnostic(diagnosticId);
+
+ ///
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ => CSharpAnalyzerVerifier.Diagnostic(descriptor);
+
+ ///
+ public static async Task VerifyAnalyzerAsync(string source, Type[] assembliesUnderTest, params DiagnosticResult[] expected)
+ {
+ var test = new Test(assembliesUnderTest)
+ {
+ TestCode = source,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs
new file mode 100644
index 00000000..094f6c9c
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs
@@ -0,0 +1,54 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+public static partial class CSharpCodeFixVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+{
+ class Test : CSharpCodeFixTest
+ {
+ public Test(params Type[] assembliesUnderTest)
+ {
+#if NET8_0
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80;
+#else
+#error ReferenceAssemblies must be updated to current version of .NET
+#endif
+ List typesForAssembliesUnderTest =
+ [
+ typeof(Microsoft.Maui.Controls.Xaml.Extensions), // Microsoft.Maui.Controls.Xaml
+ typeof(MauiApp),// Microsoft.Maui.Hosting
+ typeof(Application), // Microsoft.Maui.Controls
+ ];
+ typesForAssembliesUnderTest.AddRange(assembliesUnderTest);
+
+ foreach (Type type in typesForAssembliesUnderTest)
+ {
+ TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(type.Assembly.Location));
+ }
+
+ SolutionTransforms.Add((solution, projectId) =>
+ {
+ ArgumentNullException.ThrowIfNull(solution);
+
+ if (solution.GetProject(projectId) is not Project project)
+ {
+ throw new ArgumentException("Invalid ProjectId");
+ }
+
+ var compilationOptions = project.CompilationOptions ?? throw new InvalidOperationException($"{nameof(project.CompilationOptions)} cannot be null");
+ compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings));
+
+ solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);
+
+ return solution;
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier.cs
new file mode 100644
index 00000000..de8a8b38
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier.cs
@@ -0,0 +1,56 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+public static partial class CSharpCodeFixVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+{
+ ///
+ public static DiagnosticResult Diagnostic()
+ => CSharpCodeFixVerifier.Diagnostic();
+
+ ///
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ => CSharpCodeFixVerifier.Diagnostic(diagnosticId);
+
+ ///
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ => CSharpCodeFixVerifier.Diagnostic(descriptor);
+
+ ///
+ public static async Task VerifyAnalyzerAsync(string source, Type[] assembliesUnderTest, params DiagnosticResult[] expected)
+ {
+ var test = new Test(assembliesUnderTest)
+ {
+ TestCode = source,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, string fixedSource)
+ => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource);
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
+ => await VerifyCodeFixAsync(source, [expected], fixedSource);
+
+ ///
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource, params Type[] assembliesUnderTest)
+ {
+ var test = new Test(assembliesUnderTest)
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+ await test.RunAsync(CancellationToken.None);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
new file mode 100644
index 00000000..6ad79573
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
@@ -0,0 +1,74 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+public static partial class CSharpSourceGeneratorVerifier
+ where TSourceGenerator : IIncrementalGenerator, new()
+{
+ class Test : CSharpSourceGeneratorTest
+ {
+ public Test(params Type[] assembliesUnderTest)
+ {
+#if NET8_0
+ ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net80;
+#else
+#error ReferenceAssemblies must be updated to current version of .NET
+#endif
+ List typesForAssembliesUnderTest =
+ [
+ typeof(Microsoft.Maui.Controls.Xaml.Extensions), // Microsoft.Maui.Controls.Xaml
+ typeof(MauiApp),// Microsoft.Maui.Hosting
+ typeof(Application), // Microsoft.Maui.Controls
+ ];
+ typesForAssembliesUnderTest.AddRange(assembliesUnderTest);
+
+ foreach (var type in typesForAssembliesUnderTest)
+ {
+ TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(type.Assembly.Location));
+ }
+
+ SolutionTransforms.Add((solution, projectId) =>
+ {
+ ArgumentNullException.ThrowIfNull(solution);
+
+ if (solution.GetProject(projectId) is not Project project)
+ {
+ throw new ArgumentException("Invalid ProjectId");
+ }
+
+ var compilationOptions = project.CompilationOptions ?? throw new InvalidOperationException($"{nameof(project.CompilationOptions)} cannot be null");
+ compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings));
+ solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);
+
+ return solution;
+ });
+ }
+
+ protected override CompilationOptions CreateCompilationOptions()
+ {
+ var compilationOptions = base.CreateCompilationOptions();
+ return compilationOptions.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(GetNullableWarningsFromCompiler()));
+ }
+
+ public LanguageVersion LanguageVersion { get; } = LanguageVersion.Default;
+
+ static ImmutableDictionary GetNullableWarningsFromCompiler()
+ {
+ string[] args = { "/warnaserror:nullable" };
+ var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);
+ var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
+
+ return nullableWarnings;
+ }
+
+ protected override ParseOptions CreateParseOptions()
+ {
+ return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
new file mode 100644
index 00000000..4f2d3d1b
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -0,0 +1,33 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Text;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+public static partial class CSharpSourceGeneratorVerifier
+ where TSourceGenerator : IIncrementalGenerator, new()
+{
+ ///
+ public static async Task VerifySourceGeneratorAsync(string source, string expectedGeneratedCode, Type[] assembliesUnderTest, params DiagnosticResult[] expectedDiagnosticResults)
+ {
+ var test = new Test(assembliesUnderTest)
+ {
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck,
+ TestState =
+ {
+ Sources = { source },
+ GeneratedSources =
+ {
+ (typeof(TSourceGenerator), string.Empty, SourceText.From(expectedGeneratedCode, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+ }
+ }
+ };
+
+ test.ExpectedDiagnostics.AddRange(expectedDiagnosticResults);
+
+ await test.RunAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpVerifierHelper.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpVerifierHelper.cs
new file mode 100644
index 00000000..9aef35cb
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpVerifierHelper.cs
@@ -0,0 +1,31 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+static class CSharpVerifierHelper
+{
+ ///
+ /// By default, the compiler reports diagnostics for nullable reference types at
+ /// , and the analyzer test framework defaults to only validating
+ /// diagnostics at . This map contains all compiler diagnostic IDs
+ /// related to nullability mapped to , which is then used to enable all
+ /// of these warnings for default validation during analyzer and code fix tests.
+ ///
+ internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler();
+
+ static ImmutableDictionary GetNullableWarningsFromCompiler()
+ {
+ string[] args = ["/warnaserror:nullable"];
+ var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);
+ var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
+
+ // Workaround for https://github.com/dotnet/roslyn/issues/41610
+ nullableWarnings = nullableWarnings
+ .SetItem("CS8632", ReportDiagnostic.Error)
+ .SetItem("CS8669", ReportDiagnostic.Error);
+
+ return nullableWarnings;
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/CommunityToolkit.Maui.Markup.SourceGenerators.csproj b/src/CommunityToolkit.Maui.Markup.SourceGenerators/CommunityToolkit.Maui.Markup.SourceGenerators.csproj
index f7d22719..d7b53cc9 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/CommunityToolkit.Maui.Markup.SourceGenerators.csproj
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/CommunityToolkit.Maui.Markup.SourceGenerators.csproj
@@ -7,6 +7,10 @@
true
+
+
+
+
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index 4b119947..49eda672 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -1,365 +1,95 @@
using System.Collections.Immutable;
-using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
+
namespace CommunityToolkit.Maui.Markup.SourceGenerators;
[Generator(LanguageNames.CSharp)]
-class TextAlignmentExtensionsGenerator : IIncrementalGenerator
+public class TextAlignmentExtensionsGenerator : IIncrementalGenerator
{
- const string iTextAlignmentInterface = "Microsoft.Maui.ITextAlignment";
+ const string textAlignmentInterface = "Microsoft.Maui.ITextAlignment";
const string mauiControlsAssembly = "Microsoft.Maui.Controls";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- // Get All Classes in User Library
- var userGeneratedClassesProvider = context.SyntaxProvider.CreateSyntaxProvider(
- static (syntaxNode, cancellationToken) => syntaxNode is ClassDeclarationSyntax { BaseList: not null },
- static (context, cancellationToken) =>
- {
- var compilation = context.SemanticModel.Compilation;
-
- var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(iTextAlignmentInterface) ?? throw new Exception("There's no .NET MAUI referenced in the project.");
- var classSymbol = context.SemanticModel.GetDeclaredSymbol((ClassDeclarationSyntax)context.Node, cancellationToken);
-
- if (classSymbol is null || classSymbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken) != context.Node)
+ IncrementalValuesProvider<(INamedTypeSymbol ClassSymbol, INamedTypeSymbol ITextAlignmentInterfaceSymbol)> userGeneratedClassesProvider = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax { BaseList: not null },
+ static (syntaxContext, ct) =>
{
- // In case of multiple partial declarations, we want to run only once.
- // So we run only for the first syntax reference.
- return null;
- }
-
- return ShouldGenerateTextAlignmentExtension(classSymbol, iTextAlignmentInterfaceSymbol)
- ? GenerateMetadata(classSymbol)
- : null;
-
- }).Where(static m => m is not null);
-
- // Get Microsoft.Maui.Controls Symbols that implements the desired interfaces
- var mauiControlsAssemblySymbolProvider = context.CompilationProvider.Select(
- static (compilation, token) =>
- {
- var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(iTextAlignmentInterface) ?? throw new Exception("There's no .NET MAUI referenced in the project.");
- var mauiAssembly = compilation.SourceModule.ReferencedAssemblySymbols.Single(q => q.Name == mauiControlsAssembly);
-
- return GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol).ToImmutableArray().AsEquatableArray();
- });
-
-
- // Here we Collect all the Classes candidates from the first pipeline
- // Then we merge them with the Maui.Controls that implements the desired interfaces
- // Then we make sure they are unique and the user control doesn't inherit from any Maui control that implements the desired interface already
- // Then we transform the ISymbol to be a type that we can compare and preserve the Incremental behavior of this Source Generator
- context.RegisterSourceOutput(userGeneratedClassesProvider, Execute);
- context.RegisterSourceOutput(mauiControlsAssemblySymbolProvider, ExecuteArray);
+ var compilation = syntaxContext.SemanticModel.Compilation;
+ var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(textAlignmentInterface);
+ if (iTextAlignmentInterfaceSymbol is null)
+ {
+ return default;
+ }
+
+ var classSymbol = syntaxContext.SemanticModel.GetDeclaredSymbol((ClassDeclarationSyntax)syntaxContext.Node, ct);
+ if (classSymbol is null || classSymbol.DeclaringSyntaxReferences[0].GetSyntax(ct) != syntaxContext.Node)
+ {
+ return default;
+ }
+
+ return (classSymbol, iTextAlignmentInterfaceSymbol);
+ })
+ .Where(static tuple => tuple != default && ShouldGenerateTextAlignmentExtension(tuple.classSymbol, tuple.iTextAlignmentInterfaceSymbol));
+
+ var compilationProvider = context.CompilationProvider;
+
+ var combined = userGeneratedClassesProvider
+ .Collect()
+ .Combine(compilationProvider);
+
+ context.RegisterSourceOutput(combined, static (spc, source) => Execute(spc, source.Right, source.Left.ToImmutableArray()));
}
static bool ShouldGenerateTextAlignmentExtension(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
{
- return ImplementsInterfaceIgnoringBaseType(classSymbol, iTextAlignmentInterfaceSymbol)
- && DoesNotImplementInterface(classSymbol.BaseType, iTextAlignmentInterfaceSymbol);
+ return DoesImplementInterfaceIgnoringBaseType(classSymbol, iTextAlignmentInterfaceSymbol)
+ && !DoesImplementInterface(classSymbol.BaseType, iTextAlignmentInterfaceSymbol);
- static bool ImplementsInterfaceIgnoringBaseType(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
- => classSymbol.Interfaces.Any(i => i.Equals(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default) || i.AllInterfaces.Contains(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default));
+ static bool DoesImplementInterfaceIgnoringBaseType(INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol)
+ => classSymbol.AllInterfaces.Contains(interfaceSymbol);
- static bool DoesNotImplementInterface(INamedTypeSymbol? classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
- => classSymbol is null || !classSymbol.AllInterfaces.Any(i => i.Equals(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default));
+ static bool DoesImplementInterface(INamedTypeSymbol? classSymbol, INamedTypeSymbol interfaceSymbol)
+ => classSymbol?.AllInterfaces.Contains(interfaceSymbol) ?? false;
}
- static void ExecuteArray(SourceProductionContext context, EquatableArray metadataArray)
+ static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray<(INamedTypeSymbol ClassSymbol, INamedTypeSymbol ITextAlignmentInterfaceSymbol)> userClasses)
{
- foreach (var metadata in metadataArray.AsImmutableArray())
+ var mauiAssembly = compilation.SourceModule.ReferencedAssemblySymbols.FirstOrDefault(static a => a.Name == mauiControlsAssembly);
+ if (mauiAssembly is null)
{
- Execute(context, metadata);
+ return;
}
- }
- static void Execute(SourceProductionContext context, [NotNull] TextAlignmentClassMetadata? textAlignmentClassMetadata)
- {
- if (textAlignmentClassMetadata is null)
+ var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(textAlignmentInterface);
+ if (iTextAlignmentInterfaceSymbol is null)
{
- throw new ArgumentNullException(nameof(textAlignmentClassMetadata));
+ return;
}
- var className = typeof(TextAlignmentExtensionsGenerator).FullName;
- var assemblyVersion = typeof(TextAlignmentExtensionsGenerator).Assembly.GetName().Version.ToString();
-
- var genericTypeParameters = GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments);
- var genericArguments = GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments);
- var source = /* language=C#-test */$$"""
-//
-// See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator
-
-#nullable enable
-#pragma warning disable
+ var mauiClasses = GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol);
-using System;
-using Microsoft.Maui;
-using Microsoft.Maui.Controls;
+ var processedClasses = new HashSet(SymbolEqualityComparer.Default);
-namespace CommunityToolkit.Maui.Markup
-{
- ///
- /// Extension Methods for
- ///
- [global::System.CodeDom.Compiler.GeneratedCode("{{className}}", "{{assemblyVersion}}")]
- [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
- {
- ///
- /// =
- ///
- ///
- /// with added
- public static TAssignable TextJustify{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.Justify;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with added
- public static TAssignable TextStart{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with added
- public static TAssignable TextCenterHorizontal{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.Center;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with added
- public static TAssignable TextEnd{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with added
- public static TAssignable TextTop{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.VerticalTextAlignment = TextAlignment.Start;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with added
- public static TAssignable TextCenterVertical{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.VerticalTextAlignment = TextAlignment.Center;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with added
- public static TAssignable TextBottom{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.VerticalTextAlignment = TextAlignment.End;
- return textAlignmentControl;
- }
-
- ///
- /// = =
- ///
- ///
- /// with added
- public static TAssignable TextCenter{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- => textAlignmentControl.TextCenterHorizontal{{genericTypeParameters}}().TextCenterVertical{{genericTypeParameters}}();
- }
-
-
- // The extensions in these sub-namespaces are designed to be used together with the extensions in the parent namespace.
- // Keep them in a single file for better maintainability
-
- namespace LeftToRight
- {
- ///
- /// Extension Methods for
- ///
- [global::System.CodeDom.Compiler.GeneratedCode("{{className}}", "{{assemblyVersion}}")]
- [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
- {
- ///
- /// =
- ///
- ///
- /// with
- public static TAssignable TextLeft{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with
- public static TAssignable TextRight{{genericTypeParameters}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
- return textAlignmentControl;
- }
- }
- }
-
- // The extensions in these sub-namespaces are designed to be used together with the extensions in the parent namespace.
- // Keep them in a single file for better maintainability
- namespace RightToLeft
- {
- ///
- /// Extension methods for
- ///
- {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
- {
- ///
- /// =
- ///
- ///
- /// with
- public static TAssignable TextLeft{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
- return textAlignmentControl;
- }
-
- ///
- /// =
- ///
- ///
- /// with
- public static TAssignable TextRight{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- {
- ArgumentNullException.ThrowIfNull(textAlignmentControl);
-
- if (textAlignmentControl is not ITextAlignment)
- {
- throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
- }
-
- textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
- return textAlignmentControl;
- }
- }
- }
-}
-""";
- context.AddSource($"{textAlignmentClassMetadata.ClassName}TextAlignmentExtensions.g.cs", SourceText.From(source, Encoding.UTF8));
+ foreach (var (classSymbol, _) in userClasses.Concat(mauiClasses.Select(c => (c, iTextAlignmentInterfaceSymbol))))
+ {
+ if (processedClasses.Add(classSymbol))
+ {
+ var metadata = GenerateMetadata(classSymbol);
+ GenerateExtensionClass(context, metadata);
+ }
+ }
}
- static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbol mauiControlsAssemblySymbolProvider, INamedTypeSymbol itextAlignmentSymbol)
+ static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbol mauiAssembly, INamedTypeSymbol iTextAlignmentSymbol)
{
- return mauiControlsAssemblySymbolProvider.GlobalNamespace.GetNamedTypeSymbols().Where(x => ShouldGenerateTextAlignmentExtension(x, itextAlignmentSymbol)).Select(GenerateMetadata);
+ return mauiAssembly.GlobalNamespace.GetNamedTypeSymbols()
+ .Where(x => ShouldGenerateTextAlignmentExtension(x, iTextAlignmentSymbol));
}
static string GetClassAccessModifier(INamedTypeSymbol namedTypeSymbol) => namedTypeSymbol.DeclaredAccessibility switch
@@ -369,34 +99,366 @@ static IEnumerable GetMauiInterfaceImplementors(IAss
_ => string.Empty
};
- static string GetGenericTypeParametersDeclarationString(in string genericArguments)
+ static void GenerateExtensionClass(SourceProductionContext context, in TextAlignmentClassMetadata metadata)
{
- if (string.IsNullOrWhiteSpace(genericArguments))
- {
- return "";
- }
-
- return $"";
+ var source = GenerateExtensionClassSource(in metadata);
+ context.AddSource($"{metadata.ClassName}TextAlignmentExtensions.g.cs", SourceText.From(source, Encoding.UTF8));
}
- static string GetGenericArgumentsString(in string genericArguments)
+ static string GenerateExtensionClassSource(in TextAlignmentClassMetadata metadata)
{
- if (string.IsNullOrWhiteSpace(genericArguments))
- {
- return string.Empty;
- }
+ var assemblyVersion = typeof(TextAlignmentExtensionsGenerator).Assembly.GetName().Version.ToString();
+ var className = typeof(TextAlignmentExtensionsGenerator).FullName;
- return $"<{genericArguments}>";
+ var sb = new StringBuilder(8192); // Pre-allocate with an estimated capacity
+ sb.AppendLine( /* language=C#-test */
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator
+
+ #nullable enable
+ #pragma warning disable
+
+ using System;
+ using Microsoft.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace CommunityToolkit.Maui.Markup
+ {
+ ///
+ /// Extension Methods for
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("{{className}}", "{{assemblyVersion}}")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ {{metadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{metadata.ClassName}}
+ {
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextStart{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextCenterHorizontal{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Center;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextEnd{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextTop{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.VerticalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextCenterVertical{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.VerticalTextAlignment = TextAlignment.Center;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextBottom{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.VerticalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// = =
+ ///
+ ///
+ /// with added
+ public static TAssignable TextCenter{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ => textAlignmentControl.TextCenterHorizontal{{metadata.GenericTypeParameters}}().TextCenterVertical{{metadata.GenericTypeParameters}}();
+ }
+
+
+ // The extensions in these sub-namespaces are designed to be used together with the extensions in the parent namespace.
+ // Keep them in a single file for better maintainability
+
+ namespace LeftToRight
+ {
+ ///
+ /// Extension Methods for
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("{{className}}", "{{assemblyVersion}}")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ {{metadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{metadata.ClassName}}
+ {
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextLeft{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextRight{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl) where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+ }
+ }
+
+ // The extensions in these sub-namespaces are designed to be used together with the extensions in the parent namespace.
+ // Keep them in a single file for better maintainability
+ namespace RightToLeft
+ {
+ ///
+ /// Extension methods for
+ ///
+ {{metadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{metadata.ClassName}}
+ {
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextLeft{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.End;
+ return textAlignmentControl;
+ }
+
+ ///
+ /// =
+ ///
+ ///
+ /// with
+ public static TAssignable TextRight{{metadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{metadata.Namespace}}.{{metadata.ClassName}}{{metadata.GenericArguments}}{{metadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
+
+ if (textAlignmentControl is not ITextAlignment)
+ {
+ throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl));
+ }
+
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+ }
+ }
+ }
+ """);
+
+ return sb.ToString();
}
static TextAlignmentClassMetadata GenerateMetadata(INamedTypeSymbol namedTypeSymbol)
{
- var accessModifier = mauiControlsAssembly == namedTypeSymbol.ContainingNamespace.ToDisplayString()
+ var accessModifier = namedTypeSymbol.ContainingNamespace.ToDisplayString() == mauiControlsAssembly
? "internal"
: GetClassAccessModifier(namedTypeSymbol);
- return new(namedTypeSymbol.Name, accessModifier, namedTypeSymbol.ContainingNamespace.ToDisplayString(), namedTypeSymbol.TypeArguments.GetGenericTypeArgumentsString(), namedTypeSymbol.GetGenericTypeConstraintsAsString());
+ var genericTypeParameters = GetGenericTypeParametersDeclarationString(namedTypeSymbol);
+ var genericArguments = GetGenericArgumentsString(namedTypeSymbol);
+ var genericConstraints = GetGenericConstraintsString(namedTypeSymbol);
+
+ return new TextAlignmentClassMetadata(
+ namedTypeSymbol.Name,
+ accessModifier,
+ namedTypeSymbol.ContainingNamespace.ToDisplayString(),
+ genericTypeParameters,
+ genericArguments,
+ genericConstraints
+ );
+ }
+
+ static string GetGenericTypeParametersDeclarationString(INamedTypeSymbol namedTypeSymbol)
+ {
+ if (namedTypeSymbol.TypeParameters.Length is 0)
+ {
+ return "";
+ }
+
+ var typeParams = string.Join(", ", namedTypeSymbol.TypeParameters.Select(t => t.Name));
+ return $"";
}
- record TextAlignmentClassMetadata(string ClassName, string ClassAcessModifier, string Namespace, string GenericArguments, string GenericConstraints);
+ static string GetGenericArgumentsString(INamedTypeSymbol namedTypeSymbol)
+ {
+ return namedTypeSymbol.TypeParameters.Length > 0
+ ? $"<{string.Join(", ", namedTypeSymbol.TypeParameters.Select(t => t.Name))}>"
+ : string.Empty;
+ }
+
+ static string GetGenericConstraintsString(INamedTypeSymbol namedTypeSymbol)
+ {
+ var constraints = namedTypeSymbol.TypeParameters
+ .Select(GetGenericParameterConstraints)
+ .Where(static c => !string.IsNullOrEmpty(c));
+
+ return string.Join(" ", constraints);
+ }
+
+ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
+ {
+ var constraints = new List();
+
+ // Primary constraint (class, struct, unmanaged)
+ if (typeParameter.HasReferenceTypeConstraint)
+ {
+ constraints.Add(typeParameter.ReferenceTypeConstraintNullableAnnotation is NullableAnnotation.Annotated
+ ? "class?"
+ : "class");
+ }
+ else if (typeParameter.HasValueTypeConstraint)
+ {
+ constraints.Add(typeParameter.HasUnmanagedTypeConstraint ? "unmanaged" : "struct");
+ }
+ else if (typeParameter.HasUnmanagedTypeConstraint)
+ {
+ constraints.Add("unmanaged");
+ }
+ else if (typeParameter.HasNotNullConstraint)
+ {
+ constraints.Add("notnull");
+ }
+
+ // Secondary constraints (specific types)
+ foreach (var constraintType in typeParameter.ConstraintTypes)
+ {
+ var symbolDisplayFormat = new SymbolDisplayFormat(
+ typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
+ miscellaneousOptions: SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
+
+ var constraintTypeString = constraintType.ToDisplayString(symbolDisplayFormat);
+
+ constraints.Add(constraintTypeString);
+ }
+
+ // Check for record constraint
+ if (typeParameter.IsRecord)
+ {
+ constraints.Add("record");
+ }
+
+ // Constructor constraint (must be last)
+ if (typeParameter.HasConstructorConstraint)
+ {
+ constraints.Add("new()");
+ }
+
+ return constraints.Count > 0
+ ? $"where {typeParameter.Name} : {string.Join(", ", constraints)}"
+ : string.Empty;
+ }
}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/TextAlignmentClassMetadata.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/TextAlignmentClassMetadata.cs
new file mode 100644
index 00000000..427482ed
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/TextAlignmentClassMetadata.cs
@@ -0,0 +1,9 @@
+namespace CommunityToolkit.Maui.Markup.SourceGenerators;
+
+public record TextAlignmentClassMetadata(
+ string ClassName,
+ string ClassAccessModifier,
+ string Namespace,
+ string GenericTypeParameters,
+ string GenericArguments,
+ string GenericConstraints);
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
index 481069a5..1801dfde 100644
--- a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
@@ -81,9 +81,9 @@ public void Extensions_For_Generic_Class()
ClassConstraintWithInterface?,
ClassConstraint[],
ClassConstraintWithInterface,
- RecordClassContstraint,
- RecordClassContstraint[],
- RecordStructContstraint>()
+ RecordClassConstraint,
+ RecordClassConstraint[],
+ RecordStructConstraint>()
.TextCenter,
+ RecordClassConstraint,
+ RecordClassConstraint[],
+ RecordStructConstraint>,
ClassConstraintWithInterface,
ClassConstraint,
StructConstraint,
@@ -107,9 +107,9 @@ public void Extensions_For_Generic_Class()
ClassConstraintWithInterface?,
ClassConstraint[],
ClassConstraintWithInterface,
- RecordClassContstraint,
- RecordClassContstraint[],
- RecordStructContstraint>();
+ RecordClassConstraint,
+ RecordClassConstraint[],
+ RecordStructConstraint>();
Assert.That(textAlignmentView.HorizontalTextAlignment, Is.EqualTo(TextAlignment.Center));
@@ -123,9 +123,9 @@ public void Extensions_For_Generic_Class()
ClassConstraintWithInterface?,
ClassConstraint[],
ClassConstraintWithInterface,
- RecordClassContstraint,
- RecordClassContstraint[],
- RecordStructContstraint>,
+ RecordClassConstraint,
+ RecordClassConstraint[],
+ RecordStructConstraint>,
ClassConstraintWithInterface,
ClassConstraint,
StructConstraint,
@@ -136,9 +136,9 @@ public void Extensions_For_Generic_Class()
ClassConstraintWithInterface?,
ClassConstraint[],
ClassConstraintWithInterface,
- RecordClassContstraint,
- RecordClassContstraint[],
- RecordStructContstraint>();
+ RecordClassConstraint,
+ RecordClassConstraint[],
+ RecordStructConstraint>();
Assert.That(textAlignmentView.HorizontalTextAlignment, Is.EqualTo(TextAlignment.End));
}
@@ -460,13 +460,13 @@ class MyGenericPicker : Picker
}
- public record RecordClassContstraint
+ public record RecordClassConstraint
{
}
- public readonly record struct RecordStructContstraint
+ public readonly record struct RecordStructConstraint
{
}
diff --git a/src/CommunityToolkit.Maui.Markup.sln b/src/CommunityToolkit.Maui.Markup.sln
index 044c1144..13cea65f 100644
--- a/src/CommunityToolkit.Maui.Markup.sln
+++ b/src/CommunityToolkit.Maui.Markup.sln
@@ -14,9 +14,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\global.json = ..\global.json
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Markup.SourceGenerators", "CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.csproj", "{C66CEA39-565E-479C-974D-72795D3502CB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.SourceGenerators", "CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.csproj", "{C66CEA39-565E-479C-974D-72795D3502CB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Markup.Benchmarks", "CommunityToolkit.Maui.Markup.Benchmarks\CommunityToolkit.Maui.Markup.Benchmarks.csproj", "{9A46B6CE-CC4B-4F26-80F8-779C94A88C18}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.Benchmarks", "CommunityToolkit.Maui.Markup.Benchmarks\CommunityToolkit.Maui.Markup.Benchmarks.csproj", "{9A46B6CE-CC4B-4F26-80F8-779C94A88C18}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests", "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj", "{BB9611DF-34B9-4BBF-A564-EB2AADD943C8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0716CCB2-1629-4E73-ACE8-643A6F9F5AD3}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{F690A1B1-9B74-4DB7-8A98-D0CBF794EDAD}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks", "CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks\CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks.csproj", "{2B682080-908C-467C-B429-3700AFFFD612}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -40,10 +48,24 @@ Global
{9A46B6CE-CC4B-4F26-80F8-779C94A88C18}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A46B6CE-CC4B-4F26-80F8-779C94A88C18}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A46B6CE-CC4B-4F26-80F8-779C94A88C18}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BB9611DF-34B9-4BBF-A564-EB2AADD943C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BB9611DF-34B9-4BBF-A564-EB2AADD943C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BB9611DF-34B9-4BBF-A564-EB2AADD943C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BB9611DF-34B9-4BBF-A564-EB2AADD943C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2B682080-908C-467C-B429-3700AFFFD612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2B682080-908C-467C-B429-3700AFFFD612}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2B682080-908C-467C-B429-3700AFFFD612}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2B682080-908C-467C-B429-3700AFFFD612}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {480F4FB8-F50A-4349-B5B6-C4D6D9151343} = {0716CCB2-1629-4E73-ACE8-643A6F9F5AD3}
+ {9A46B6CE-CC4B-4F26-80F8-779C94A88C18} = {F690A1B1-9B74-4DB7-8A98-D0CBF794EDAD}
+ {BB9611DF-34B9-4BBF-A564-EB2AADD943C8} = {0716CCB2-1629-4E73-ACE8-643A6F9F5AD3}
+ {2B682080-908C-467C-B429-3700AFFFD612} = {F690A1B1-9B74-4DB7-8A98-D0CBF794EDAD}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {42B065EB-DA54-40BE-B676-3D47D59A81F2}
EndGlobalSection
diff --git a/src/CommunityToolkit.Maui.Markup/CommunityToolkit.Maui.Markup.csproj b/src/CommunityToolkit.Maui.Markup/CommunityToolkit.Maui.Markup.csproj
index 0290633e..edf0a8c4 100644
--- a/src/CommunityToolkit.Maui.Markup/CommunityToolkit.Maui.Markup.csproj
+++ b/src/CommunityToolkit.Maui.Markup/CommunityToolkit.Maui.Markup.csproj
@@ -14,7 +14,7 @@
Microsoft
Microsoft
en
- CommunityToolkit.Maui.Markup (net6.0)
+ CommunityToolkit.Maui.Markup
© Microsoft Corporation. All rights reserved.
MIT
https://github.com/communitytoolkit/Maui.Markup