MinimalHelpers icon indicating copy to clipboard operation
MinimalHelpers copied to clipboard

reference Microsoft.CodeAnalysis.CSharp version 4.9.2 is completely out of mind

Open NotAsea opened this issue 9 months ago • 4 comments

Sorry if title feel harsh but how can i use source gen version if it reference Microsoft.CodeAnalysis.CSharp 4.9.2 while every tooling in newest Asp.Net core reference 4.5, is this lib suppose to use with preview version of .NET or we suppose to force reference 4.9.2 ??

NotAsea avatar May 03 '24 02:05 NotAsea

i forked source gen version and build it myself using standard Microsoft.CodeAnalysis.CSharp 4.5.0 and it work fine, maybe there's some reason i dont know

NotAsea avatar May 03 '24 02:05 NotAsea

this loc

if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp11))
{
      return default;
}

i dont see any point to force use 4.9.2 as 4.5.0 already contain definition for CSharp 11 and compatible with good amount of library outhere that target .Net 7+

NotAsea avatar May 03 '24 03:05 NotAsea

we can event add branch to detect .Net 6 LanguageVersion.CSharp10 and .Net 7+ LanguageVersion.CSharp11 , with .Net 6 we generate register as

public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder endpoints)
    {
       new global::TestRepo.Api.Routes.AccountRoute().MapEndpoints(endpoints);
        return endpoints;
    }

and for .Net 7+

public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder endpoints)
    {
        global::TestRepo.Api.Routes.AccountRoute.MapEndpoints(endpoints);
        return endpoints;
    }

NotAsea avatar May 03 '24 04:05 NotAsea

i propose my very simple implementation that can use in Net 6 +

// EndpointHandlerGenerator.cs
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace MinimalHelpers.Routing.Analyzers;

[Generator]
public sealed class EndpointHandlerGenerator : IIncrementalGenerator
{
    private static bool isLang11OrUp;

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var provider = context
            .SyntaxProvider.CreateSyntaxProvider(
                static (node, _) =>
                    node is ClassDeclarationSyntax classDeclaration
                    && classDeclaration.HasOrPotentiallyHasBaseTypes(),
                static (context, token) =>
                {
                    var compilation = context.SemanticModel.Compilation;
                    if (!compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp10))
                    {
                        return default;
                    }

                    token.ThrowIfCancellationRequested();
                    isLang11OrUp = compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp11);
                    return (ClassDeclarationSyntax)context.Node;
                }
            )
            .Where(static c => c is not null);

        var compilation = context.CompilationProvider.Combine(provider.Collect());
        context.RegisterSourceOutput(compilation, Execute!);
    }

    private static void Execute(
        SourceProductionContext context,
        (Compilation Compilation, ImmutableArray<ClassDeclarationSyntax> Classes) tuple
    )
    {
        //#if DEBUG
        //        if (!Debugger.IsAttached)
        //        {
        //            Debugger.Launch();
        //        }
        //#endif

        var (_, classes) = tuple;

        var @interface = GetIEndpointRouteHandlerBuilderInterface();
        context.AddSource("IEndpointRouteHandlerBuilder.g.cs", @interface);

        const string prefixCode = """
            // <auto-generated />
            namespace Microsoft.AspNetCore.Routing;

            #nullable enable annotations
            #nullable disable warnings

            /// <summary>
            /// Provides extension methods for <see cref="IEndpointRouteBuilder" /> to add route handlers.
            /// </summary>
            public static class EndpointRouteBuilderExtensions
            {
                /// <summary>
                /// Automatically registers all the route endpoints defined in classes that implement the <see cref="IEndpointRouteHandlerBuilder "/> interface.
                /// </summary>
                /// <param name="endpoints">The <see cref="IEndpointRouteBuilder" /> to add routes to.</param>
                public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder endpoints)
                {
            """;

        const string suffixCode = """

                    return endpoints;
                }
            }
            """;

        var codeBuilder = new StringBuilder();
        codeBuilder.AppendLine(prefixCode);

        foreach (
            var @class in classes.Where(c =>
                c.BaseList?.Types.Any(t => t.Type.ToString() == "IEndpointRouteHandlerBuilder")
                    is true
            )
        )
        {
            var @namespace = GetNamespace(@class);
            var fullClassName = $"{@namespace}.{@class.Identifier.Text}".TrimStart('.');

            codeBuilder.AppendLine(
                $"              {(!isLang11OrUp ? $"new global::{fullClassName}()" : $"global::{fullClassName}")}.MapEndpoints(endpoints);"
            );
        }

        codeBuilder.AppendLine(suffixCode);

        context.AddSource("EndpointRouteBuilderExtensions.g.cs", codeBuilder.ToString());
    }

    private static string GetIEndpointRouteHandlerBuilderInterface() =>
        $$"""
            // <auto-generated />
            namespace Microsoft.AspNetCore.Routing;

            #nullable enable annotations
            #nullable disable warnings

            /// <summary>
            /// Defines a contract for a class that holds one or more route handlers that must be registered by the application.
            /// </summary>
            public interface IEndpointRouteHandlerBuilder
            {
                /// <summary>
                /// Maps route endpoints to the corresponding handlers.
                /// </summary>
                {{(
                isLang11OrUp ? "static" : ""
            )}} abstract void MapEndpoints(IEndpointRouteBuilder endpoints);
            }
            """;

    // determine the namespace the class/enum/struct is declared in, if any
    // https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/
    private static string GetNamespace(BaseTypeDeclarationSyntax syntax)
    {
        var @namespace = string.Empty;
        var potentialNamespaceParent = syntax.Parent;

        while (
            potentialNamespaceParent
                is not null
                    and not NamespaceDeclarationSyntax
                    and not FileScopedNamespaceDeclarationSyntax
        )
        {
            potentialNamespaceParent = potentialNamespaceParent.Parent;
        }

        if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent)
        {
            @namespace = namespaceParent.Name.ToString();

            while (namespaceParent.Parent is NamespaceDeclarationSyntax parent)
            {
                @namespace = $"{namespaceParent.Name}.{@namespace}";
                namespaceParent = parent;
            }
        }

        return @namespace;
    }
}

NotAsea avatar May 03 '24 12:05 NotAsea

Could you please give me an example of an ASP.NET Core library that references Microsoft.CodeAnalysis.CSharp v4.5?

marcominerva avatar Jun 25 '24 08:06 marcominerva

Closing as i choose another tool

NotAsea avatar Jun 25 '24 08:06 NotAsea