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<T></c> çš„é…置类,为é…置模型类型生æˆé™æ€æˆå‘˜è¯»å†™å§”托注册,é…置绑定时优先使用,é¿å…å射与表达å¼ç¼–译</summary>
/// <remarks>
/// æ ‡å‡†ç”¨æ³• <c>class MyConfig : Config<MyConfig></c>:沿继承链上溯查找 <c>NewLife.Configuration.Config<T></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
}
|