From b0f260228e348ababa1ca927e56342c4bfc04bf6 Mon Sep 17 00:00:00 2001 From: Gerald Versluis Date: Wed, 18 Mar 2026 11:18:45 +0100 Subject: [PATCH 1/2] Filter out non-public types in TextColorToGenerator assembly scanning The GetMauiInterfaceImplementors method scans all namespace-level types in the Microsoft.Maui.Controls assembly but did not filter by DeclaredAccessibility. This caused it to pick up internal types and generate extension methods referencing them, resulting in CS0122 compile errors in consuming projects. Add a DeclaredAccessibility == Accessibility.Public check so only public types are included in code generation. Fixes #3160 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generators/TextColorToGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.SourceGenerators/Generators/TextColorToGenerator.cs b/src/CommunityToolkit.Maui.SourceGenerators/Generators/TextColorToGenerator.cs index 6a0688c5ed..5dfb4e500d 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators/Generators/TextColorToGenerator.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators/Generators/TextColorToGenerator.cs @@ -249,7 +249,8 @@ static IEnumerable Deduplicate(ImmutableArray GetMauiInterfaceImplementors(IAssemblySymbol mauiControlsAssemblySymbolProvider, INamedTypeSymbol iAnimatableSymbol, INamedTypeSymbol itextStyleSymbol) { - return mauiControlsAssemblySymbolProvider.GlobalNamespace.GetNamedTypeSymbols().Where(x => x.AllInterfaces.Contains(itextStyleSymbol, SymbolEqualityComparer.Default) + return mauiControlsAssemblySymbolProvider.GlobalNamespace.GetNamedTypeSymbols().Where(x => x.DeclaredAccessibility == Accessibility.Public + && x.AllInterfaces.Contains(itextStyleSymbol, SymbolEqualityComparer.Default) && x.AllInterfaces.Contains(iAnimatableSymbol, SymbolEqualityComparer.Default)); } From 0e8a3d529f9d80bd3955e9b08856f8b31292f1f5 Mon Sep 17 00:00:00 2001 From: Gerald Versluis Date: Wed, 18 Mar 2026 11:34:42 +0100 Subject: [PATCH 2/2] Add unit test verifying internal types are excluded from generation Uses a synthetic Microsoft.Maui.Controls assembly with both a public and an internal type implementing ITextStyle + IAnimatable. Asserts that the generator only emits code for the public type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.props | 1 + .../TextColorToGeneratorTests.cs | 122 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/CommunityToolkit.Maui.SourceGenerators.UnitTests/TextColorToGeneratorTests/TextColorToGeneratorTests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 71102538c0..9d5af4701a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -243,6 +243,7 @@ https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitati + diff --git a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/TextColorToGeneratorTests/TextColorToGeneratorTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/TextColorToGeneratorTests/TextColorToGeneratorTests.cs new file mode 100644 index 0000000000..9c2d007edc --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/TextColorToGeneratorTests/TextColorToGeneratorTests.cs @@ -0,0 +1,122 @@ +using System.IO; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using CommunityToolkit.Maui.SourceGenerators.Generators; +using Xunit; + +namespace CommunityToolkit.Maui.SourceGenerators.UnitTests; + +public class TextColorToGeneratorTests +{ + [Fact] + public async Task Generator_DoesNotEmitCode_ForInternalMauiControlsTypes() + { + var cancellationToken = TestContext.Current.CancellationToken; + // Arrange: create a synthetic "Microsoft.Maui.Controls" assembly that + // contains both a public and an internal type implementing ITextStyle + IAnimatable. + const string syntheticMauiSource = + """ + namespace Microsoft.Maui + { + public interface ITextStyle + { + Microsoft.Maui.Graphics.Color TextColor { get; set; } + } + } + + namespace Microsoft.Maui.Graphics + { + public class Color + { + public float Red { get; } + public float Green { get; } + public float Blue { get; } + public float Alpha { get; } + + public Color WithRed(float v) => this; + public Color WithGreen(float v) => this; + public Color WithBlue(float v) => this; + public Color WithAlpha(float v) => this; + } + + public static class Colors + { + public static Color Transparent => new(); + } + } + + namespace Microsoft.Maui.Controls + { + public interface IAnimatable { } + + public class BindableObject + { + public void Animate(string name, Animation animation, uint rate, uint length, Easing easing) { } + } + + public class Animation : System.Collections.IEnumerable + { + public void Add(double beginAt, double finishAt, Animation animation) { } + public void Commit(IAnimatable owner, string name, uint rate = 16, uint length = 250, Easing easing = null, System.Action finished = null) { } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => null; + } + + public class Easing { } + + public class View : BindableObject, Microsoft.Maui.ITextStyle, IAnimatable + { + public Microsoft.Maui.Graphics.Color TextColor { get; set; } + } + + public class PublicLabel : View { } + internal class InternalLabel : View { } + } + """; + + var coreRef = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); + + var mauiCompilation = CSharpCompilation.Create( + "Microsoft.Maui.Controls", + [CSharpSyntaxTree.ParseText(syntheticMauiSource, cancellationToken: cancellationToken)], + [coreRef], + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + using var ms = new MemoryStream(); + var emitResult = mauiCompilation.Emit(ms, cancellationToken: cancellationToken); + Assert.True(emitResult.Success, + "Synthetic Maui assembly failed to compile: " + + string.Join("\n", emitResult.Diagnostics)); + + ms.Seek(0, SeekOrigin.Begin); + var mauiRef = MetadataReference.CreateFromStream(ms); + + // A trivial user compilation that references the synthetic assembly. + const string userSource = + """ + namespace MyApp + { + public class Placeholder { } + } + """; + + var userCompilation = CSharpCompilation.Create( + "UserAssembly", + [CSharpSyntaxTree.ParseText(userSource, cancellationToken: cancellationToken)], + [coreRef, mauiRef], + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + // Act: run the generator + var generator = new TextColorToGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + driver = driver.RunGenerators(userCompilation, cancellationToken); + + var runResult = driver.GetRunResult(); + var generatedSources = runResult.GeneratedTrees + .Select(t => t.GetText(cancellationToken).ToString()) + .ToList(); + + // Assert: code is generated for the public type, but NOT the internal one. + Assert.Contains(generatedSources, src => src.Contains("PublicLabel")); + Assert.DoesNotContain(generatedSources, src => src.Contains("InternalLabel")); + } +}