using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace PCL.Core.SourceGenerators; [Generator(LanguageNames.CSharp)] public sealed class ConfigGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { // 收集所有可能的属性与类 var propertyCandidates = context.SyntaxProvider.CreateSyntaxProvider( static (s, _) => s is PropertyDeclarationSyntax { AttributeLists.Count: > 0 }, static (ctx, _) => _GetItemCandidate(ctx) ).Where(static m => m is not null); var groupCandidates = context.SyntaxProvider.CreateSyntaxProvider( static (s, _) => s is ClassDeclarationSyntax { AttributeLists.Count: > 0 }, static (ctx, _) => _GetGroupCandidate(ctx) ).Where(static m => m is not null); var configClassCandidates = context.SyntaxProvider.CreateSyntaxProvider( static (s, _) => s is ClassDeclarationSyntax, static (ctx, _) => _GetConfigClass(ctx) ).Where(static m => m is not null); // 新增:收集 [RegisterConfigEvent] 的 public static 属性 var eventCandidates = context.SyntaxProvider.CreateSyntaxProvider( static (s, _) => s is PropertyDeclarationSyntax { AttributeLists.Count: > 0 }, static (ctx, _) => _GetRegisterConfigEventCandidate(ctx) ).Where(static m => m is not null); var collected = propertyCandidates.Collect() .Combine(groupCandidates.Collect()) .Combine(configClassCandidates.Collect()); context.RegisterSourceOutput(collected, static (spc, triple) => { var items = triple.Left.Left; var groups = triple.Left.Right; var configs = triple.Right; if (configs.Length == 0) return; // 建立快速查找 var itemList = items.Cast().ToImmutableArray(); var groupList = groups.Cast().ToImmutableArray(); var configList = configs.Cast().ToImmutableArray(); foreach (var config in configList) { try { var tree = _BuildConfigTree(config, itemList, groupList); // 跳过无用生成(无顶层项和顶层组声明的类型) if (tree.TopItems.Count == 0 && tree.TopGroups.Count == 0) continue; var source = _GenerateAdditionalSource(tree); var hint = _MakeHintName(config); spc.AddSource(hint, source); } catch { // 可添加诊断,此处直接忽略以免打断编译 } } }); var serviceInputs = propertyCandidates.Collect() .Combine(groupCandidates.Collect()) .Combine(eventCandidates.Collect()); context.RegisterSourceOutput(serviceInputs, static (spc, tuple) => { var items = tuple.Left.Left.Cast().OrderBy(i => i.DeclOrder).ToList(); var groups = tuple.Left.Right.Cast().ToList(); var events = tuple.Right.Cast().OrderBy(e => e.DeclOrder).ToList(); // 两者都为空则不生成 if (items.Count == 0 && events.Count == 0) return; var groupLookup = new Dictionary(SymbolEqualityComparer.Default); foreach (var group in groups) { groupLookup[group.GroupType] = group; } var src = _GenerateServiceInitSource(items, events, groupLookup); spc.AddSource("ConfigService.g.cs", src); }); } private static object? _GetItemCandidate(GeneratorSyntaxContext ctx) { var propSyntax = (PropertyDeclarationSyntax)ctx.Node; var symbol = ctx.SemanticModel.GetDeclaredSymbol(propSyntax); if (symbol is null) return null; var compilation = ctx.SemanticModel.Compilation; var attrDefItem = compilation.GetTypeByMetadataName("PCL.Core.App.Configuration.ConfigItemAttribute`1"); var attrDefAny = compilation.GetTypeByMetadataName("PCL.Core.App.Configuration.AnyConfigItemAttribute`1"); if (attrDefItem is null && attrDefAny is null) return null; AttributeData? picked = null; var isAny = false; foreach (var a in symbol.GetAttributes()) { var ac = a.AttributeClass; if (ac is null) continue; if (attrDefItem is not null && SymbolEqualityComparer.Default.Equals(ac.ConstructedFrom, attrDefItem)) { picked = a; isAny = false; break; } if (attrDefAny is not null && SymbolEqualityComparer.Default.Equals(ac.ConstructedFrom, attrDefAny)) { picked = a; isAny = true; break; } } if (picked is null) return null; if (picked.ConstructorArguments.Length < 1) return null; var key = picked.ConstructorArguments[0].Value as string; if (string.IsNullOrEmpty(key)) return null; // 默认值与来源解析 var defaultCode = "default"; string? sourceCode = null; var attrSyntax = (AttributeSyntax?)picked.ApplicationSyntaxReference?.GetSyntax(); if (attrSyntax is not null) { var args = attrSyntax.ArgumentList?.Arguments; if (isAny) { // AnyConfigItem:没有“默认值”参数: 替换为无参构造函数 var tQualified = symbol.Type.GetFullyQualifiedName(); defaultCode = "() => new " + tQualified + "()"; // 来源参数若存在,是第 2 个实参 if (args is { Count: >= 2 }) { sourceCode = _RenderSourceCode(ctx.SemanticModel, args.Value[1].Expression); } } else { // ConfigItem:参数2为默认值,参数3为来源(可省略) if (args is { Count: >= 2 }) { defaultCode = ctx.SemanticModel.RenderDefaultValueCode(args.Value[1].Expression); } if (args is { Count: >= 3 }) { sourceCode = _RenderSourceCode(ctx.SemanticModel, args.Value[2].Expression); } } } return new ItemModel { Property = symbol, Key = key!, Type = symbol.Type, IsStatic = symbol.IsStatic, DeclOrder = symbol.GetDeclarationOrder(), DefaultValueCode = defaultCode, SourceCode = sourceCode }; } private static object? _GetGroupCandidate(GeneratorSyntaxContext ctx) { var classSyntax = (ClassDeclarationSyntax)ctx.Node; var symbol = ModelExtensions.GetDeclaredSymbol(ctx.SemanticModel, classSyntax) as INamedTypeSymbol; if (symbol is null) return null; var compilation = ctx.SemanticModel.Compilation; var attrDef = compilation.GetTypeByMetadataName("PCL.Core.App.Configuration.ConfigGroupAttribute"); if (attrDef is null) return null; var attr = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass is not null && SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrDef)); if (attr is null) return null; if (attr.ConstructorArguments.Length < 1) return null; var name = attr.ConstructorArguments[0].Value as string; if (string.IsNullOrEmpty(name)) return null; var hasDeclaredSource = false; string? declaredSourceCode = null; var attrSyntax = (AttributeSyntax?)attr.ApplicationSyntaxReference?.GetSyntax(); if (attrSyntax?.ArgumentList?.Arguments is { Count: >= 2 } arguments) { hasDeclaredSource = true; declaredSourceCode = _RenderSourceCode(ctx.SemanticModel, arguments[1].Expression); } return new GroupModel { GroupType = symbol, GroupName = name!, DeclOrder = symbol.GetDeclarationOrder(), HasDeclaredSource = hasDeclaredSource, DeclaredSourceCode = declaredSourceCode }; } private static object? _GetRegisterConfigEventCandidate(GeneratorSyntaxContext ctx) { var propSyntax = (PropertyDeclarationSyntax)ctx.Node; if (ctx.SemanticModel.GetDeclaredSymbol(propSyntax) is not { } symbol) return null; // 仅 public static if (symbol.DeclaredAccessibility != Accessibility.Public || !symbol.IsStatic) return null; // 精确匹配 [RegisterConfigEvent] var compilation = ctx.SemanticModel.Compilation; var attrDef = compilation.GetTypeByMetadataName("PCL.Core.App.Configuration.RegisterConfigEventAttribute"); if (attrDef is null) return null; var hasAttr = symbol.GetAttributes().Any(a => a.AttributeClass is not null && SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrDef)); if (!hasAttr) return null; return new EventRegisterModel { Property = symbol, DeclOrder = symbol.GetDeclarationOrder() }; } private static object? _GetConfigClass(GeneratorSyntaxContext ctx) { var classSyntax = (ClassDeclarationSyntax)ctx.Node; if (ModelExtensions.GetDeclaredSymbol(ctx.SemanticModel, classSyntax) is not INamedTypeSymbol symbol) return null; // 限定为 partial if (!symbol.IsPartial()) return null; // 绕过 [ConfigGroup] var compilation = ctx.SemanticModel.Compilation; var attrDef = compilation.GetTypeByMetadataName("PCL.Core.App.Configuration.ConfigGroupAttribute"); if (attrDef is not null) { var hasAttr = symbol.GetAttributes().Any(a => a.AttributeClass is not null && SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrDef)); if (hasAttr) return null; } return new ConfigModel { ConfigType = symbol, DeclOrder = symbol.GetDeclarationOrder() }; } private static ConfigTree _BuildConfigTree(ConfigModel config, ImmutableArray items, ImmutableArray groups) { var configType = config.ConfigType; // 过滤归属于该 Config 的顶层项与组 var topItems = items.Where(i => SymbolEqualityComparer.Default.Equals(i.Property.ContainingType, configType)) .OrderBy(i => i.DeclOrder) .ToList(); var allGroupsForConfig = groups .Where(g => g.GroupType.IsNestedWithin(configType)) .OrderBy(g => g.DeclOrder) .ToList(); // 构建组索引 var groupMap = allGroupsForConfig.ToDictionary(g => g.GroupType, g => new GroupNode(g), SymbolEqualityComparer.Default); GroupModel? GroupLookup(INamedTypeSymbol type) => groupMap.TryGetValue(type, out var node) ? node.Model : null; string ResolveItemSource(ItemModel item) => _ResolveItemSourceCode(item, GroupLookup); // 组装层级 foreach (var node in groupMap.Values) { var parentType = node.Model.GroupType.ContainingType; if (parentType is not null && !SymbolEqualityComparer.Default.Equals(parentType, configType)) { if (groupMap.TryGetValue(parentType, out var parentNode)) { parentNode.Children.Add(node); } } } // 顶层组 var topGroups = groupMap.Values .Where(n => SymbolEqualityComparer.Default.Equals(n.Model.GroupType.ContainingType, configType)) .OrderBy(n => n.Model.DeclOrder) .ToList(); // 将 Item 分配到各自的组 foreach (var item in items.Except(topItems)) { var container = item.Property.ContainingType; if (container is null) continue; if (groupMap.TryGetValue(container, out var groupNode)) { groupNode.Items.Add(item); } } return new ConfigTree { Namespace = configType.ContainingNamespace?.ToDisplayString() ?? "", ConfigType = configType, TopItems = topItems, TopGroups = topGroups, ResolveSourceCode = ResolveItemSource }; } private static string _MakeHintName(ConfigModel config) { var ns = config.ConfigType.ContainingNamespace?.ToDisplayString() ?? "Global"; return $"{ns}.{config.ConfigType.Name}.g.cs"; } private static string _GenerateAdditionalSource(ConfigTree tree) { var sb = new StringBuilder(4096); var ns = string.IsNullOrEmpty(tree.Namespace) ? null : tree.Namespace; var configType = tree.ConfigType; var configName = configType.Name; var resolveSource = tree.ResolveSourceCode; sb.AppendLine("// "); sb.AppendLine("// 此文件由 Source Generator 自动生成,请勿手动修改"); sb.AppendLine(); sb.AppendLine("using System.Collections.Generic;"); sb.AppendLine("using System.Linq;"); sb.AppendLine("using PCL.Core.App.Configuration;"); sb.AppendLine(); sb.AppendLine("#nullable enable"); sb.AppendLine(); if (!string.IsNullOrEmpty(ns)) { sb.Append("namespace ").Append(ns).AppendLine(";"); sb.AppendLine(); } sb.Append("partial class ").Append(configName).AppendLine(); sb.AppendLine("{"); // === Config Items === sb.AppendLine(" // === Config Items ==="); sb.AppendLine(); if (tree.TopItems.Count == 0) { sb.AppendLine(); } else { foreach (var item in tree.TopItems) { _EmitItem(sb, item, indent: 1, isTopLevel: true, resolveSource); sb.AppendLine(); } } // === Config Groups === sb.AppendLine(" // === Config Groups ==="); if (tree.TopGroups.Count > 0) { foreach (var grp in tree.TopGroups) { _EmitGroupInto(sb, grp, indent: 1, isTopLevel: true, resolveSource); } } sb.AppendLine("}"); return sb.ToString(); } private static string _GenerateServiceInitSource( IReadOnlyList items, IReadOnlyList events, IReadOnlyDictionary groupLookup) { GroupModel? Lookup(INamedTypeSymbol type) => groupLookup.TryGetValue(type, out var model) ? model : null; string ResolveSource(ItemModel item) => _ResolveItemSourceCode(item, Lookup); var sb = new StringBuilder(1024); sb.AppendLine("// "); sb.AppendLine("// 此文件由 Source Generator 自动生成,请勿手动修改"); sb.AppendLine(); sb.AppendLine("namespace PCL.Core.App.Configuration;"); sb.AppendLine(); sb.AppendLine("public sealed partial class ConfigService"); sb.AppendLine("{"); // 配置项初始化 sb.AppendLine(" private static void _InitializeConfigItems()"); sb.AppendLine(" {"); sb.AppendLine(" (string, ConfigItem)[] items = ["); HashSet keysAdded = []; for (var i = 0; i < items.Count; i++) { var it = items[i]; if (!keysAdded.Add(it.Key)) continue; var keyLiteral = it.Key.ToLiteral(); var typeName = it.Type.GetFullyQualifiedName().CorrectConfigTypeName(out _); var sourceCode = ResolveSource(it); sb.Append(" (") .Append(keyLiteral) .Append(", new ConfigItem<").Append(typeName).Append(">(") .Append(keyLiteral).Append(", ") .Append(it.DefaultValueCode).Append(", ").Append(sourceCode) .Append("))"); if (i != items.Count - 1) sb.Append(','); sb.AppendLine(); } sb.AppendLine(" ];"); sb.AppendLine(" foreach (var (key, value) in items) {"); sb.AppendLine(" _KeySet.Add(key);"); sb.AppendLine(" _Items[key] = value;"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(); // 事件观察器初始化 sb.AppendLine(" private static void _InitializeObservers()"); sb.AppendLine(" {"); sb.AppendLine(" ConfigEventRegistry[] registers = ["); for (var i = 0; i < events.Count; i++) { var ev = events[i]; sb.Append(" ") .Append(ev.Property.GetQualifiedPropertyAccess()); if (i != events.Count - 1) sb.Append(','); sb.AppendLine(); } sb.AppendLine(" ];"); sb.AppendLine(" foreach (var r in registers) foreach (var scope in r.Scopes) {"); sb.AppendLine(" RegisterObserver(scope, r.ToObserver());"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } private static string _ResolveItemSourceCode(ItemModel item, Func groupLookup) { if (item.SourceCode is { } explicitSource) { return explicitSource; } var container = item.Property.ContainingType; while (container is not null) { var group = groupLookup(container); if (group is { HasDeclaredSource: true, DeclaredSourceCode: { } declared }) { return declared; } container = container.ContainingType; } return "ConfigSource.Shared"; } private static Action? _EmitItem( StringBuilder sb, ItemModel item, int indent, bool isTopLevel, Func resolveSource) { Action? accessorInitializer = null; var typeName = item.Type.GetFullyQualifiedName().CorrectConfigTypeName(out var fullTypeName); var propName = item.Property.Name; var configItemName = propName + "Config"; var staticKeyword = item.IsStatic || isTopLevel ? "static " : string.Empty; var indentStr = new string(' ', indent * 4); var sourceCode = resolveSource(item); // 注释 sb.Append(indentStr).Append("// Item: ").Append(propName).Append(" [").Append(item.Key).AppendLine("]"); // 访问器 sb.Append(indentStr) .Append("public ").Append(staticKeyword).Append("partial ") .Append(fullTypeName ?? typeName).Append(' ').Append(propName); if (fullTypeName is not null) { var accessorName = "ACCESSOR_" + propName; sb.Append(" => ").Append(accessorName).AppendLine(";"); // 初始化带参数访问器 sb.Append(indentStr) .Append("private ").Append(staticKeyword).Append("readonly ") .Append(fullTypeName).Append(' '); // ReSharper disable once VariableHidesOuterVariable void AccessorInitializer(StringBuilder sb) { sb.Append(accessorName) .Append(" = new((arg) => ") .Append(configItemName) .Append(".GetValue(arg), (arg, value) => ") .Append(configItemName) .AppendLine(".SetValue(value, arg));"); } if (item.IsStatic) AccessorInitializer(sb); else { accessorInitializer = AccessorInitializer; sb.Append(accessorName).AppendLine(";"); } } else { sb.Append(" { get => ") .Append(configItemName) .Append(".GetValue(); set => ") .Append(configItemName) .AppendLine(".SetValue(value); }"); } // 配置项 sb.Append(indentStr) .Append("public ").Append(staticKeyword) .Append("ConfigItem<").Append(typeName).Append("> ") .Append(configItemName).Append(" { get => field ??= ConfigService.GetConfigItem<") .Append(typeName) .Append(">(") .Append(item.Key.ToLiteral()) .AppendLine("); } = null!;"); return accessorInitializer; } private static void _EmitGroupInto( StringBuilder sb, GroupNode node, int indent, bool isTopLevel, Func resolveSource) { var indentStr = new string(' ', indent * 4); var type = node.Model.GroupType; var typeName = type.Name; var staticKeyword = isTopLevel ? "static " : string.Empty; // 组实例字段(在其父作用域中) sb.AppendLine(); sb.Append(indentStr).Append("// Group: ").AppendLine(node.Model.GroupName); sb.Append(indentStr).Append("/// "); sb.Append(indentStr) .Append("public ") .Append(staticKeyword) .Append("readonly ") .Append(typeName) .Append(' ') .Append(node.Model.GroupName) .Append(" = ") .Append(typeName) .AppendLine(".SINGLE_INSTANCE;"); // 嵌套类型定义 sb.Append(indentStr) .Append("public sealed partial class ") .Append(typeName) .AppendLine(" : IConfigScope"); sb.Append(indentStr).AppendLine("{"); // === Config Items === sb.Append(indentStr).AppendLine(" // === Config Items ==="); sb.Append(indentStr).AppendLine(); List> accessorInitializers = []; foreach (var item in node.Items.OrderBy(i => i.DeclOrder)) { var result = _EmitItem(sb, item, indent + 1, isTopLevel: false, resolveSource); if (result is not null) accessorInitializers.Add(result); sb.AppendLine(); } // === Config Groups === sb.Append(indentStr).AppendLine(" // === Config Groups ==="); foreach (var child in node.Children.OrderBy(c => c.Model.DeclOrder)) { _EmitGroupInto(sb, child, indent + 1, isTopLevel: false, resolveSource); } // === Group Scope Implementation === sb.AppendLine(); sb.Append(indentStr).AppendLine(" // === Group Scope Implementation ==="); sb.Append(indentStr).AppendLine(); sb.Append(indentStr).AppendLine(" public static readonly " + typeName + " SINGLE_INSTANCE = new();"); sb.Append(indentStr).AppendLine(" private readonly IConfigScope[] _InnerScopes;"); sb.Append(indentStr).AppendLine(" private " + typeName + "()"); sb.Append(indentStr).AppendLine(" {"); sb.Append(indentStr).AppendLine(" _InnerScopes = ["); // InnerScopes: 先项再子组 var first = true; foreach (var item in node.Items.SkipWhile(i => i.IsStatic).OrderBy(i => i.DeclOrder)) { if (!first) sb.AppendLine(","); sb.Append(indentStr).Append(" ").Append(item.Property.Name).Append("Config"); first = false; } foreach (var child in node.Children.OrderBy(c => c.Model.DeclOrder)) { if (!first) sb.AppendLine(","); sb.Append(indentStr).Append(" ").Append(child.Model.GroupName); first = false; } if (!first) sb.AppendLine(); sb.Append(indentStr).AppendLine(" ];"); foreach (var initializer in accessorInitializers) { sb.Append(indentStr).Append(" "); initializer.Invoke(sb); } sb.Append(indentStr).AppendLine(" }"); // CheckScope sb.Append(indentStr).AppendLine(" public IEnumerable CheckScope(IReadOnlySet keys)"); sb.Append(indentStr).AppendLine(" {"); sb.Append(indentStr).AppendLine(" IEnumerable result = [];"); sb.Append(indentStr).AppendLine(" foreach (var scope in _InnerScopes)"); sb.Append(indentStr).AppendLine(" {"); sb.Append(indentStr).AppendLine(" var next = scope.CheckScope(keys);"); sb.Append(indentStr).AppendLine(" if (next.Any()) result = result.Concat(next);"); sb.Append(indentStr).AppendLine(" }"); sb.Append(indentStr).AppendLine(" return result;"); sb.Append(indentStr).AppendLine(" }"); // Reset sb.Append(indentStr).AppendLine(" public bool Reset(object? argument = null)"); sb.Append(indentStr).AppendLine(" {"); sb.Append(indentStr).AppendLine(" var result = true;"); sb.Append(indentStr).AppendLine(" foreach (var scope in _InnerScopes)"); sb.Append(indentStr).AppendLine(" {"); sb.Append(indentStr).AppendLine(" var next = scope.Reset(argument);"); sb.Append(indentStr).AppendLine(" if (!next) result = false;"); sb.Append(indentStr).AppendLine(" }"); sb.Append(indentStr).AppendLine(" return result;"); sb.Append(indentStr).AppendLine(" }"); // IsDefault sb.Append(indentStr).AppendLine(" public bool IsDefault(object? argument = null)"); sb.Append(indentStr).AppendLine(" {"); sb.Append(indentStr).AppendLine(" var result = true;"); sb.Append(indentStr).AppendLine(" foreach (var scope in _InnerScopes)"); sb.Append(indentStr).AppendLine(" {"); sb.Append(indentStr).AppendLine(" var next = scope.IsDefault(argument);"); sb.Append(indentStr).AppendLine(" if (!next) result = false;"); sb.Append(indentStr).AppendLine(" }"); sb.Append(indentStr).AppendLine(" return result;"); sb.Append(indentStr).AppendLine(" }"); sb.Append(indentStr).AppendLine("}"); } public static string _RenderSourceCode(SemanticModel sm, ExpressionSyntax expr) { var sym = sm.GetSymbolInfo(expr).Symbol; if (sym is IFieldSymbol fs && fs.ContainingType?.ToDisplayString() == "PCL.Core.App.Configuration.ConfigSource") { return "ConfigSource." + fs.Name; } return expr.ToString(); } // ===== Models/Trees ===== private sealed class ItemModel { public IPropertySymbol Property { get; set; } = null!; public string Key { get; set; } = ""; public ITypeSymbol Type { get; set; } = null!; public bool IsStatic { get; set; } public int DeclOrder { get; set; } public string DefaultValueCode { get; set; } = ""; public string? SourceCode { get; set; } } private sealed class GroupModel { public INamedTypeSymbol GroupType { get; set; } = null!; public string GroupName { get; set; } = ""; public int DeclOrder { get; set; } public bool HasDeclaredSource { get; set; } public string? DeclaredSourceCode { get; set; } } private sealed class GroupNode(GroupModel model) { public GroupModel Model { get; } = model; public List Children { get; } = []; public List Items { get; } = []; } private sealed class ConfigModel { public INamedTypeSymbol ConfigType { get; set; } = null!; // ReSharper disable once UnusedAutoPropertyAccessor.Local public int DeclOrder { get; set; } } private sealed class ConfigTree { public string Namespace { get; set; } = ""; public INamedTypeSymbol ConfigType { get; set; } = null!; public List TopItems { get; set; } = []; public List TopGroups { get; set; } = []; public Func ResolveSourceCode { get; set; } = static _ => "ConfigSource.Shared"; } private sealed class EventRegisterModel { public IPropertySymbol Property { get; set; } = null!; public int DeclOrder { get; set; } } }