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 e5dc1c58..7e4b6cdf 100644 --- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/CommunityToolkit.Maui.Markup.SourceGenerators.csproj +++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/CommunityToolkit.Maui.Markup.SourceGenerators.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -10,6 +10,7 @@ + diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/CompilationExtensions.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/CompilationExtensions.cs deleted file mode 100644 index f6a760f8..00000000 --- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/CompilationExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace CommunityToolkit.Maui.Markup.SourceGenerators; - -static class CompilationExtensions -{ - public static TSymbol? GetSymbol(this Compilation compilation, BaseTypeDeclarationSyntax declarationSyntax) - where TSymbol : ISymbol - { - var model = compilation.GetSemanticModel(declarationSyntax.SyntaxTree); - return (TSymbol?)model.GetDeclaredSymbol(declarationSyntax); - } -} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/EquatableArray{T}.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/EquatableArray{T}.cs new file mode 100644 index 00000000..c3848c89 --- /dev/null +++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/EquatableArray{T}.cs @@ -0,0 +1,190 @@ +using System.Collections; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +// Inspired by https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray%7BT%7D.cs +namespace CommunityToolkit.Maui.Markup.SourceGenerators; + +/// +/// Extensions for . +/// +static class EquatableArray +{ + /// + /// Creates an instance from a given . + /// + /// The type of items in the input array. + /// The input instance. + /// An instance from a given . + public static EquatableArray AsEquatableArray(this ImmutableArray array) + where T : IEquatable + { + return new(array); + } +} + +/// +/// An imutable, equatable array. This is equivalent to but with value equality support. +/// +/// The type of values in the array. +readonly struct EquatableArray : IEquatable>, IEnumerable + where T : IEquatable +{ + /// + /// The underlying array. + /// + readonly T[]? array; + + /// + /// Creates a new instance. + /// + /// The input to wrap. + public EquatableArray(ImmutableArray array) + { + this.array = Unsafe.As, T[]?>(ref array); + } + + /// + /// Gets a reference to an item at a specified position within the array. + /// + /// The index of the item to retrieve a reference to. + /// A reference to an item at a specified position within the array. + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref AsImmutableArray().ItemRef(index); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator EquatableArray(ImmutableArray array) + { + return FromImmutableArray(array); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator ImmutableArray(EquatableArray array) + { + return array.AsImmutableArray(); + } + + /// + /// Checks whether two values are the same. + /// + /// The first value. + /// The second value. + /// Whether and are equal. + public static bool operator ==(EquatableArray left, EquatableArray right) + { + return left.Equals(right); + } + + /// + /// Checks whether two values are not the same. + /// + /// The first value. + /// The second value. + /// Whether and are not equal. + public static bool operator !=(EquatableArray left, EquatableArray right) + { + return !left.Equals(right); + } + + /// + /// Gets a value indicating whether the current array is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsEmpty; + } + + /// + /// Creates an instance from a given . + /// + /// The input instance. + /// An instance from a given . + public static EquatableArray FromImmutableArray(ImmutableArray array) + { + return new(array); + } + + /// + public bool Equals(EquatableArray array) + { + return AsSpan().SequenceEqual(array.AsSpan()); + } + + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is EquatableArray array && Equals(this, array); + } + + /// + public override int GetHashCode() + { + if (this.array is not T[] array) + { + return 0; + } + + // Not ideal, but does the job. Diverges from original implementation https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray%7BT%7D.cs + return array.Length; + } + + /// + /// Gets an instance from the current . + /// + /// The from the current . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray AsImmutableArray() + { + return Unsafe.As>(ref Unsafe.AsRef(in this.array)); + } + + /// + /// Returns a wrapping the current items. + /// + /// A wrapping the current items. + public ReadOnlySpan AsSpan() + { + return AsImmutableArray().AsSpan(); + } + + /// + /// Copies the contents of this instance. to a mutable array. + /// + /// The newly instantiated array. + public T[] ToArray() + { + return AsImmutableArray().ToArray(); + } + + /// + /// Gets an value to traverse items in the current array. + /// + /// An value to traverse items in the current array. + public ImmutableArray.Enumerator GetEnumerator() + { + return AsImmutableArray().GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/NamespaceSymbolExtensions.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/NamespaceSymbolExtensions.cs index 45ae583a..1a0398f2 100644 --- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/NamespaceSymbolExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/NamespaceSymbolExtensions.cs @@ -142,23 +142,4 @@ public static string GetGenericTypeConstraintsAsString(this INamedTypeSymbol typ return result.ToString(); } - - public static bool ContainsSymbolBaseType(this IEnumerable namedSymbolList, INamedTypeSymbol symbol) - { - INamedTypeSymbol? baseType = symbol.BaseType; - - while (baseType is not null) - { - var doesListContainBaseType = namedSymbolList.Any(x => x.Equals(baseType, SymbolEqualityComparer.Default)); - - if (doesListContainBaseType) - { - return true; - } - - baseType = baseType.BaseType; - } - - return false; - } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/SourceStringExtensions.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/SourceStringExtensions.cs deleted file mode 100644 index fef3cd26..00000000 --- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/Extensions/SourceStringExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Text; - -namespace CommunityToolkit.Maui.Markup.SourceGenerators; - -static class SourceStringExtensions -{ - public static void FormatText(ref string classSource, CSharpParseOptions? options = null) - { - var source = CSharpSyntaxTree.ParseText(SourceText.From(classSource, Encoding.UTF8), options); - var formattedRoot = (CSharpSyntaxNode)source.GetRoot().NormalizeWhitespace(); - - classSource = CSharpSyntaxTree.Create(formattedRoot).ToString(); - } -} \ 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 ba9283eb..8b76bd00 100644 --- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs +++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -25,39 +23,42 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { var compilation = context.SemanticModel.Compilation; - var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(iTextAlignmentInterface); + 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 (iTextAlignmentInterfaceSymbol is null) + if (classSymbol is null || classSymbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken) != context.Node) { - throw new Exception("There's no .NET MAUI referenced in the project."); + // In case of multiple partial declarations, we want to run only once. + // So we run only for the first syntax reference. + return null; } - var classSymbol = (INamedTypeSymbol?)context.SemanticModel.GetDeclaredSymbol(context.Node); - - // If the ClassDlecarationSyntax doesn't implements those interfaces we just return null - if (classSymbol?.AllInterfaces.Contains(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default) is not true) + while (classSymbol is not null) { - return null; + if (classSymbol.ContainingAssembly.Name == mauiControlsAssembly) + { + break; + } + + if (classSymbol.Interfaces.Any(i => i.Equals(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default) || i.AllInterfaces.Contains(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default))) + { + return GenerateMetadata(classSymbol); + } + + classSymbol = classSymbol.BaseType; } - return classSymbol; - }); + return 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); - - if (iTextAlignmentInterfaceSymbol is null) - { - throw new Exception("There's no .NET MAUI referenced in the project."); - } - + 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); - var symbols = GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol).Where(static x => x is not null); - return symbols; + return EquatableArray.AsEquatableArray(GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol).ToImmutableArray()); }); @@ -65,276 +66,272 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // 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 - var inputs = userGeneratedClassesProvider.Collect() - .Combine(mauiControlsAssemblySymbolProvider) - .SelectMany(static (x, _) => Deduplicate(x.Left, x.Right).ToImmutableArray()) - .Select(static (x, _) => GenerateMetadata(x)); + context.RegisterSourceOutput(userGeneratedClassesProvider, Execute); + context.RegisterSourceOutput(mauiControlsAssemblySymbolProvider, ExecuteArray); + } - context.RegisterSourceOutput(inputs, Execute); + static void ExecuteArray(SourceProductionContext context, EquatableArray metadataArray) + { + foreach (var metadata in metadataArray.AsImmutableArray()) + { + Execute(context, metadata); + } } - static void Execute(SourceProductionContext context, TextAlignmentClassMetadata textAlignmentClassMetadata) + static void Execute(SourceProductionContext context, [NotNull] TextAlignmentClassMetadata? textAlignmentClassMetadata) { - var textColorToBuilder = $$""" + if (textAlignmentClassMetadata is null) + { + throw new ArgumentNullException(nameof(textAlignmentClassMetadata)); + } + + var genericTypeParameters = GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments); + var genericArguments = GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments); + var source = $$""" // // See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator #nullable enable - using System; using Microsoft.Maui; using Microsoft.Maui.Controls; namespace CommunityToolkit.Maui.Markup { - /// - /// Extension Methods for - /// - {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}} - { - /// - /// = - /// - /// - /// with added - public static TAssignable TextStart{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start; - return textAlignmentControl; - } - - /// - /// = - /// - /// - /// with added - public static TAssignable TextCenterHorizontal{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.HorizontalTextAlignment = TextAlignment.Center; - return textAlignmentControl; - } - - /// - /// = - /// - /// - /// with added - public static TAssignable TextEnd{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.HorizontalTextAlignment = TextAlignment.End; - return textAlignmentControl; - } - - /// - /// = - /// - /// - /// with added - public static TAssignable TextTop{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.VerticalTextAlignment = TextAlignment.Start; - return textAlignmentControl; - } - - /// - /// = - /// - /// - /// with added - public static TAssignable TextCenterVertical{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.VerticalTextAlignment = TextAlignment.Center; - return textAlignmentControl; - } - - /// - /// = - /// - /// - /// with added - public static TAssignable TextBottom{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.VerticalTextAlignment = TextAlignment.End; - return textAlignmentControl; - } - - /// - /// = = - /// - /// - /// with added - public static TAssignable TextCenter{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - => textAlignmentControl.TextCenterHorizontal{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}().TextCenterVertical{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(); - } - - - // 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 + /// + {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}} { - /// - /// Extension Methods for - /// - {{textAlignmentClassMetadata.ClassAcessModifier}} static partial class TextAlignmentExtensions_{{textAlignmentClassMetadata.ClassName}} - { - /// - /// = - /// - /// - /// with - public static TAssignable TextLeft{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start; - return textAlignmentControl; - } + /// + /// = + /// + /// + /// with added + public static TAssignable 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}}(); + } - /// - /// = - /// - /// - /// with - public static TAssignable TextRight{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(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 - textAlignmentControl.HorizontalTextAlignment = TextAlignment.End; - return textAlignmentControl; - } - } + namespace LeftToRight + { + /// + /// 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.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{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.HorizontalTextAlignment = TextAlignment.End; - return textAlignmentControl; - } - - /// - /// = - /// - /// - /// with - public static TAssignable TextRight{{GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments)}}(this TAssignable textAlignmentControl) where TAssignable : {{textAlignmentClassMetadata.Namespace}}.{{textAlignmentClassMetadata.ClassName}}{{GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments)}}{{textAlignmentClassMetadata.GenericConstraints}} - { - ArgumentNullException.ThrowIfNull(textAlignmentControl); - - if (textAlignmentControl is not ITextAlignment) - { - throw new ArgumentException($"Element must implement {nameof(ITextAlignment)}", nameof(textAlignmentControl)); - } - - textAlignmentControl.HorizontalTextAlignment = TextAlignment.Start; - return textAlignmentControl; - } - } + /// + /// 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; + } + } } } """; - var source = textColorToBuilder.ToString(); - SourceStringExtensions.FormatText(ref source); context.AddSource($"{textAlignmentClassMetadata.ClassName}TextAlignmentExtensions.g.cs", SourceText.From(source, Encoding.UTF8)); } - static IEnumerable Deduplicate(ImmutableArray left, IEnumerable right) - { - foreach (var leftItem in left) - { - if (leftItem is null) - { - continue; - } - - var result = right.ContainsSymbolBaseType(leftItem); - if (!result) - { - yield return leftItem; - } - } - - foreach (var rightItem in right) - { - yield return rightItem; - } - } - - static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbol mauiControlsAssemblySymbolProvider, INamedTypeSymbol itextAlignmentSymbol) + static IEnumerable GetMauiInterfaceImplementors(IAssemblySymbol mauiControlsAssemblySymbolProvider, INamedTypeSymbol itextAlignmentSymbol) { - return mauiControlsAssemblySymbolProvider.GlobalNamespace.GetNamedTypeSymbols().Where(x => x.AllInterfaces.Contains(itextAlignmentSymbol, SymbolEqualityComparer.Default)); + return mauiControlsAssemblySymbolProvider.GlobalNamespace.GetNamedTypeSymbols().Where(x => x.AllInterfaces.Contains(itextAlignmentSymbol, SymbolEqualityComparer.Default)).Select(GenerateMetadata); } static string GetClassAccessModifier(INamedTypeSymbol namedTypeSymbol) => namedTypeSymbol.DeclaredAccessibility switch diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs index d29ab8d5..a369e67c 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using System.Reflection; using CommunityToolkit.Maui.Markup.UnitTests.Base; using CommunityToolkit.Maui.UnitTests.Extensions.TextAlignmentExtensions; -using Microsoft.Maui; -using Microsoft.Maui.Controls; using NUnit.Framework; using Unique.Namespace.To.Test.Interface; @@ -374,6 +369,22 @@ public void SupportDerivedFromBindable() .Italic()); } + [Test] + public void SupportPartialClasses() + { + var partialClassControl = new PartialClassControl() + .TextStart() + .TextCenterHorizontal() + .TextEnd() + .TextTop() + .TextCenterVertical() + .TextBottom() + .TextCenter(); + + Assert.True(partialClassControl.IsPartial); + Assert.IsInstanceOf(partialClassControl); + } + [Test] public void SupportCustomTextAlignment() { @@ -483,6 +494,18 @@ class BrandNewControl : View, ITextAlignment public TextAlignment VerticalTextAlignment { get; set; } } + + partial class PartialClassControl : View, ITextAlignment + { + public TextAlignment HorizontalTextAlignment { get; set; } + + public TextAlignment VerticalTextAlignment { get; set; } + } + + partial class PartialClassControl + { + public bool IsPartial { get; } = true; + } } namespace Unique.Namespace.To.Test.Interface