From f64408afc5cdd35b9bda87efc8c55407a4a6bb87 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Fri, 4 Oct 2024 12:40:54 -0700
Subject: [PATCH 01/20] Add Unit Tests
---
.../CommunityToolkit.Maui.Markup.Sample.sln | 6 ++
...i.Markup.SourceGenerators.UnitTests.csproj | 32 ++++++++
.../TextAlignmentExtensionsGeneratorTests.cs | 63 ++++++++++++++++
.../Verifiers/CSharpAnalyzerVerifier+Test.cs | 49 ++++++++++++
.../Verifiers/CSharpAnalyzerVerifier.cs | 34 +++++++++
.../Verifiers/CSharpCodeFixVerifier+Test.cs | 54 ++++++++++++++
.../Verifiers/CSharpCodeFixVerifier.cs | 56 ++++++++++++++
.../CSharpSourceGeneratorVerifier+Test.cs | 74 +++++++++++++++++++
.../CSharpSourceGeneratorVerifier.cs | 22 ++++++
.../Verifiers/CSharpVerifierHelper.cs | 31 ++++++++
...oolkit.Maui.Markup.SourceGenerators.csproj | 4 +
.../TextAlignmentExtensionsGenerator.cs | 22 +++---
src/CommunityToolkit.Maui.Markup.sln | 6 ++
.../CommunityToolkit.Maui.Markup.csproj | 2 +-
14 files changed, 443 insertions(+), 12 deletions(-)
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier+Test.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpVerifierHelper.cs
diff --git a/samples/CommunityToolkit.Maui.Markup.Sample.sln b/samples/CommunityToolkit.Maui.Markup.Sample.sln
index 14dd228a..37316951 100644
--- a/samples/CommunityToolkit.Maui.Markup.Sample.sln
+++ b/samples/CommunityToolkit.Maui.Markup.Sample.sln
@@ -22,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Marku
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}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests", "..\src\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj", "{26D5485B-68EA-454A-BBB7-11C6FF9B1B98}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -50,6 +52,10 @@ 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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
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..097cc1ad
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj
@@ -0,0 +1,32 @@
+
+
+
+ $(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..dad5f8e1
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -0,0 +1,63 @@
+using NUnit.Framework;
+using static CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.CSharpSourceGeneratorVerifier;
+
+namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
+
+public class TextAlignmentExtensionsGeneratorTests
+{
+ [Test]
+ public async Task VerifyGeneratedSource_WhenClassImplementsITextAlignmentInterface()
+ {
+ // Arrange
+ const string source = """
+
+ namespace MyNamespace
+ {
+ public class MyClass : Microsoft.Maui.ITextAlignment
+ {
+ }
+ }
+ """;
+
+ // Act // Assert
+ await VerifySourceGeneratorAsync(source, [typeof(TextAlignmentExtensionsGenerator)]);
+ }
+
+ [Test]
+ public async Task VerifyGeneratedSource_WhenClassDoesNotImplementITextAlignmentInterface()
+ {
+ // Arrange
+ const string source = """
+
+ namespace MyNamespace
+ {
+ public class MyClass
+ {
+ }
+ }
+ """;
+
+ // Act // Assert
+ await VerifySourceGeneratorAsync(source, [typeof(TextAlignmentExtensionsGenerator)]);
+ }
+
+ [Test]
+ public async Task VerifyGeneratedSource_WhenClassIsGeneric()
+ {
+ // Arrange
+ const string source = """
+
+ namespace MyNamespace
+ {
+ public class MyClass : Microsoft.Maui.ITextAlignment
+ where T : IDisposable, new()
+ where U : class
+ {
+ }
+ }
+ """;
+
+ // Act // Assert
+ await VerifySourceGeneratorAsync(source, [typeof(TextAlignmentExtensionsGenerator)]);
+ }
+}
\ 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..e883ee8b
--- /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()
+{
+ public 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..9a5eafed
--- /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()
+{
+ protected 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..e9ab495c
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -0,0 +1,22 @@
+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 CSharpSourceGeneratorVerifier
+ where TSourceGenerator : IIncrementalGenerator, new()
+{
+ ///
+ public static async Task VerifySourceGeneratorAsync(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/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 da3bfa2c..c7ae9a34 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 606d6b65..5735d8df 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -8,7 +8,7 @@
namespace CommunityToolkit.Maui.Markup.SourceGenerators;
[Generator(LanguageNames.CSharp)]
-class TextAlignmentExtensionsGenerator : IIncrementalGenerator
+public class TextAlignmentExtensionsGenerator : IIncrementalGenerator
{
const string iTextAlignmentInterface = "Microsoft.Maui.ITextAlignment";
const string mauiControlsAssembly = "Microsoft.Maui.Controls";
@@ -56,6 +56,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.RegisterSourceOutput(userGeneratedClassesProvider, Execute);
context.RegisterSourceOutput(mauiControlsAssemblySymbolProvider, ExecuteArray);
}
+
+ internal static string GetGenericArgumentsString(in string genericArguments)
+ {
+ if (string.IsNullOrWhiteSpace(genericArguments))
+ {
+ return string.Empty;
+ }
+
+ return $"<{genericArguments}>";
+ }
static bool ShouldGenerateTextAlignmentExtension(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
{
@@ -360,16 +370,6 @@ static string GetGenericTypeParametersDeclarationString(in string genericArgumen
return $"";
}
- static string GetGenericArgumentsString(in string genericArguments)
- {
- if (string.IsNullOrWhiteSpace(genericArguments))
- {
- return string.Empty;
- }
-
- return $"<{genericArguments}>";
- }
-
static TextAlignmentClassMetadata GenerateMetadata(INamedTypeSymbol namedTypeSymbol)
{
var accessModifier = mauiControlsAssembly == namedTypeSymbol.ContainingNamespace.ToDisplayString()
diff --git a/src/CommunityToolkit.Maui.Markup.sln b/src/CommunityToolkit.Maui.Markup.sln
index 044c1144..eb7c3c43 100644
--- a/src/CommunityToolkit.Maui.Markup.sln
+++ b/src/CommunityToolkit.Maui.Markup.sln
@@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Marku
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}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests", "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj", "{BB9611DF-34B9-4BBF-A564-EB2AADD943C8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -40,6 +42,10 @@ 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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/CommunityToolkit.Maui.Markup/CommunityToolkit.Maui.Markup.csproj b/src/CommunityToolkit.Maui.Markup/CommunityToolkit.Maui.Markup.csproj
index abb4ea2f..c8cd985b 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
From 3d80496d393a2466f2e961019426fc2af152e98e Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Fri, 4 Oct 2024 12:49:47 -0700
Subject: [PATCH 02/20] Update TextAlignmentExtensionsGenerator.cs
---
.../TextAlignmentExtensionsGenerator.cs | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index 5735d8df..c2dd458a 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -56,16 +56,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.RegisterSourceOutput(userGeneratedClassesProvider, Execute);
context.RegisterSourceOutput(mauiControlsAssemblySymbolProvider, ExecuteArray);
}
-
- internal static string GetGenericArgumentsString(in string genericArguments)
- {
- if (string.IsNullOrWhiteSpace(genericArguments))
- {
- return string.Empty;
- }
-
- return $"<{genericArguments}>";
- }
static bool ShouldGenerateTextAlignmentExtension(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
{
@@ -369,6 +359,16 @@ static string GetGenericTypeParametersDeclarationString(in string genericArgumen
return $"";
}
+
+ static string GetGenericArgumentsString(in string genericArguments)
+ {
+ if (string.IsNullOrWhiteSpace(genericArguments))
+ {
+ return string.Empty;
+ }
+
+ return $"<{genericArguments}>";
+ }
static TextAlignmentClassMetadata GenerateMetadata(INamedTypeSymbol namedTypeSymbol)
{
From 2fac95ba6f160f1f685769d7bf79ac773606de75 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Fri, 4 Oct 2024 13:33:16 -0700
Subject: [PATCH 03/20] Update Tests
---
.../Temp.cs | 7 +
.../TextAlignmentExtensionsGeneratorTests.cs | 269 +++++++++++++++++-
.../Verifiers/CSharpAnalyzerVerifier+Test.cs | 2 +-
.../Verifiers/CSharpCodeFixVerifier+Test.cs | 2 +-
.../CSharpSourceGeneratorVerifier+Test.cs | 7 +-
.../CSharpSourceGeneratorVerifier.cs | 17 +-
.../TextAlignmentExtensionsGenerator.cs | 8 +-
.../TextAlignmentClassMetadata.cs | 3 +
8 files changed, 293 insertions(+), 22 deletions(-)
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators/TextAlignmentClassMetadata.cs
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs
new file mode 100644
index 00000000..30808950
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs
@@ -0,0 +1,7 @@
+namespace MyNamespace;
+
+public class MyClass : Microsoft.Maui.ITextAlignment
+{
+ public TextAlignment HorizontalTextAlignment { get; }
+ public TextAlignment VerticalTextAlignment { get; }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
index dad5f8e1..732fcfe7 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -1,16 +1,19 @@
-using NUnit.Framework;
+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 = """
-
namespace MyNamespace
{
public class MyClass : Microsoft.Maui.ITextAlignment
@@ -20,7 +23,7 @@ public class MyClass : Microsoft.Maui.ITextAlignment
""";
// Act // Assert
- await VerifySourceGeneratorAsync(source, [typeof(TextAlignmentExtensionsGenerator)]);
+ await VerifySourceGeneratorAsync(source, "MyNamespaceTextAlignmentExtensions.g.cs", GenerateSourceCode(textAlignmentExtensionsGeneratorFullName, assemblyVersion, new("MyClass", "public", "MyNamespace", string.Empty, string.Empty), string.Empty, string.Empty), []);
}
[Test]
@@ -37,8 +40,12 @@ public class MyClass
}
""";
+ const string generated = """
+
+ """;
+
// Act // Assert
- await VerifySourceGeneratorAsync(source, [typeof(TextAlignmentExtensionsGenerator)]);
+ await VerifySourceGeneratorAsync(source, "MyClassTextAlignmentExtensions.g.cs", generated, []);
}
[Test]
@@ -57,7 +64,259 @@ public class MyClass : Microsoft.Maui.ITextAlignment
}
""";
+ const string generated = """
+
+ """;
+
// Act // Assert
- await VerifySourceGeneratorAsync(source, [typeof(TextAlignmentExtensionsGenerator)]);
+ await VerifySourceGeneratorAsync(source, "MyClassTextAlignmentExtensions.g.cs", generated, []);
}
+
+ static string GenerateSourceCode(string fullClassName, string assemblyVersion, TextAlignmentClassMetadata textAlignmentClassMetadata, string genericArguments, string genericTypeParameters) =>
+ $$"""
+ //
+ // 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{{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("{{fullClassName}}", "{{assemblyVersion}}")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ {{textAlignmentClassMetadata.ClassAccessModifier}} 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.ClassAccessModifier}} 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;
+ }
+ }
+ }
+ }
+ """;
}
\ 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
index e883ee8b..21e265d4 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier+Test.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpAnalyzerVerifier+Test.cs
@@ -6,7 +6,7 @@ namespace CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests;
public static partial class CSharpAnalyzerVerifier
where TAnalyzer : DiagnosticAnalyzer, new()
{
- public class Test : CSharpAnalyzerTest
+ class Test : CSharpAnalyzerTest
{
public Test(params Type[] assembliesUnderTest)
{
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs
index 9a5eafed..094f6c9c 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpCodeFixVerifier+Test.cs
@@ -10,7 +10,7 @@ public static partial class CSharpCodeFixVerifier
where TAnalyzer : DiagnosticAnalyzer, new()
where TCodeFix : CodeFixProvider, new()
{
- protected class Test : CSharpCodeFixTest
+ class Test : CSharpCodeFixTest
{
public Test(params Type[] assembliesUnderTest)
{
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
index 6ad79573..cd9e063e 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
@@ -17,12 +17,7 @@ public Test(params Type[] assembliesUnderTest)
#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
- ];
+ List typesForAssembliesUnderTest = [];
typesForAssembliesUnderTest.AddRange(assembliesUnderTest);
foreach (var type in typesForAssembliesUnderTest)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
index e9ab495c..84945ce6 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -1,7 +1,9 @@
+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;
@@ -9,14 +11,21 @@ public static partial class CSharpSourceGeneratorVerifier
where TSourceGenerator : IIncrementalGenerator, new()
{
///
- public static async Task VerifySourceGeneratorAsync(string source, Type[] assembliesUnderTest, params DiagnosticResult[] expected)
+ public static async Task VerifySourceGeneratorAsync(string source, string generatedFileName, string expectedGeneratedCode, Type[] assembliesUnderTest, params DiagnosticResult[] expectedDiagnosticResults)
{
var test = new Test(assembliesUnderTest)
{
- TestCode = source,
+ TestState =
+ {
+ Sources = { source },
+ GeneratedSources =
+ {
+ (typeof(TSourceGenerator), generatedFileName, SourceText.From(expectedGeneratedCode, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+ }
+ }
};
- test.ExpectedDiagnostics.AddRange(expected);
- await test.RunAsync(CancellationToken.None);
+ 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/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index c2dd458a..a104c0c1 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -107,7 +107,7 @@ namespace CommunityToolkit.Maui.Markup
///
[global::System.CodeDom.Compiler.GeneratedCode("{{className}}", "{{assemblyVersion}}")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
+ {{textAlignmentClassMetadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
{
///
/// =
@@ -244,7 +244,7 @@ namespace LeftToRight
///
[global::System.CodeDom.Compiler.GeneratedCode("{{className}}", "{{assemblyVersion}}")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
+ {{textAlignmentClassMetadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
{
///
/// =
@@ -292,7 +292,7 @@ namespace RightToLeft
///
/// Extension methods for
///
- {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
+ {{textAlignmentClassMetadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
{
///
/// =
@@ -378,6 +378,4 @@ static TextAlignmentClassMetadata GenerateMetadata(INamedTypeSymbol namedTypeSym
return new(namedTypeSymbol.Name, accessModifier, namedTypeSymbol.ContainingNamespace.ToDisplayString(), namedTypeSymbol.TypeArguments.GetGenericTypeArgumentsString(), namedTypeSymbol.GetGenericTypeConstraintsAsString());
}
-
- record TextAlignmentClassMetadata(string ClassName, string ClassAcessModifier, string Namespace, string GenericArguments, string GenericConstraints);
}
\ 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..05c9e92c
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/TextAlignmentClassMetadata.cs
@@ -0,0 +1,3 @@
+namespace CommunityToolkit.Maui.Markup.SourceGenerators;
+
+public record TextAlignmentClassMetadata(string ClassName, string ClassAccessModifier, string Namespace, string GenericArguments, string GenericConstraints);
\ No newline at end of file
From 93e985b7e5cc779cce62a8b087d747c6771a0dce Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Fri, 4 Oct 2024 14:33:47 -0700
Subject: [PATCH 04/20] Fix Unit Tests
---
.../Temp.cs | 7 -
.../TextAlignmentExtensionsGeneratorTests.cs | 572 +++++++++---------
.../CSharpSourceGeneratorVerifier+Test.cs | 7 +-
.../CSharpSourceGeneratorVerifier.cs | 2 +
.../TextAlignmentExtensionsTests.cs | 4 +-
5 files changed, 290 insertions(+), 302 deletions(-)
delete mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs
deleted file mode 100644
index 30808950..00000000
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Temp.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace MyNamespace;
-
-public class MyClass : Microsoft.Maui.ITextAlignment
-{
- public TextAlignment HorizontalTextAlignment { get; }
- public TextAlignment VerticalTextAlignment { get; }
-}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
index 732fcfe7..789378a2 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -6,317 +6,305 @@ 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");
-
+ 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 = """
- namespace MyNamespace
- {
- public class MyClass : Microsoft.Maui.ITextAlignment
- {
- }
- }
- """;
+ 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, "MyNamespaceTextAlignmentExtensions.g.cs", GenerateSourceCode(textAlignmentExtensionsGeneratorFullName, assemblyVersion, new("MyClass", "public", "MyNamespace", string.Empty, string.Empty), string.Empty, string.Empty), []);
+ await VerifySourceGeneratorAsync(
+ source,
+ "MyClassTextAlignmentExtensions.g.cs",
+ GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
+ new("MyClass", "public", "MyNamespace", string.Empty, string.Empty), string.Empty, string.Empty),
+ []);
}
[Test]
- public async Task VerifyGeneratedSource_WhenClassDoesNotImplementITextAlignmentInterface()
+ public async Task VerifyGeneratedSource_WhenClassIsGeneric()
{
// Arrange
- const string source = """
-
- namespace MyNamespace
- {
- public class MyClass
- {
- }
- }
- """;
-
- const string generated = """
+ const string source = /* language=C#-test */ """
+using System;
+using Microsoft.Maui;
+namespace MyNamespace;
- """;
+public class MyClass : 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, "MyClassTextAlignmentExtensions.g.cs", generated, []);
+ await VerifySourceGeneratorAsync(
+ source,
+ "MyClassTextAlignmentExtensions.g.cs",
+ GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
+ new("MyClass", "public", "MyNamespace", "", "where T : IDisposable, new() where U : class"), string.Empty, string.Empty),
+ []);
}
- [Test]
- public async Task VerifyGeneratedSource_WhenClassIsGeneric()
- {
- // Arrange
- const string source = """
+ static string GenerateSourceCode(string fullClassName, TextAlignmentClassMetadata textAlignmentClassMetadata, string genericArguments, string genericTypeParameters) =>
+ /* language=C#-test */ $$"""
+//
+// See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator
- namespace MyNamespace
- {
- public class MyClass : Microsoft.Maui.ITextAlignment
- where T : IDisposable, new()
- where U : class
- {
- }
- }
- """;
+#nullable enable
+#pragma warning disable
- const string generated = """
+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{{genericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ {
+ ArgumentNullException.ThrowIfNull(textAlignmentControl);
- // Act // Assert
- await VerifySourceGeneratorAsync(source, "MyClassTextAlignmentExtensions.g.cs", generated, []);
- }
+ 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("{{fullClassName}}", "{{assemblyVersion}}")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ {{textAlignmentClassMetadata.ClassAccessModifier}} 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.ClassAccessModifier}} 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));
+ }
- static string GenerateSourceCode(string fullClassName, string assemblyVersion, TextAlignmentClassMetadata textAlignmentClassMetadata, string genericArguments, string genericTypeParameters) =>
- $$"""
- //
- // 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{{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("{{fullClassName}}", "{{assemblyVersion}}")]
- [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- {{textAlignmentClassMetadata.ClassAccessModifier}} 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.ClassAccessModifier}} 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;
- }
- }
- }
- }
- """;
+ textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start;
+ return textAlignmentControl;
+ }
+ }
+ }
+}
+""";
}
\ 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
index cd9e063e..6ad79573 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier+Test.cs
@@ -17,7 +17,12 @@ public Test(params Type[] assembliesUnderTest)
#else
#error ReferenceAssemblies must be updated to current version of .NET
#endif
- List typesForAssembliesUnderTest = [];
+ 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)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
index 84945ce6..d3d61b5d 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -15,6 +15,7 @@ public static async Task VerifySourceGeneratorAsync(string source, string genera
{
var test = new Test(assembliesUnderTest)
{
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck,
TestState =
{
Sources = { source },
@@ -26,6 +27,7 @@ public static async Task VerifySourceGeneratorAsync(string source, string genera
};
test.ExpectedDiagnostics.AddRange(expectedDiagnosticResults);
+
await test.RunAsync(CancellationToken.None).ConfigureAwait(false);
}
}
\ 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 213453cd..fb4ed88d 100644
--- a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
@@ -251,14 +251,14 @@ where type.Name.StartsWith("TextAlignmentExtensions_")
class DerivedFromSearchBar : SearchBar { }
}
- class CustomTextAlignmentControl : ITextAlignment
+ sealed class CustomTextAlignmentControl : ITextAlignment
{
public TextAlignment HorizontalTextAlignment { get; set; } = TextAlignment.Center;
public TextAlignment VerticalTextAlignment { get; set; } = TextAlignment.Center;
}
- class MyEntry : Entry, IEntry
+ sealed class MyEntry : Entry, IEntry
{
public string Type { get; set; } = string.Empty;
}
From 9f51055cdc30d6f6f91039c48bd76de1a1fb2302 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Fri, 4 Oct 2024 14:36:03 -0700
Subject: [PATCH 05/20] Update TextAlignmentExtensionsTests.cs
---
.../TextAlignmentExtensionsTests.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
index fb4ed88d..213453cd 100644
--- a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
@@ -251,14 +251,14 @@ where type.Name.StartsWith("TextAlignmentExtensions_")
class DerivedFromSearchBar : SearchBar { }
}
- sealed class CustomTextAlignmentControl : ITextAlignment
+ class CustomTextAlignmentControl : ITextAlignment
{
public TextAlignment HorizontalTextAlignment { get; set; } = TextAlignment.Center;
public TextAlignment VerticalTextAlignment { get; set; } = TextAlignment.Center;
}
- sealed class MyEntry : Entry, IEntry
+ class MyEntry : Entry, IEntry
{
public string Type { get; set; } = string.Empty;
}
From 7dd2c3f2104a93371f82ec0d225072cfa734ecee Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Fri, 4 Oct 2024 14:37:24 -0700
Subject: [PATCH 06/20] Update azure-pipelines.yml
---
azure-pipelines.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 1b0b92a7..7afd6edd 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -12,6 +12,7 @@ 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'
CommunityToolkitSampleApp_Xcode_Version: '16'
CommunityToolkitLibrary_Xcode_Version: '16'
ShouldCheckDependencies: true
@@ -170,6 +171,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:
From f751a621b6a5862dae0e5ba93e4f32db0b967284 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Fri, 4 Oct 2024 14:40:48 -0700
Subject: [PATCH 07/20] Remove Generated File Name
---
.../TextAlignmentExtensionsGeneratorTests.cs | 2 --
.../Verifiers/CSharpSourceGeneratorVerifier.cs | 4 ++--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
index 789378a2..02abe956 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -27,7 +27,6 @@ public class MyClass : ITextAlignment
// Act // Assert
await VerifySourceGeneratorAsync(
source,
- "MyClassTextAlignmentExtensions.g.cs",
GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
new("MyClass", "public", "MyNamespace", string.Empty, string.Empty), string.Empty, string.Empty),
[]);
@@ -54,7 +53,6 @@ public class MyClass : Microsoft.Maui.ITextAlignment
// Act // Assert
await VerifySourceGeneratorAsync(
source,
- "MyClassTextAlignmentExtensions.g.cs",
GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
new("MyClass", "public", "MyNamespace", "", "where T : IDisposable, new() where U : class"), string.Empty, string.Empty),
[]);
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
index d3d61b5d..4f2d3d1b 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -11,7 +11,7 @@ public static partial class CSharpSourceGeneratorVerifier
where TSourceGenerator : IIncrementalGenerator, new()
{
///
- public static async Task VerifySourceGeneratorAsync(string source, string generatedFileName, string expectedGeneratedCode, Type[] assembliesUnderTest, params DiagnosticResult[] expectedDiagnosticResults)
+ public static async Task VerifySourceGeneratorAsync(string source, string expectedGeneratedCode, Type[] assembliesUnderTest, params DiagnosticResult[] expectedDiagnosticResults)
{
var test = new Test(assembliesUnderTest)
{
@@ -21,7 +21,7 @@ public static async Task VerifySourceGeneratorAsync(string source, string genera
Sources = { source },
GeneratedSources =
{
- (typeof(TSourceGenerator), generatedFileName, SourceText.From(expectedGeneratedCode, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+ (typeof(TSourceGenerator), string.Empty, SourceText.From(expectedGeneratedCode, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
}
}
};
From f661c5fa20bbccd3b2dda11a7c50550cb1205164 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 12:03:42 -0700
Subject: [PATCH 08/20] Refactor TextAlignmentExtensionsGenerator
---
.../TextAlignmentExtensionsGeneratorTests.cs | 50 +-
.../TextAlignmentExtensionsGenerator.cs | 727 ++++++++++--------
.../TextAlignmentClassMetadata.cs | 8 +-
3 files changed, 438 insertions(+), 347 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
index 02abe956..0e4c8917 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -28,7 +28,7 @@ public class MyClass : ITextAlignment
await VerifySourceGeneratorAsync(
source,
GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
- new("MyClass", "public", "MyNamespace", string.Empty, string.Empty), string.Empty, string.Empty),
+ new("MyClass", "public", "MyNamespace", string.Empty, string.Empty, string.Empty)),
[]);
}
@@ -54,11 +54,11 @@ public class MyClass : Microsoft.Maui.ITextAlignment
await VerifySourceGeneratorAsync(
source,
GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
- new("MyClass", "public", "MyNamespace", "", "where T : IDisposable, new() where U : class"), string.Empty, string.Empty),
+ new("MyClass", "public", "MyNamespace", "", string.Empty, "where T : IDisposable, new() where U : class")),
[]);
}
- static string GenerateSourceCode(string fullClassName, TextAlignmentClassMetadata textAlignmentClassMetadata, string genericArguments, string genericTypeParameters) =>
+ static string GenerateSourceCode(string fullClassName, TextAlignmentClassMetadata textAlignmentClassMetadata) =>
/* language=C#-test */ $$"""
//
// See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator
@@ -84,8 +84,8 @@ namespace CommunityToolkit.Maui.Markup
///
///
/// with added
- public static TAssignable TextStart{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextStart{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -103,8 +103,8 @@ namespace CommunityToolkit.Maui.Markup
///
///
/// with added
- public static TAssignable TextCenterHorizontal{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextCenterHorizontal{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -122,8 +122,8 @@ namespace CommunityToolkit.Maui.Markup
///
///
/// with added
- public static TAssignable TextEnd{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextEnd{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -141,8 +141,8 @@ namespace CommunityToolkit.Maui.Markup
///
///
/// with added
- public static TAssignable TextTop{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextTop{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -160,8 +160,8 @@ namespace CommunityToolkit.Maui.Markup
///
///
/// with added
- public static TAssignable TextCenterVertical{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextCenterVertical{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -179,8 +179,8 @@ namespace CommunityToolkit.Maui.Markup
///
///
/// with added
- public static TAssignable TextBottom{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextBottom{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -198,9 +198,9 @@ namespace CommunityToolkit.Maui.Markup
///
///
/// with added
- public static TAssignable TextCenter{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
- => textAlignmentControl.TextCenterHorizontal{{genericTypeParameters}}().TextCenterVertical{{genericTypeParameters}}();
+ 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}}();
}
@@ -221,8 +221,8 @@ namespace LeftToRight
///
///
/// with
- public static TAssignable TextLeft{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextLeft{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -240,7 +240,7 @@ namespace LeftToRight
///
///
/// with
- public static TAssignable TextRight{{genericTypeParameters}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextRight{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -269,8 +269,8 @@ namespace RightToLeft
///
///
/// with
- public static TAssignable TextLeft{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextLeft{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
@@ -288,8 +288,8 @@ namespace RightToLeft
///
///
/// with
- public static TAssignable TextRight{{genericTypeParameters}}(this TAssignable textAlignmentControl)
- where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{genericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
+ public static TAssignable TextRight{{textAlignmentClassMetadata.GenericTypeParameters}}(this TAssignable textAlignmentControl)
+ where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{textAlignmentClassMetadata.GenericArguments}}{{textAlignmentClassMetadata.GenericConstraints}}
{
ArgumentNullException.ThrowIfNull(textAlignmentControl);
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index a104c0c1..49e8c633 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -1,346 +1,99 @@
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)]
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)
+ // Optimize: Use ValueTuple for lightweight data passing
+ 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; // Early return to avoid unnecessary processing
+ }
+
+ 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;
+
+ // Optimize: Combine providers to reduce the number of operations
+ var combined = userGeneratedClassesProvider
+ .Collect()
+ .Combine(compilationProvider);
+
+ // Register the source output
+ context.RegisterSourceOutput(combined, static (spc, source) => Execute(spc, source.Right, [..source.Left.Select(static x => x.ClassSymbol)]));
}
static bool ShouldGenerateTextAlignmentExtension(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
{
return ImplementsInterfaceIgnoringBaseType(classSymbol, iTextAlignmentInterfaceSymbol)
- && DoesNotImplementInterface(classSymbol.BaseType, iTextAlignmentInterfaceSymbol);
+ && !ImplementsInterface(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 ImplementsInterfaceIgnoringBaseType(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 ImplementsInterface(INamedTypeSymbol? classSymbol, INamedTypeSymbol interfaceSymbol)
+ => classSymbol?.AllInterfaces.Contains(interfaceSymbol) ?? false;
}
- static void ExecuteArray(SourceProductionContext context, EquatableArray metadataArray)
+ static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray 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 = $$"""
-//
-// See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator
+ var mauiClasses = GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol);
-#nullable enable
-#pragma warning disable
+ // Use HashSet for faster lookup
+ var processedClasses = new HashSet(SymbolEqualityComparer.Default);
-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]
- {{textAlignmentClassMetadata.ClassAccessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
- {
- ///
- /// =
- ///
- ///
- /// 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.ClassAccessModifier}} 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.ClassAccessModifier}} 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))
+ {
+ 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
@@ -350,32 +103,364 @@ static IEnumerable GetMauiInterfaceImplementors(IAss
_ => string.Empty
};
- static string GetGenericTypeParametersDeclarationString(in string genericArguments)
+ static void GenerateExtensionClass(SourceProductionContext context, TextAlignmentClassMetadata metadata)
+ {
+ var source = GenerateExtensionClassSource(metadata);
+ context.AddSource($"{metadata.ClassName}TextAlignmentExtensions.g.cs", SourceText.From(source, Encoding.UTF8));
+ }
+
+ static string GenerateExtensionClassSource(TextAlignmentClassMetadata metadata)
+ {
+ var assemblyVersion = typeof(TextAlignmentExtensionsGenerator).Assembly.GetName().Version.ToString();
+ var className = typeof(TextAlignmentExtensionsGenerator).FullName;
+
+ var sb = new StringBuilder();
+ 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 = namedTypeSymbol.ContainingNamespace.ToDisplayString() == mauiControlsAssembly
+ ? "internal"
+ : GetClassAccessModifier(namedTypeSymbol);
+
+ 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 (string.IsNullOrWhiteSpace(genericArguments))
+ if (namedTypeSymbol.TypeParameters.Length is 0)
{
return "";
}
- return $"";
+ var typeParams = string.Join(", ", namedTypeSymbol.TypeParameters.Select(t => t.Name));
+ return $"";
}
-
- static string GetGenericArgumentsString(in string genericArguments)
+
+ static string GetGenericArgumentsString(INamedTypeSymbol namedTypeSymbol)
{
- if (string.IsNullOrWhiteSpace(genericArguments))
- {
- return string.Empty;
- }
+ 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(c => !string.IsNullOrEmpty(c));
- return $"<{genericArguments}>";
+ return string.Join(" ", constraints);
}
- static TextAlignmentClassMetadata GenerateMetadata(INamedTypeSymbol namedTypeSymbol)
+ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
{
- var accessModifier = mauiControlsAssembly == namedTypeSymbol.ContainingNamespace.ToDisplayString()
- ? "internal"
- : GetClassAccessModifier(namedTypeSymbol);
+ var constraints = new StringBuilder();
+
+ // Primary constraint (class, struct, unmanaged)
+ if (typeParameter.HasReferenceTypeConstraint)
+ {
+ constraints.Append("class");
+ }
+ else if (typeParameter.HasValueTypeConstraint)
+ {
+ constraints.Append(typeParameter.HasUnmanagedTypeConstraint ? "unmanaged" : "struct");
+ }
+ else if (typeParameter.HasUnmanagedTypeConstraint)
+ {
+ constraints.Append("unmanaged");
+ }
+ else if (typeParameter.HasNotNullConstraint)
+ {
+ constraints.Append("notnull");
+ }
+
+ // Secondary constraints (specific types)
+ foreach (var constraintType in typeParameter.ConstraintTypes)
+ {
+ var constraintTypeString = constraintType.NullableAnnotation switch
+ {
+ NullableAnnotation.Annotated => constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + '?',
+ _ => constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
+ };
+
+ constraints.Append(constraintTypeString);
+ }
+
+ // Check for record constraint
+ if (typeParameter.IsRecord)
+ {
+ constraints.Append("record");
+ }
- return new(namedTypeSymbol.Name, accessModifier, namedTypeSymbol.ContainingNamespace.ToDisplayString(), namedTypeSymbol.TypeArguments.GetGenericTypeArgumentsString(), namedTypeSymbol.GetGenericTypeConstraintsAsString());
+ // Constructor constraint (must be last)
+ if (typeParameter.HasConstructorConstraint)
+ {
+ constraints.Append("new()");
+ }
+
+ return constraints.Length > 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
index 05c9e92c..427482ed 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/TextAlignmentClassMetadata.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/TextAlignmentClassMetadata.cs
@@ -1,3 +1,9 @@
namespace CommunityToolkit.Maui.Markup.SourceGenerators;
-public record TextAlignmentClassMetadata(string ClassName, string ClassAccessModifier, string Namespace, string GenericArguments, string GenericConstraints);
\ No newline at end of file
+public record TextAlignmentClassMetadata(
+ string ClassName,
+ string ClassAccessModifier,
+ string Namespace,
+ string GenericTypeParameters,
+ string GenericArguments,
+ string GenericConstraints);
\ No newline at end of file
From 83eb152a280ffb8024092ee01a579a0e14c9e7e8 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 12:49:39 -0700
Subject: [PATCH 09/20] Update Source Generator
---
...i.Markup.SourceGenerators.UnitTests.csproj | 1 +
.../TextAlignmentExtensionsGeneratorTests.cs | 7 ++--
.../TextAlignmentExtensionsGenerator.cs | 20 +++++------
.../TextAlignmentExtensionsTests.cs | 34 +++++++++----------
4 files changed, 33 insertions(+), 29 deletions(-)
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
index 097cc1ad..752e70f8 100644
--- 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
@@ -11,6 +11,7 @@
+
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
index 0e4c8917..ce0cfb1c 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -41,7 +41,7 @@ public async Task VerifyGeneratedSource_WhenClassIsGeneric()
using Microsoft.Maui;
namespace MyNamespace;
-public class MyClass : Microsoft.Maui.ITextAlignment
+public class GenericClass : Microsoft.Maui.ITextAlignment
where T : IDisposable, new()
where U : class
{
@@ -50,11 +50,14 @@ public class MyClass : Microsoft.Maui.ITextAlignment
}
""";
+ var temp = GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
+ new("GenericClass", "public", "MyNamespace", "", "", "where T : IDisposable, new() where U : class"));
+
// Act // Assert
await VerifySourceGeneratorAsync(
source,
GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
- new("MyClass", "public", "MyNamespace", "", string.Empty, "where T : IDisposable, new() where U : class")),
+ new("GenericClass", "public", "MyNamespace", "", "", "where T : IDisposable, new() where U : class")),
[]);
}
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index 49e8c633..bbb02f89 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -415,24 +415,24 @@ static string GetGenericConstraintsString(INamedTypeSymbol namedTypeSymbol)
static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
{
- var constraints = new StringBuilder();
+ var constraints = new List();
// Primary constraint (class, struct, unmanaged)
if (typeParameter.HasReferenceTypeConstraint)
{
- constraints.Append("class");
+ constraints.Add("class");
}
else if (typeParameter.HasValueTypeConstraint)
{
- constraints.Append(typeParameter.HasUnmanagedTypeConstraint ? "unmanaged" : "struct");
+ constraints.Add(typeParameter.HasUnmanagedTypeConstraint ? "unmanaged" : "struct");
}
else if (typeParameter.HasUnmanagedTypeConstraint)
{
- constraints.Append("unmanaged");
+ constraints.Add("unmanaged");
}
else if (typeParameter.HasNotNullConstraint)
{
- constraints.Append("notnull");
+ constraints.Add("notnull");
}
// Secondary constraints (specific types)
@@ -440,26 +440,26 @@ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
{
var constraintTypeString = constraintType.NullableAnnotation switch
{
- NullableAnnotation.Annotated => constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + '?',
+ NullableAnnotation.Annotated => string.Concat(constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), '?'),
_ => constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
};
- constraints.Append(constraintTypeString);
+ constraints.Add(constraintTypeString);
}
// Check for record constraint
if (typeParameter.IsRecord)
{
- constraints.Append("record");
+ constraints.Add("record");
}
// Constructor constraint (must be last)
if (typeParameter.HasConstructorConstraint)
{
- constraints.Append("new()");
+ constraints.Add("new()");
}
- return constraints.Length > 0
+ return constraints.Count > 0
? $"where {typeParameter.Name} : {string.Join(", ", constraints)}"
: string.Empty;
}
diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs
index 213453cd..1258c7ce 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
{
}
From 064be4b376bfb4237c9032e52151c791515d78fa Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 12:52:15 -0700
Subject: [PATCH 10/20] Update TextAlignmentExtensionsGenerator.cs
---
.../SourceGenerators/TextAlignmentExtensionsGenerator.cs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index bbb02f89..9b566f29 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -15,7 +15,6 @@ public class TextAlignmentExtensionsGenerator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- // Optimize: Use ValueTuple for lightweight data passing
IncrementalValuesProvider<(INamedTypeSymbol ClassSymbol, INamedTypeSymbol ITextAlignmentInterfaceSymbol)> userGeneratedClassesProvider = context.SyntaxProvider
.CreateSyntaxProvider(
static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax { BaseList: not null },
@@ -40,7 +39,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var compilationProvider = context.CompilationProvider;
- // Optimize: Combine providers to reduce the number of operations
+ // Combine providers to reduce the number of operations
var combined = userGeneratedClassesProvider
.Collect()
.Combine(compilationProvider);
@@ -408,7 +407,7 @@ static string GetGenericConstraintsString(INamedTypeSymbol namedTypeSymbol)
{
var constraints = namedTypeSymbol.TypeParameters
.Select(GetGenericParameterConstraints)
- .Where(c => !string.IsNullOrEmpty(c));
+ .Where(static c => !string.IsNullOrEmpty(c));
return string.Join(" ", constraints);
}
From 1744eee0f50fdba646a28d04e73f2cdc9822f349 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 12:53:53 -0700
Subject: [PATCH 11/20] Remove temporary variable
---
.../TextAlignmentExtensionsGeneratorTests.cs | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
index ce0cfb1c..a3a2d378 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests/TextAlignmentExtensionsGeneratorTests.cs
@@ -50,9 +50,6 @@ public class GenericClass : Microsoft.Maui.ITextAlignment
}
""";
- var temp = GenerateSourceCode(textAlignmentExtensionsGeneratorFullName,
- new("GenericClass", "public", "MyNamespace", "", "", "where T : IDisposable, new() where U : class"));
-
// Act // Assert
await VerifySourceGeneratorAsync(
source,
From 1e00aea06f1c0f86bb9dcf73e12f29ee92230aa7 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 13:11:49 -0700
Subject: [PATCH 12/20] Refactor Source Generator
---
...i.Markup.SourceGenerators.UnitTests.csproj | 1 -
.../TextAlignmentExtensionsGenerator.cs | 24 ++++++++++---------
2 files changed, 13 insertions(+), 12 deletions(-)
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
index 752e70f8..097cc1ad 100644
--- 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
@@ -11,7 +11,6 @@
-
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index 9b566f29..4b23ffa1 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -50,13 +50,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
static bool ShouldGenerateTextAlignmentExtension(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
{
- return ImplementsInterfaceIgnoringBaseType(classSymbol, iTextAlignmentInterfaceSymbol)
- && !ImplementsInterface(classSymbol.BaseType, iTextAlignmentInterfaceSymbol);
+ return DoesImplementInterfaceIgnoringBaseType(classSymbol, iTextAlignmentInterfaceSymbol)
+ && !DoesImplementInterface(classSymbol.BaseType, iTextAlignmentInterfaceSymbol);
- static bool ImplementsInterfaceIgnoringBaseType(INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol)
+ static bool DoesImplementInterfaceIgnoringBaseType(INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol)
=> classSymbol.AllInterfaces.Contains(interfaceSymbol);
- static bool ImplementsInterface(INamedTypeSymbol? classSymbol, INamedTypeSymbol interfaceSymbol)
+ static bool DoesImplementInterface(INamedTypeSymbol? classSymbol, INamedTypeSymbol interfaceSymbol)
=> classSymbol?.AllInterfaces.Contains(interfaceSymbol) ?? false;
}
@@ -419,7 +419,9 @@ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
// Primary constraint (class, struct, unmanaged)
if (typeParameter.HasReferenceTypeConstraint)
{
- constraints.Add("class");
+ constraints.Add(typeParameter.ReferenceTypeConstraintNullableAnnotation is NullableAnnotation.Annotated
+ ? "class?"
+ : "class");
}
else if (typeParameter.HasValueTypeConstraint)
{
@@ -437,12 +439,12 @@ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
// Secondary constraints (specific types)
foreach (var constraintType in typeParameter.ConstraintTypes)
{
- var constraintTypeString = constraintType.NullableAnnotation switch
- {
- NullableAnnotation.Annotated => string.Concat(constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), '?'),
- _ => constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
- };
+ var symbolDisplayFormat = new SymbolDisplayFormat(
+ typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
+ miscellaneousOptions: SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
+ var constraintTypeString = constraintType.ToDisplayString(symbolDisplayFormat);
+
constraints.Add(constraintTypeString);
}
@@ -457,7 +459,7 @@ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
{
constraints.Add("new()");
}
-
+
return constraints.Count > 0
? $"where {typeParameter.Name} : {string.Join(", ", constraints)}"
: string.Empty;
From 8b00b91df850421fade638bcfd4b77cef2be85df Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 15:27:59 -0700
Subject: [PATCH 13/20] Update
CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj
---
...ommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj | 1 +
1 file changed, 1 insertion(+)
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
index 097cc1ad..752e70f8 100644
--- 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
@@ -11,6 +11,7 @@
+
From 696fbf3df15181f0f4e61fe9437bea7c784c7112 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 15:41:29 -0700
Subject: [PATCH 14/20] Add Benchmarks
---
azure-pipelines.yml | 8 ++++++-
.../CommunityToolkit.Maui.Markup.Sample.sln | 18 +++++++++++++--
....Markup.SourceGenerators.Benchmarks.csproj | 22 +++++++++++++++++++
.../Program.cs | 12 ++++++++++
...tAlignmentExtensionsGeneratorBenchmarks.cs | 22 +++++++++++++++++++
src/CommunityToolkit.Maui.Markup.sln | 22 ++++++++++++++++---
6 files changed, 98 insertions(+), 6 deletions(-)
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks.csproj
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/Program.cs
create mode 100644 src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 7afd6edd..f461feb5 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -13,6 +13,7 @@ variables:
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
@@ -291,10 +292,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 37316951..d55ee66f 100644
--- a/samples/CommunityToolkit.Maui.Markup.Sample.sln
+++ b/samples/CommunityToolkit.Maui.Markup.Sample.sln
@@ -20,9 +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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests", "..\src\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj", "{26D5485B-68EA-454A-BBB7-11C6FF9B1B98}"
+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
@@ -56,12 +62,20 @@ Global
{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..14ab12ac
--- /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 VerifyNoErrorsWhenUseMauiCommunityToolkitHasAdditonalWhitespace()
+ {
+ return textAlignmentExtensionsGeneratorTests.VerifyGeneratedSource_WhenClassImplementsITextAlignmentInterface();
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Markup.sln b/src/CommunityToolkit.Maui.Markup.sln
index eb7c3c43..13cea65f 100644
--- a/src/CommunityToolkit.Maui.Markup.sln
+++ b/src/CommunityToolkit.Maui.Markup.sln
@@ -14,11 +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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests", "CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests\CommunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj", "{BB9611DF-34B9-4BBF-A564-EB2AADD943C8}"
+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
@@ -46,10 +52,20 @@ Global
{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
From 30d55d8754f37d2c01b72567685dab7ef56f6e19 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 15:43:39 -0700
Subject: [PATCH 15/20] Update TextAlignmentExtensionsGeneratorBenchmarks.cs
---
.../TextAlignmentExtensionsGeneratorBenchmarks.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs
index 14ab12ac..e9dd9702 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators.Benchmarks/TextAlignmentExtensionsGeneratorBenchmarks.cs
@@ -15,7 +15,7 @@ public Task VerifyGeneratedSource_WhenClassIsGeneric()
}
[Benchmark]
- public Task VerifyNoErrorsWhenUseMauiCommunityToolkitHasAdditonalWhitespace()
+ public Task VerifyGeneratedSource_WhenClassImplementsITextAlignmentInterface()
{
return textAlignmentExtensionsGeneratorTests.VerifyGeneratedSource_WhenClassImplementsITextAlignmentInterface();
}
From f8697d0f6de78301a5f9233229fba30f635a3272 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 15:52:10 -0700
Subject: [PATCH 16/20] Remove `PrivateAssets`
---
...unityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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
index 752e70f8..e604ddf1 100644
--- 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
@@ -10,8 +10,8 @@
-
-
+
+
From 2ed054ebc576b3002f2b0f8207259ba61c4e4448 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 15:53:44 -0700
Subject: [PATCH 17/20] Remove unnecessary NuGet Packages
---
...mmunityToolkit.Maui.Markup.SourceGenerators.UnitTests.csproj | 2 --
1 file changed, 2 deletions(-)
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
index e604ddf1..c458156f 100644
--- 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
@@ -20,8 +20,6 @@
-
-
From efa18ffb93e63ff64c05f35b9e79e4af46fc454c Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 16:10:35 -0700
Subject: [PATCH 18/20] Update TextAlignmentExtensionsGenerator.cs
---
.../TextAlignmentExtensionsGenerator.cs | 25 ++++++++-----------
1 file changed, 11 insertions(+), 14 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index 4b23ffa1..49eda672 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -24,7 +24,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(textAlignmentInterface);
if (iTextAlignmentInterfaceSymbol is null)
{
- return default; // Early return to avoid unnecessary processing
+ return default;
}
var classSymbol = syntaxContext.SemanticModel.GetDeclaredSymbol((ClassDeclarationSyntax)syntaxContext.Node, ct);
@@ -39,13 +39,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var compilationProvider = context.CompilationProvider;
- // Combine providers to reduce the number of operations
var combined = userGeneratedClassesProvider
.Collect()
.Combine(compilationProvider);
- // Register the source output
- context.RegisterSourceOutput(combined, static (spc, source) => Execute(spc, source.Right, [..source.Left.Select(static x => x.ClassSymbol)]));
+ context.RegisterSourceOutput(combined, static (spc, source) => Execute(spc, source.Right, source.Left.ToImmutableArray()));
}
static bool ShouldGenerateTextAlignmentExtension(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
@@ -60,7 +58,7 @@ static bool DoesImplementInterface(INamedTypeSymbol? classSymbol, INamedTypeSymb
=> classSymbol?.AllInterfaces.Contains(interfaceSymbol) ?? false;
}
- static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray userClasses)
+ static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray<(INamedTypeSymbol ClassSymbol, INamedTypeSymbol ITextAlignmentInterfaceSymbol)> userClasses)
{
var mauiAssembly = compilation.SourceModule.ReferencedAssemblySymbols.FirstOrDefault(static a => a.Name == mauiControlsAssembly);
if (mauiAssembly is null)
@@ -76,10 +74,9 @@ static void Execute(SourceProductionContext context, Compilation compilation, Im
var mauiClasses = GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol);
- // Use HashSet for faster lookup
var processedClasses = new HashSet(SymbolEqualityComparer.Default);
- foreach (var classSymbol in userClasses.Concat(mauiClasses))
+ foreach (var (classSymbol, _) in userClasses.Concat(mauiClasses.Select(c => (c, iTextAlignmentInterfaceSymbol))))
{
if (processedClasses.Add(classSymbol))
{
@@ -102,18 +99,18 @@ static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbo
_ => string.Empty
};
- static void GenerateExtensionClass(SourceProductionContext context, TextAlignmentClassMetadata metadata)
+ static void GenerateExtensionClass(SourceProductionContext context, in TextAlignmentClassMetadata metadata)
{
- var source = GenerateExtensionClassSource(metadata);
+ var source = GenerateExtensionClassSource(in metadata);
context.AddSource($"{metadata.ClassName}TextAlignmentExtensions.g.cs", SourceText.From(source, Encoding.UTF8));
}
- static string GenerateExtensionClassSource(TextAlignmentClassMetadata metadata)
+ static string GenerateExtensionClassSource(in TextAlignmentClassMetadata metadata)
{
var assemblyVersion = typeof(TextAlignmentExtensionsGenerator).Assembly.GetName().Version.ToString();
var className = typeof(TextAlignmentExtensionsGenerator).FullName;
- var sb = new StringBuilder();
+ var sb = new StringBuilder(8192); // Pre-allocate with an estimated capacity
sb.AppendLine( /* language=C#-test */
$$"""
//
@@ -419,8 +416,8 @@ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
// Primary constraint (class, struct, unmanaged)
if (typeParameter.HasReferenceTypeConstraint)
{
- constraints.Add(typeParameter.ReferenceTypeConstraintNullableAnnotation is NullableAnnotation.Annotated
- ? "class?"
+ constraints.Add(typeParameter.ReferenceTypeConstraintNullableAnnotation is NullableAnnotation.Annotated
+ ? "class?"
: "class");
}
else if (typeParameter.HasValueTypeConstraint)
@@ -442,7 +439,7 @@ static string GetGenericParameterConstraints(ITypeParameterSymbol typeParameter)
var symbolDisplayFormat = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
-
+
var constraintTypeString = constraintType.ToDisplayString(symbolDisplayFormat);
constraints.Add(constraintTypeString);
From f93e9d91e83b6885d8afc573a8b27063e3578abf Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 16:43:36 -0700
Subject: [PATCH 19/20] Revert Source Generator for Benchmarking
---
.../TextAlignmentExtensionsGenerator.cs | 729 ++++++++----------
1 file changed, 324 insertions(+), 405 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index 49eda672..606d6b65 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -1,95 +1,346 @@
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)]
-public class TextAlignmentExtensionsGenerator : IIncrementalGenerator
+class TextAlignmentExtensionsGenerator : IIncrementalGenerator
{
- const string textAlignmentInterface = "Microsoft.Maui.ITextAlignment";
+ const string iTextAlignmentInterface = "Microsoft.Maui.ITextAlignment";
const string mauiControlsAssembly = "Microsoft.Maui.Controls";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- IncrementalValuesProvider<(INamedTypeSymbol ClassSymbol, INamedTypeSymbol ITextAlignmentInterfaceSymbol)> userGeneratedClassesProvider = context.SyntaxProvider
- .CreateSyntaxProvider(
- static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax { BaseList: not null },
- static (syntaxContext, ct) =>
+ // 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)
{
- 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()));
+ // 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);
}
static bool ShouldGenerateTextAlignmentExtension(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
{
- return DoesImplementInterfaceIgnoringBaseType(classSymbol, iTextAlignmentInterfaceSymbol)
- && !DoesImplementInterface(classSymbol.BaseType, iTextAlignmentInterfaceSymbol);
+ return ImplementsInterfaceIgnoringBaseType(classSymbol, iTextAlignmentInterfaceSymbol)
+ && DoesNotImplementInterface(classSymbol.BaseType, iTextAlignmentInterfaceSymbol);
- static bool DoesImplementInterfaceIgnoringBaseType(INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol)
- => classSymbol.AllInterfaces.Contains(interfaceSymbol);
+ static bool ImplementsInterfaceIgnoringBaseType(INamedTypeSymbol classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
+ => classSymbol.Interfaces.Any(i => i.Equals(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default) || i.AllInterfaces.Contains(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default));
- static bool DoesImplementInterface(INamedTypeSymbol? classSymbol, INamedTypeSymbol interfaceSymbol)
- => classSymbol?.AllInterfaces.Contains(interfaceSymbol) ?? false;
+ static bool DoesNotImplementInterface(INamedTypeSymbol? classSymbol, INamedTypeSymbol iTextAlignmentInterfaceSymbol)
+ => classSymbol is null || !classSymbol.AllInterfaces.Any(i => i.Equals(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default));
}
- static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray<(INamedTypeSymbol ClassSymbol, INamedTypeSymbol ITextAlignmentInterfaceSymbol)> userClasses)
+ static void ExecuteArray(SourceProductionContext context, EquatableArray metadataArray)
{
- var mauiAssembly = compilation.SourceModule.ReferencedAssemblySymbols.FirstOrDefault(static a => a.Name == mauiControlsAssembly);
- if (mauiAssembly is null)
+ foreach (var metadata in metadataArray.AsImmutableArray())
{
- return;
+ Execute(context, metadata);
}
+ }
- var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(textAlignmentInterface);
- if (iTextAlignmentInterfaceSymbol is null)
+ static void Execute(SourceProductionContext context, [NotNull] TextAlignmentClassMetadata? textAlignmentClassMetadata)
+ {
+ if (textAlignmentClassMetadata is null)
{
- return;
+ throw new ArgumentNullException(nameof(textAlignmentClassMetadata));
}
- var mauiClasses = GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol);
+ var className = typeof(TextAlignmentExtensionsGenerator).FullName;
+ var assemblyVersion = typeof(TextAlignmentExtensionsGenerator).Assembly.GetName().Version.ToString();
- var processedClasses = new HashSet(SymbolEqualityComparer.Default);
+ var genericTypeParameters = GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments);
+ var genericArguments = GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments);
+ var source = $$"""
+//
+// See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator
- foreach (var (classSymbol, _) in userClasses.Concat(mauiClasses.Select(c => (c, iTextAlignmentInterfaceSymbol))))
- {
- if (processedClasses.Add(classSymbol))
- {
- var metadata = GenerateMetadata(classSymbol);
- GenerateExtensionClass(context, metadata);
- }
- }
+#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]
+ {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}}
+ {
+ ///
+ /// =
+ ///
+ ///
+ /// 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));
}
- static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbol mauiAssembly, INamedTypeSymbol iTextAlignmentSymbol)
+ static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbol mauiControlsAssemblySymbolProvider, INamedTypeSymbol itextAlignmentSymbol)
{
- return mauiAssembly.GlobalNamespace.GetNamedTypeSymbols()
- .Where(x => ShouldGenerateTextAlignmentExtension(x, iTextAlignmentSymbol));
+ return mauiControlsAssemblySymbolProvider.GlobalNamespace.GetNamedTypeSymbols().Where(x => ShouldGenerateTextAlignmentExtension(x, itextAlignmentSymbol)).Select(GenerateMetadata);
}
static string GetClassAccessModifier(INamedTypeSymbol namedTypeSymbol) => namedTypeSymbol.DeclaredAccessibility switch
@@ -99,366 +350,34 @@ static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbo
_ => string.Empty
};
- static void GenerateExtensionClass(SourceProductionContext context, in TextAlignmentClassMetadata metadata)
- {
- var source = GenerateExtensionClassSource(in metadata);
- context.AddSource($"{metadata.ClassName}TextAlignmentExtensions.g.cs", SourceText.From(source, Encoding.UTF8));
- }
-
- static string GenerateExtensionClassSource(in TextAlignmentClassMetadata metadata)
- {
- var assemblyVersion = typeof(TextAlignmentExtensionsGenerator).Assembly.GetName().Version.ToString();
- var className = typeof(TextAlignmentExtensionsGenerator).FullName;
-
- 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 = namedTypeSymbol.ContainingNamespace.ToDisplayString() == mauiControlsAssembly
- ? "internal"
- : GetClassAccessModifier(namedTypeSymbol);
-
- 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)
+ static string GetGenericTypeParametersDeclarationString(in string genericArguments)
{
- if (namedTypeSymbol.TypeParameters.Length is 0)
+ if (string.IsNullOrWhiteSpace(genericArguments))
{
return "";
}
- var typeParams = string.Join(", ", namedTypeSymbol.TypeParameters.Select(t => t.Name));
- return $"";
+ return $"";
}
- static string GetGenericArgumentsString(INamedTypeSymbol namedTypeSymbol)
+ static string GetGenericArgumentsString(in string genericArguments)
{
- 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)
+ if (string.IsNullOrWhiteSpace(genericArguments))
{
- constraints.Add("unmanaged");
+ return string.Empty;
}
- 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");
- }
+ return $"<{genericArguments}>";
+ }
- // Constructor constraint (must be last)
- if (typeParameter.HasConstructorConstraint)
- {
- constraints.Add("new()");
- }
+ static TextAlignmentClassMetadata GenerateMetadata(INamedTypeSymbol namedTypeSymbol)
+ {
+ var accessModifier = mauiControlsAssembly == namedTypeSymbol.ContainingNamespace.ToDisplayString()
+ ? "internal"
+ : GetClassAccessModifier(namedTypeSymbol);
- return constraints.Count > 0
- ? $"where {typeParameter.Name} : {string.Join(", ", constraints)}"
- : string.Empty;
+ return new(namedTypeSymbol.Name, accessModifier, namedTypeSymbol.ContainingNamespace.ToDisplayString(), namedTypeSymbol.TypeArguments.GetGenericTypeArgumentsString(), namedTypeSymbol.GetGenericTypeConstraintsAsString());
}
+
+ record TextAlignmentClassMetadata(string ClassName, string ClassAcessModifier, string Namespace, string GenericArguments, string GenericConstraints);
}
\ No newline at end of file
From 59c136f204bfa35ec6e7e330c6683e0cf2957a16 Mon Sep 17 00:00:00 2001
From: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Date: Sun, 6 Oct 2024 16:56:12 -0700
Subject: [PATCH 20/20] Revert "Revert Source Generator for Benchmarking"
This reverts commit f93e9d91e83b6885d8afc573a8b27063e3578abf.
---
.../TextAlignmentExtensionsGenerator.cs | 729 ++++++++++--------
1 file changed, 405 insertions(+), 324 deletions(-)
diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
index 606d6b65..49eda672 100644
--- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
+++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs
@@ -1,346 +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 = $$"""
-//
-// 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 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
@@ -350,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