解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
5.37 KiB
X
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace NewLife.SourceGenerator;

/// <summary>配置模型生成器。扫描继承 <c>Config&lt;T&gt;</c> 的配置类,为配置模型类型生成静态成员读写委托注册,配置绑定时优先使用,避免反射与表达式编译</summary>
/// <remarks>
/// 标准用法 <c>class MyConfig : Config&lt;MyConfig&gt;</c>:沿继承链上溯查找 <c>NewLife.Configuration.Config&lt;T&gt;</c>,
/// 为泛型参数类型与声明类本身生成成员访问。未命中的类型(含多级继承的上层派生类)保持原反射路径,行为不变。
/// </remarks>
[Generator(LanguageNames.CSharp)]
public sealed class ConfigModelGenerator : IIncrementalGenerator
{
    #region 生成入口
    /// <summary>初始化增量生成管线</summary>
    /// <param name="context">生成上下文</param>
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var lines = context.SyntaxProvider
            .CreateSyntaxProvider(IsCandidate, Extract)
            .SelectMany(static (items, token) => items)
            .Where(static x => x != null);

        var combined = context.CompilationProvider.Combine(lines.Collect());

        context.RegisterSourceOutput(combined, static (spc, pair) => Emit(spc, pair.Left, pair.Right));
    }
    #endregion

    #region 提取
    // 语法谓词:带基类列表的类声明(多级继承时只需直接基类名即可上溯,故放宽到全部有基类的类)
    private static Boolean IsCandidate(SyntaxNode node, CancellationToken token)
        => node is ClassDeclarationSyntax { BaseList: not null };

    // 语义提取:沿基类链查找 Config<T>,为泛型参数与声明类生成成员访问行
    private static ImmutableArray<String?> Extract(GeneratorSyntaxContext context, CancellationToken token)
    {
        if (context.SemanticModel.GetDeclaredSymbol((ClassDeclarationSyntax)context.Node, token) is not INamedTypeSymbol symbol)
            return ImmutableArray<String?>.Empty;

        INamedTypeSymbol? configType = null;
        for (var current = symbol.BaseType; current != null; current = current.BaseType)
        {
            if (IsConfig(current))
            {
                configType = current;
                break;
            }
        }
        if (configType == null || configType.TypeArguments.Length == 0) return ImmutableArray<String?>.Empty;

        var builder = ImmutableArray.CreateBuilder<String?>();
        var compilation = context.SemanticModel.Compilation;

        // 配置模型语义上的目标类型是 Config 的泛型参数;声明类自身也可能是实例类型(多级继承场景)
        if (configType.TypeArguments[0] is INamedTypeSymbol model && !SymbolEqualityComparer.Default.Equals(model, symbol))
            MemberLineBuilder.AddMembers(builder, model, compilation);

        MemberLineBuilder.AddMembers(builder, symbol, compilation);

        return builder.ToImmutable();
    }

    // 判定是否为 NewLife.Configuration.Config<T>
    private static Boolean IsConfig(INamedTypeSymbol type)
    {
        var definition = type.OriginalDefinition;
        return definition.MetadataName == "Config`1" && definition.ContainingNamespace.ToDisplayString() == "NewLife.Configuration";
    }
    #endregion

    #region 产出
    // 输出生成源码。仅 .NET 5+(含模块初始化器)且在可用语言版本下生成
    private static void Emit(SourceProductionContext context, Compilation compilation, ImmutableArray<String?> items)
    {
        if (items.IsDefaultOrEmpty) return;

        var set = new SortedSet<String>(StringComparer.Ordinal);
        foreach (var item in items)
        {
            if (!String.IsNullOrEmpty(item)) set.Add(item!);
        }
        if (set.Count == 0) return;

        var tree = compilation.SyntaxTrees.FirstOrDefault();
        if (tree?.Options is not CSharpParseOptions options) return;
        if (!options.PreprocessorSymbolNames.Contains("NET5_0_OR_GREATER")) return;

        var builder = new StringBuilder();
        builder.AppendLine("// <auto-generated/>");
        if ((Int32)options.LanguageVersion >= (Int32)LanguageVersion.CSharp8) builder.AppendLine("#nullable enable");
        builder.AppendLine();
        builder.AppendLine("namespace NewLife.SourceGenerator.Generated");
        builder.AppendLine("{");
        builder.AppendLine("    /// <summary>源生成的配置成员访问注册。NewLife.Core 源生成器自动生成,请勿修改</summary>");
        builder.AppendLine("    internal static class ConfigMemberRegistrar");
        builder.AppendLine("    {");
        builder.AppendLine("        [global::System.Runtime.CompilerServices.ModuleInitializer]");
        builder.AppendLine("        internal static void Initialize()");
        builder.AppendLine("        {");
        foreach (var line in set)
        {
            builder.Append("            ").AppendLine(line);
        }
        builder.AppendLine("        }");
        builder.AppendLine("    }");
        builder.AppendLine("}");

        context.AddSource("ConfigMemberRegistrar.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
    }
    #endregion
}