Source generator

Eduardo Gomez 4,236 Reputation points
2025-06-08T21:28:58.7333333+00:00

Hello

I have a Class library, with a class

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using System.Text;

namespace FontAliasGenerator {

    // Step 1: Mark this class as an incremental source generator
    [Generator]
    public class FontAliasGenerator : IIncrementalGenerator {
        // Step 2: Initialize the generator and set up an incremental syntax provider pipeline
        public void Initialize(IncrementalGeneratorInitializationContext context) {

            var syntaxProvider = context.SyntaxProvider
                .CreateSyntaxProvider((node, _) => IsMauiProgram(node), // Step 3: Identify MauiProgram class
                    (ctx, _) => ctx.SemanticModel.Compilation) // Step 4: Extract Compilation object
                .Where(comp => comp is not null);

            // Step 5: Register the source output, generating code from extracted compilation data
            context.RegisterSourceOutput(syntaxProvider, GenerateCode);
        }

        // Step 6: Function to determine if a node belongs to MauiProgram.cs
        private bool IsMauiProgram(SyntaxNode node) {
            return node is CompilationUnitSyntax root &&
                   root.DescendantNodes().OfType<ClassDeclarationSyntax>()
                       .Any(c => c.Identifier.Text == "MauiProgram");
        }

        // Step 7: Main source generation logic—extract font aliases and add generated code
        private void GenerateCode(SourceProductionContext context, Compilation compilation) {

            context.ReportDiagnostic(Diagnostic.Create(
     new DiagnosticDescriptor(
         "FONT001",
         "Debug",
         "Source Generator is executing!",
         "SourceGenerator",
         DiagnosticSeverity.Info,
         true),
     Location.None));



            var fontAliases = ExtractFontAliases(compilation);
            context.AddSource("FontAliases.g.cs", GenerateFontAliasEnum(fontAliases));
        }

        // Step 8: Extract font alias names from AddFont method calls
        private List<string> ExtractFontAliases(Compilation compilation) {
            var fontAliases = new List<string>();

            // Step 9: Iterate over syntax trees to find MauiProgram.cs
            foreach(var syntaxTree in compilation.SyntaxTrees) {
                var root = syntaxTree.GetRoot();

                foreach(var invocation in root.DescendantNodes().OfType<InvocationExpressionSyntax>()) {
                    if(!invocation.Expression.ToString().Contains("AddFont")) {
                        continue; // Step 10: Filter only AddFont method calls
                    }

                    var arguments = invocation.ArgumentList.Arguments;
                    if(arguments.Count < 2) {
                        continue; // Step 11: Ensure we have both font file and alias
                    }

                    var semanticModel = compilation.GetSemanticModel(syntaxTree);
                    var aliasExpr = arguments[1].Expression;
                    var constValue = semanticModel.GetConstantValue(aliasExpr);

                    if(constValue.HasValue && constValue.Value is string alias) {
                        fontAliases.Add(alias); // Step 12: Extract and store alias
                    }
                }
            }

            return fontAliases;
        }

        // Step 13: Generate Enum for font aliases
        private string GenerateFontAliasEnum(List<string> fontAliases) {
            var sb = new StringBuilder();
            sb.AppendLine("namespace WindowsPhoneTile.Enums");
            sb.AppendLine("{");
            sb.AppendLine("    public enum FontAliases");
            sb.AppendLine("    {");

            foreach(var alias in fontAliases.Distinct()) {
                var safeName = Sanitize(alias); // Step 14: Clean up alias names for enums
                sb.AppendLine($"        {safeName},");
            }

            sb.AppendLine("    }");
            sb.AppendLine("}");
            return sb.ToString();
        }

        // Step 15: Function to sanitize alias names for enum compatibility
        private string Sanitize(string alias) {
            var sanitized = new string([.. alias.Where(char.IsLetterOrDigit)]);
            return string.IsNullOrEmpty(sanitized) ? "_" : char.IsLetter(sanitized[0]) ? sanitized : "_" + sanitized;
        }
    }
}

that search for my MauiProgram in my Maui app, and generates an Enum in my Enum folder with all my font alias

this is my structure

User's image

but every time I build it doesn't generate anything.

I also put

using FontAliasGenerator;

in my maui program

and inmy csproj file

<ItemGroup>
	<ProjectReference Include="..\FontAliasGenerator\FontAliasGenerator.csproj"
					  OutputItemType="Analyzer"
					  ReferenceOutputAssembly="true" />
</ItemGroup>
Developer technologies | .NET | .NET MAUI
0 comments No comments
{count} votes

Answer accepted by question author
  1. Anonymous
    2025-08-19T09:10:24.3266667+00:00

    Hello Eduardo Gomez!

    I see that you are trying to implement the source generator that inspects user code and generate new C# source files. Based on my review, here are a few issues I noticed:

    1. Wrong return type: In your code:
    var syntaxProvider = context.SyntaxProvider
        .CreateSyntaxProvider((node, _) => IsMauiProgram(node), // Step 3: Identify MauiProgram class
            (ctx, _) => ctx.SemanticModel.Compilation) // Step 4: Extract Compilation object
        .Where(comp => comp is not null);
    

    The (ctx, _) => ctx.SemanticModel.Compilation) return the wrong type. By returning the whole compilation, you’re not actually triggering anything useful. Since compilation is already available via context.CompilationProvider, you don’t need this implementation at all.

    The "transformer" is supposed to take the syntax node that passed the filter and transform it into a smaller, more relevant piece of data. Instead, it throws away the node and returns the entire Compilation object.

    1. The source output is never triggered: Since your pipeline is filtering for MauiProgram incorrectly and your provider returns Compilation instead of syntax nodes, the pipeline doesn’t produce anything, which causes the generator to remain silent.

    Solutions

    1. Update your code:

    The correct approach for an incremental generator would be to filter on the type of node you are interested in (e.g., a ClassDeclarationSyntax) and then use the semantic model to check if it has the properties you need.

    Update your Initialize function to match the filter and return type

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var addFontCalls =
            context.SyntaxProvider.CreateSyntaxProvider(
                static (node, _) => node is InvocationExpressionSyntax ies &&
                                    ies.Expression is MemberAccessExpressionSyntax ma &&
                                    ma.Name.Identifier.Text == "AddFont",
                static (ctx, _) => (InvocationExpressionSyntax)ctx.Node)
            .Collect();
     
        var compilationAndCalls = context.CompilationProvider.Combine(addFontCalls);
     
        context.RegisterSourceOutput(compilationAndCalls, Generate);
    }
    

    Update the rest of the function parameters accordingly.

    private static void GenerateCode(SourceProductionContext spc, (Compilation compilation, ImmutableArray<InvocationExpressionSyntax> invocations) pair)
    {
        var (compilation, invocations) = pair;
        var aliases = ExtractFontAliases(compilation, invocations);
     
        spc.ReportDiagnostic(Diagnostic.Create(
            FontDiagnosticDescriptor,
            Location.None,
            aliases.Count));
     
        var source = GenerateFontAliasEnum(aliases);
        spc.AddSource("FontAliases.g.cs", source);
    }
     
     
    private static List<string> ExtractFontAliases(Compilation compilation, IReadOnlyList<InvocationExpressionSyntax> invocations)
    {
        var list = new List<string>();
        foreach (var invocation in invocations)
        {
            var args = invocation.ArgumentList.Arguments;
            if (args.Count < 2) continue;
     
            var semanticModel = compilation.GetSemanticModel(invocation.SyntaxTree);
            var aliasExpr = args[1].Expression;
            var constValue = semanticModel.GetConstantValue(aliasExpr);
            if (constValue.HasValue && constValue.Value is string s && !string.IsNullOrWhiteSpace(s))
            {
                list.Add(s);
            }
        }
        return list;
    }
    
    1. Check your class library configuration
    • Source generator must use .NET standard 2.0, other version won't work, see here

    Here is the example configuration for your Class Library:

    <Project Sdk="Microsoft.NET.Sdk">
     
        <PropertyGroup>
            <TargetFramework>netstandard2.0</TargetFramework>
            <LangVersion>latest</LangVersion>
            <IsRoslynComponent>true</IsRoslynComponent>
            <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
            <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
            <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
        </PropertyGroup>
     
        <ItemGroup>
            <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" PrivateAssets="all" />
            <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
        </ItemGroup>
    </Project>
    
    1. Ensure ReferenceOutputAssembly set to false:

    Since the source generator is in compiler runtime, you don't want to reference the library at the runtime, here is the correct one:

    <ItemGroup>
      <ProjectReference Include="..\FontAliasGenerator\FontAliasGenerator.csproj"
                        OutputItemType="Analyzer"
                        ReferenceOutputAssembly="false" />
    </ItemGroup>
     
    

    After update, make sure you:

    • Do a Clean and Rebuild project/solution.
    • After finish building, Look inside your MAUI project and check for Dependencies>AnyPlatform>Analyzer>WndowsPhoneTile, your FontAliases.g.cs should appear.

    If you're looking for a well-documented example, the MVVM Toolkit provides source generators you can check out here.

    I hope this helps! Let me know if you stuck any!

    References:

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.


    Comments have been turned off. Learn more

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.