feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,746 @@
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<ItemModel>().ToImmutableArray();
var groupList = groups.Cast<GroupModel>().ToImmutableArray();
var configList = configs.Cast<ConfigModel>().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<ItemModel>().OrderBy(i => i.DeclOrder).ToList();
var groups = tuple.Left.Right.Cast<GroupModel>().ToList();
var events = tuple.Right.Cast<EventRegisterModel>().OrderBy(e => e.DeclOrder).ToList();
// 两者都为空则不生成
if (items.Count == 0 && events.Count == 0) return;
var groupLookup = new Dictionary<INamedTypeSymbol, GroupModel>(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<ItemModel> items,
ImmutableArray<GroupModel> 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("// <auto-generated />");
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<ItemModel> items,
IReadOnlyList<EventRegisterModel> events,
IReadOnlyDictionary<INamedTypeSymbol, GroupModel> 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("// <auto-generated />");
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<string> 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<INamedTypeSymbol, GroupModel?> 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<StringBuilder>? _EmitItem(
StringBuilder sb,
ItemModel item,
int indent,
bool isTopLevel,
Func<ItemModel, string> resolveSource)
{
Action<StringBuilder>? 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<ItemModel, string> 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("/// <inheritdoc cref=\"").Append(typeName).AppendLine("\" />");
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<Action<StringBuilder>> 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<string> CheckScope(IReadOnlySet<string> keys)");
sb.Append(indentStr).AppendLine(" {");
sb.Append(indentStr).AppendLine(" IEnumerable<string> 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<GroupNode> Children { get; } = [];
public List<ItemModel> 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<ItemModel> TopItems { get; set; } = [];
public List<GroupNode> TopGroups { get; set; } = [];
public Func<ItemModel, string> ResolveSourceCode { get; set; } = static _ => "ConfigSource.Shared";
}
private sealed class EventRegisterModel
{
public IPropertySymbol Property { get; set; } = null!;
public int DeclOrder { get; set; }
}
}
@@ -0,0 +1,350 @@
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;
public readonly record struct CollectorInfo(
INamedTypeSymbol CollectorAttrSymbol,
ITypeSymbol DependencyType,
string Identifier,
AttributeTargets Targets
);
public readonly record struct DependencyMatchResult(
ISymbol Target,
AttributeTargets TargetType,
AttributeData CollectorAttr,
CollectorInfo Info
);
public readonly record struct InjectionPointInfo(
IMethodSymbol Target,
string Identifier
);
public readonly record struct InjectionPointMatchResult(
InjectionPointInfo Info,
ImmutableArray<DependencyMatchResult> Dependencies
);
[Generator(LanguageNames.CSharp)]
public sealed class DependencyCollectorGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
const string collectorMarkupAttr = SharedConstants.DependencyCollectorAttribute;
const string collectorMarkupAttrFull = $"{collectorMarkupAttr}`1";
const string injectionPointAttr = SharedConstants.DependencyInjectionPointAttribute;
// 收集被标记为 collector 的注解
var collectorAttrs = context.SyntaxProvider
.ForAttributeWithMetadataName(collectorMarkupAttrFull,
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) =>
{
if (ctx.TargetSymbol is not INamedTypeSymbol attr || !attr.IsAttribute()) return default;
var infos = new List<CollectorInfo>();
foreach (var attrData in ctx.Attributes)
{
var attrClass = attrData.AttributeClass;
if (attrClass is null || attrClass.GetSimplifiedTypeName() != collectorMarkupAttr) continue;
// 收集注解信息
var dependencyType = attrClass.TypeArguments.FirstOrDefault();
if (dependencyType is null) continue;
var ctorArgs = attrData.ConstructorArguments;
if (ctorArgs.Length < 2
|| ctorArgs[0].Value is not string identifier
|| ctorArgs[1].Value is not int targets)
continue;
infos.Add(new CollectorInfo(attr, dependencyType, identifier, (AttributeTargets)targets));
}
return new KeyValuePair<INamedTypeSymbol, List<CollectorInfo>>(attr, infos);
})
.Where(x => x.Key is not null)
.Collect()
// 此处合并到 dictionary 以优化后续查找性能
.Select(static (pairs, _) =>
{
var dict = new Dictionary<INamedTypeSymbol, List<CollectorInfo>>(SymbolEqualityComparer.Default);
foreach (var pair in pairs)
{
if (dict.TryGetValue(pair.Key, out var list)) list.AddRange(pair.Value);
else dict[pair.Key] = pair.Value;
}
return dict.ToImmutableDictionary(SymbolEqualityComparer.Default);
});
// 收集所有带注解的 static member
var potentialTargets = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) =>
{
// 仅支持 class, property, method
if (node is not MemberDeclarationSyntax { AttributeLists.Count: > 0 } member) return false;
if (node is ClassDeclarationSyntax) return true;
if (node is PropertyDeclarationSyntax or MethodDeclarationSyntax
&& member.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword))) return true;
return false;
},
transform: static (ctx, _) => ctx);
// 筛选出被 collector 标记的 member
var matches = potentialTargets.Combine(collectorAttrs)
.SelectMany(static (pair, cancelToken) =>
{
var (ctx, validAttrs) = pair;
// 从 syntax node 获取对应语义 symbol
var symbol = ctx.SemanticModel.GetDeclaredSymbol(ctx.Node, cancelToken);
if (symbol is null) return [];
// 确定目标类型
AttributeTargets targetType = default;
if (symbol is INamedTypeSymbol) targetType = AttributeTargets.Class;
else if (symbol is IPropertySymbol) targetType = AttributeTargets.Property;
else if (symbol is IMethodSymbol) targetType = AttributeTargets.Method;
// 筛选目标所有符合条件的注解
var results = new List<DependencyMatchResult>();
foreach (var attrData in symbol.GetAttributes())
{
var attr = attrData.AttributeClass;
if (attr is null) continue;
if (!validAttrs.TryGetValue(attr, out var infos)) continue;
results.AddRange(
from info in infos
where info.Targets.HasFlag(targetType)
select new DependencyMatchResult(symbol, targetType, attrData, info)
);
}
return results;
})
.Collect();
// 收集被标记为注入点的方法
var injectionPoints = context.SyntaxProvider
.ForAttributeWithMetadataName(injectionPointAttr,
predicate: static (node, _) => node is MethodDeclarationSyntax,
transform: static (ctx, _) =>
{
var method = (IMethodSymbol)ctx.TargetSymbol;
var attr = ctx.Attributes.First(x => x.AttributeClass?.GetSimplifiedTypeName() == injectionPointAttr);
var attrArgs = attr.ConstructorArguments;
var identifier = attrArgs[0].Value?.ToString();
return identifier is null ? default : new InjectionPointInfo(method, identifier);
})
.Where(x => x != default);
// 将注入点与对应标记的依赖项关联
var injectionPointMatches = injectionPoints.Combine(matches)
.Select((item, _) =>
{
var point = item.Left;
var deps = item.Right
.Where(x => x.Info.Identifier == point.Identifier)
.ToImmutableArray();
return new InjectionPointMatchResult(point, deps);
})
.Collect();
// 生成注入实现
context.RegisterSourceOutput(injectionPointMatches, _GenerateDependencyInjectionMethods);
// 保留旧生成模式以供旧组件兼容
context.RegisterSourceOutput(matches, _GenerateDependencyGroup);
}
private static void _GenerateDependencyGroup(SourceProductionContext spc, ImmutableArray<DependencyMatchResult> matches)
{
var dependencyMap = new Dictionary<CollectorInfo, Dictionary<AttributeTargets, List<DependencyMatchResult>>>();
foreach (var dep in matches)
{
var info = dep.Info;
if (!dependencyMap.TryGetValue(info, out var map))
{
map = new Dictionary<AttributeTargets, List<DependencyMatchResult>>
{
[AttributeTargets.Class] = [],
[AttributeTargets.Method] = [],
[AttributeTargets.Property] = []
};
dependencyMap[info] = map;
}
map[dep.TargetType].Add(dep);
}
var sb = new StringBuilder(1024);
sb.AppendLine("// <auto-generated />");
sb.AppendLine("// 此文件由 Source Generator 自动生成,请勿手动修改");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("using System.Collections.Immutable;");
sb.AppendLine();
sb.AppendLine("namespace PCL.Core.App.IoC;");
sb.AppendLine();
sb.AppendLine("#nullable enable");
sb.AppendLine();
sb.AppendLine("public static partial class DependencyGroups");
sb.AppendLine("{");
sb.AppendLine(" private static readonly Dictionary<string, Dictionary<AttributeTargets, DependencyGroup>> _GroupMap = new()");
sb.AppendLine(" {");
foreach (var (info, map) in dependencyMap
.Select(x => (x.Key, x.Value)))
{
sb.Append(" [").Append(info.Identifier.ToLiteral()).AppendLine("] = new()");
sb.AppendLine(" {");
string? typeStr = null;
string? argTypeList = null;
foreach (var (target, deps) in map
.Where(x => x.Value.Count > 0)
.Select(x => (x.Key, x.Value)))
{
sb.Append(" [AttributeTargets.").Append(target).Append("] = new DependencyGroup<");
typeStr ??= info.DependencyType.GetFullyQualifiedName();
switch (target)
{
case AttributeTargets.Class:
sb.Append("Action<").Append(typeStr).Append(">");
break;
case AttributeTargets.Method:
sb.Append(typeStr);
break;
case AttributeTargets.Property:
sb.Append("PropertyAccessor<").Append(typeStr).Append(">");
break;
}
argTypeList ??= ((Func<string>)(() =>
{
var ctor = info.CollectorAttrSymbol.InstanceConstructors.FirstOrDefault();
if (ctor is null) return string.Empty;
var args = ctor.Parameters.Select(para => para.Type.GetFullyQualifiedName()).ToList();
var cnt = args.Count;
if (cnt == 0) return string.Empty;
if (cnt == 1) return args[0];
return "(" + string.Join(", ", args) + ")";
}))();
if (argTypeList != string.Empty) sb.Append(", ").Append(argTypeList);
sb.AppendLine("> { Items = [");
foreach (var dep in deps)
{
sb.Append(" (");
var depRef = dep.Target.GetQualifiedSymbolName();
switch (target)
{
case AttributeTargets.Class:
sb.Append("static () => new ")
.Append(depRef).Append("()");
break;
case AttributeTargets.Method:
sb.Append(depRef);
break;
case AttributeTargets.Property:
sb.Append("new(getter: ");
var prop = (IPropertySymbol)dep.Target;
if (prop.IsWriteOnly) sb.Append("null");
else sb.Append("static () => ").Append(depRef);
sb.Append(", setter: ");
if (prop.IsReadOnly) sb.Append("null");
else sb.Append("static value => ").Append(depRef).Append(" = value");
sb.Append(")");
break;
}
if (argTypeList != string.Empty)
{
sb.Append(", ");
var args = dep.CollectorAttr.ConstructorArguments.Select(arg => arg.ToCSharpString()).ToList();
if (args.Count == 1) sb.Append(args[0]);
else sb.Append("(").Append(string.Join(", ", args)).Append(")");
}
sb.AppendLine("),");
}
sb.AppendLine(" ] },");
}
sb.AppendLine(" },");
}
sb.AppendLine(" };");
sb.AppendLine("}");
spc.AddSource("DependencyGroups.g.cs", sb.ToString());
}
private static void _GenerateDependencyInjectionMethods(SourceProductionContext spc, ImmutableArray<InjectionPointMatchResult> matches)
{
foreach (var match in matches)
{
var sb = new StringBuilder(1024);
// file header
sb.AppendLine("// 此文件由 Source Generator 自动生成,请勿手动修改");
sb.AppendLine("// <auto-generated />");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine("using System.Threading.Tasks;");
sb.AppendLine($"using {SharedConstants.IocNamespace};");
sb.AppendLine();
sb.AppendLine("#nullable enable");
sb.AppendLine();
// type header
var targetMethod = match.Info.Target;
var indent = targetMethod.ContainingType.GenerateTypeHeader(sb);
// method
var indentStr = new string(' ', indent * 4);
var targetMethodName = targetMethod.Name;
var isStatic = targetMethod.IsStatic;
var isAwaitable = targetMethod.IsAwaitable();
sb.Append(indentStr).AppendLine("[global::System.CodeDom.Compiler.GeneratedCode(\"PCL.Core.SourceGenerators.DependencyCollectorGenerator\", \"1.0.0.0\")]");
sb.Append(indentStr).AppendLine("[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]");
var idCode = match.Info.Identifier.SnakeIdToPascal();
sb.Append(indentStr).Append("private ");
if (isStatic) sb.Append("static ");
sb.Append(isAwaitable ? "async Task " : "void ");
sb.Append(targetMethodName).Append("_InvokeInjection_").Append(idCode).AppendLine("()");
sb.Append(indentStr).AppendLine("{");
foreach (var dep in match.Dependencies)
{
sb.Append(indentStr).Append(" ");
if (isAwaitable) sb.Append("await ");
sb.Append(targetMethodName).Append("(");
var depRef = dep.Target.GetQualifiedSymbolName();
switch (dep.TargetType)
{
case AttributeTargets.Class:
sb.Append("static () => new ").Append(depRef).Append("()");
break;
case AttributeTargets.Method:
sb.Append(depRef);
break;
case AttributeTargets.Property:
sb.Append("new PropertyAccessor(getter: ");
var prop = (IPropertySymbol)dep.Target;
if (prop.IsWriteOnly) sb.Append("null");
else sb.Append("static () => ").Append(depRef);
sb.Append(", setter: ");
if (prop.IsReadOnly) sb.Append("null");
else sb.Append("static value => ").Append(depRef).Append(" = value");
sb.Append(")");
break;
}
foreach (var arg in dep.CollectorAttr.ConstructorArguments)
sb.Append(", ").Append(arg.ToCSharpString());
sb.AppendLine(");");
}
sb.Append(indentStr).AppendLine("}");
// type footer
while (indent-- > 0) sb.Append(' ', indent * 4).AppendLine("}");
// register source code
spc.AddSource($"{targetMethod.GetQualifiedSymbolName()}.g.cs", sb.ToString());
}
}
}
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace PCL.Core.SourceGenerators;
[Generator(LanguageNames.CSharp)]
public class EnvironmentInteropGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var secretProvider = context.CompilationProvider.Select(static (_, _) =>
{
// 判断 PCL_WRITE_SECRET 是否存在并遍历转换环境变量
// 打死也不会用 MSBuild Properties 这种非人类设计出来的垃圾
#pragma warning disable RS1035
var envs = Environment.GetEnvironmentVariables();
#pragma warning restore RS1035
var secretPairs = envs.Contains("PCL_WRITE_SECRET") ? (
from key in (
from key in envs.Keys.Cast<string>()
where !string.IsNullOrWhiteSpace(key) && key.StartsWith("PCL_") && key != "PCL_WRITE_SECRET"
select key
)
let value = envs[key]?.ToString()
where !string.IsNullOrWhiteSpace(value)
select (key.Substring(4), value)
) : [];
return secretPairs;
});
// 注册源代码输出
context.RegisterSourceOutput(secretProvider, static (spc, secretPairs) => _Execute(spc, secretPairs));
}
private static void _Execute(SourceProductionContext context, IEnumerable<(string, string)> secretPairs)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("// 此文件由 Source Generator 自动生成,请勿手动修改");
sb.AppendLine();
sb.AppendLine("#nullable enable");
sb.AppendLine();
sb.AppendLine("namespace PCL.Core.Utils.OS;");
sb.AppendLine();
sb.AppendLine("partial class EnvironmentInterop");
sb.AppendLine("{");
sb.AppendLine(" private static readonly System.Collections.Generic.Dictionary<string, string?> SecretDictionary = new()");
sb.AppendLine(" {");
foreach (var (key, value) in secretPairs)
sb.AppendLine($" [\"{key}\"] = {_ToVerbatimString(value)},");
sb.AppendLine(" };");
sb.AppendLine("}");
context.AddSource("EnvironmentInterop.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
private static string _ToVerbatimString(string text)
{
return "@\"" + text.Replace("\"", "\"\"") + "\"";
}
}
@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace PCL.Core.SourceGenerators;
[Generator(LanguageNames.CSharp)]
public class LifecycleScopeGenerator : IIncrementalGenerator
{
private const string ScopeAttributeType = SharedConstants.LifecycleScopeAttribute;
private const string StartMethodAttributeType = SharedConstants.LifecycleStartAttribute;
private const string StopMethodAttributeType = SharedConstants.LifecycleStopAttribute;
private const string CommandHandlerMethodAttributeType = SharedConstants.LifecycleCommandHandlerAttribute;
private const string DependencyInjectionMethodAttributeType = SharedConstants.LifecycleDependencyInjectionAttribute;
private const string NewDependencyInjectionPointAttributeType = SharedConstants.DependencyInjectionPointAttribute;
private static readonly HashSet<string> _MethodAttributeTypes = [
StartMethodAttributeType, StopMethodAttributeType,
CommandHandlerMethodAttributeType, DependencyInjectionMethodAttributeType,
NewDependencyInjectionPointAttributeType
];
private record ScopeMethodModel
{
public string MethodName { get; init; } = null!;
public bool Awaitable { get; init; }
}
private record StartMethodModel : ScopeMethodModel;
private record StopMethodModel : ScopeMethodModel;
private record CommandHandlerMethodModel(
string Command,
bool HasCommandModelArg,
bool HasIsCallbackArg,
(string Name, string TypeName, bool hasDefaultValue, object? DefaultValue)[] SplitArgs
) : ScopeMethodModel;
private record DependencyInjectionMethodModel(
string Identifier,
int Targets,
string ParameterType
) : ScopeMethodModel;
private record NewDependencyInjectionPointModel(
string Identifier
) : ScopeMethodModel;
private class ScopeModel
{
public string Namespace { get; init; } = null!;
public string TypeName { get; init; } = null!;
public string QualifiedTypeName => $"{Namespace}.{TypeName}";
public string Identifier { get; init; } = null!;
public string Name { get; init; } = null!;
public bool SupportAsync { get; init; }
public List<ScopeMethodModel> Methods { get; } = [];
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Debugger.Launch();
var candidates = context.SyntaxProvider.ForAttributeWithMetadataName(
ScopeAttributeType,
static (node, _) => node is ClassDeclarationSyntax syntax && syntax.Modifiers.Any(m => m.ValueText == "partial"),
static (INamedTypeSymbol, ScopeModel)? (ctx, _) =>
{
if (ctx.TargetSymbol is not INamedTypeSymbol typeSymbol) return null;
var attr = ctx.Attributes[0];
var args = attr.ConstructorArguments;
var scopeIdentifier = args[0].Value!.ToString();
var scopeName = args[1].Value!.ToString();
var scopeAsyncStart = true;
if (args.Length > 2 && args[2].Value is bool v) scopeAsyncStart = v;
var ns = typeSymbol.ContainingNamespace.ToDisplayString();
var typeName = typeSymbol.Name;
return (typeSymbol, new ScopeModel
{
Namespace = ns,
TypeName = typeName,
Identifier = scopeIdentifier,
Name = scopeName,
SupportAsync = scopeAsyncStart
});
}
).Where(static i => i is not null).Select(static (i, _) => i.GetValueOrDefault());
var collected = candidates.Collect();
context.RegisterSourceOutput(collected, _CollectSources);
}
private static void _CollectSources(SourceProductionContext spc, ImmutableArray<(INamedTypeSymbol TypeSymbol, ScopeModel Model)> models)
{
foreach (var (symbol, model) in models)
{
model.Methods.Clear();
foreach (var member in symbol.GetMembers())
{
if (member is not IMethodSymbol method) continue;
var attrTypeName = string.Empty;
var attr = method.GetAttributes().FirstOrDefault(data =>
{
attrTypeName = data.AttributeClass?.GetSimplifiedTypeName();
return attrTypeName is not null && _MethodAttributeTypes.Contains(attrTypeName);
});
if (attr is null) continue;
var methodName = method.Name;
var awaitable = method.IsAwaitable();
ScopeMethodModel? methodModel = attrTypeName switch
{
StartMethodAttributeType => new StartMethodModel { MethodName = methodName, Awaitable = awaitable },
StopMethodAttributeType => new StopMethodModel { MethodName = methodName, Awaitable = awaitable },
CommandHandlerMethodAttributeType => GetCommandHandlerMethodModel(),
DependencyInjectionMethodAttributeType => GetDependencyInjectionMethodModel(),
NewDependencyInjectionPointAttributeType => GetNewDependencyInjectionPointModel(),
_ => null
};
if (methodModel is not null) model.Methods.Add(methodModel);
continue;
CommandHandlerMethodModel? GetCommandHandlerMethodModel()
{
if (awaitable) return null;
var command = attr.ConstructorArguments[0].Value!.ToString();
var paraArray = method.Parameters;
var skip = 0;
var hasCommandModelArg = paraArray.Length > 0
&& paraArray[0].Type.GetSimplifiedTypeName() == "PCL.Core.App.Cli.CommandLine";
if (hasCommandModelArg) skip++;
var hasIsCallbackArgIndex = hasCommandModelArg ? 1 : 0;
var hasIsCallbackArg = paraArray.Length > hasIsCallbackArgIndex
&& paraArray[hasIsCallbackArgIndex].Type.SpecialType == SpecialType.System_Boolean
&& paraArray[hasIsCallbackArgIndex].Name == "isCallback";
if (hasIsCallbackArg) skip++;
var splitArgs = (
from para in paraArray.Skip(skip)
let name = para.Name
let typeName = para.Type.GetFullyQualifiedName()
let hasDefaultValue = para.HasExplicitDefaultValue
select (name, typeName, hasDefaultValue, hasDefaultValue ? para.ExplicitDefaultValue : null)
).ToArray();
return new CommandHandlerMethodModel(command, hasCommandModelArg, hasIsCallbackArg, splitArgs)
{
MethodName = methodName,
Awaitable = false
};
}
DependencyInjectionMethodModel? GetDependencyInjectionMethodModel()
{
var args = attr.ConstructorArguments;
var identifier = args[0].Value!.ToString();
var targets = (int)args[1].Value!;
if (method.Parameters.FirstOrDefault() is not { } param) return null;
var paramType = param.Type.GetFullyQualifiedName();
return new DependencyInjectionMethodModel(identifier, targets, paramType)
{
MethodName = methodName,
Awaitable = awaitable
};
}
NewDependencyInjectionPointModel? GetNewDependencyInjectionPointModel()
{
var args = attr.ConstructorArguments;
if (args.Length > 1 && args[1].Value is false) return null; // lifecycleAutoInvoke is set to false
var identifier = args[0].Value!.ToString();
return new NewDependencyInjectionPointModel(identifier)
{
MethodName = methodName,
Awaitable = awaitable
};
}
}
spc.AddSource($"{model.QualifiedTypeName}.g.cs", _GenerateScopeSource(model));
}
}
private static readonly HashSet<Type> _TypesIncludingInStartMethod = [
typeof(StartMethodModel),
typeof(CommandHandlerMethodModel),
typeof(DependencyInjectionMethodModel),
typeof(CommandHandlerMethodModel),
typeof(NewDependencyInjectionPointModel),
];
private static string _GenerateScopeSource(ScopeModel model)
{
var sb = new StringBuilder();
// head
sb.AppendLine("// 此文件由 Source Generator 自动生成,请勿手动修改");
sb.AppendLine("// <auto-generated />");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine("using System.Threading.Tasks;");
sb.AppendLine($"using {SharedConstants.AppNamespace};");
sb.AppendLine($"using {SharedConstants.IocNamespace};");
sb.AppendLine();
sb.AppendLine("#nullable enable");
sb.AppendLine();
sb.AppendLine($"namespace {model.Namespace};");
sb.AppendLine();
// basic structure
sb.AppendLine($"partial class {model.TypeName} : ILifecycleService");
sb.AppendLine("{");
sb.AppendLine($" public string Identifier => {model.Identifier.ToLiteral()};");
sb.AppendLine($" public string Name => {model.Name.ToLiteral()};");
sb.AppendLine($" public bool SupportAsync => {(model.SupportAsync ? "true" : "false")};");
sb.AppendLine();
sb.AppendLine(" private static LifecycleContext Context { get => field ?? throw new InvalidOperationException(\"Not initialized\"); set; } = null!;");
sb.AppendLine(" private static ILifecycleService Service => Context.ServiceInstance;");
sb.AppendLine($" public {model.TypeName}() {{ Context = Lifecycle.GetContext(this); }}");
sb.AppendLine();
// StopAsync() implementation
sb.AppendLine(" public async Task StopAsync()");
sb.AppendLine(" {");
var stopCount = AppendMethodInvokes(2, model.Methods.Where(x => x is StopMethodModel));
sb.AppendLine(" }");
sb.AppendLine();
// StartAsync() implementation
sb.AppendLine(" public async Task StartAsync()");
sb.AppendLine(" {");
AppendMethodInvokes(2, model.Methods.Where(x => _TypesIncludingInStartMethod.Contains(x.GetType())));
if (stopCount == 0) sb.AppendLine(" Context.DeclareStopped();");
sb.AppendLine(" }");
// structure tail
sb.AppendLine("}");
return sb.ToString();
// method invokes implementation
int AppendMethodInvokes(int indent, IEnumerable<ScopeMethodModel> models)
{
var count = 0;
var indentStr = new string(' ', indent * 4);
foreach (var methodModel in models)
{
count++;
sb.Append(indentStr).AppendLine("{");
foreach (var line in _EmitMethod(methodModel)) sb.Append(indentStr).Append(" ").AppendLine(line);
sb.Append(indentStr).AppendLine("}");
}
return count;
}
}
private static IEnumerable<string> _EmitMethod(ScopeMethodModel model)
{
if (model is StartMethodModel or StopMethodModel)
{
yield return MethodInvoke();
}
else if (model is CommandHandlerMethodModel argModel)
{
var actionParamModel = argModel.HasCommandModelArg ? "model" : "_";
var actionParamIsCallback = argModel.HasIsCallbackArg ? "isCallback" : "_";
yield return $"Essentials.StartupService.TryHandleCommand(" +
$"{argModel.Command.ToLiteral()}, ({actionParamModel}, {actionParamIsCallback}) => {{";
var argTexts = new List<string>();
if (argModel.HasCommandModelArg) argTexts.Add(actionParamModel);
if (argModel.HasIsCallbackArg) argTexts.Add(actionParamIsCallback);
foreach (var (name, typeName, hasDefaultValue, defaultValue) in argModel.SplitArgs)
{
var existsText = "exists_" + name;
var isTypeMatchText = "isTypeMatch_" + name;
var valueText = "value_" + name;
yield return $" var ({(hasDefaultValue ? existsText : "_")}, {isTypeMatchText}) = model.TryGetArgumentValue<{typeName}>(\"{name}\", out var {valueText});";
yield return $" if (!{isTypeMatchText}) throw new InvalidCastException(\"Argument type mismatch\");";
argTexts.Add(hasDefaultValue ? $"{existsText} ? {valueText} : {defaultValue.ToPrimitive() ?? "default"}" : valueText);
}
yield return MethodInvoke(" ", argTexts);
yield return "}, true);";
}
else if (model is DependencyInjectionMethodModel diModel)
{
var awaitable = diModel.Awaitable;
if (awaitable) yield return "await Task.Run(() => {";
var indentStr = awaitable ? " " : "";
if (awaitable) yield return $"{indentStr}Func<{diModel.ParameterType}, Task>";
else yield return $"{indentStr}Action<{diModel.ParameterType}>";
yield return $"{indentStr} action = {diModel.MethodName};";
yield return $"{indentStr}var result = DependencyGroups.InvokeInjection(action, " +
$"{diModel.Identifier.ToLiteral()}, " +
$"(AttributeTargets){diModel.Targets});";
var logStr = diModel.Identifier + "@" + diModel.Targets;
yield return $"{indentStr}if (result) Context.Trace(\"Dependency injection success: {logStr}\");";
yield return $"{indentStr}else Context.Warn(\"Dependency injection failed: {logStr}\");";
if (awaitable) yield return "});";
}
else if (model is NewDependencyInjectionPointModel newDiModel)
{
yield return $"{(model.Awaitable ? "await " : "")}" +
$"{model.MethodName}_InvokeInjection_{newDiModel.Identifier.SnakeIdToPascal()}();";
}
yield break;
string MethodInvoke(string prefix = "", params IEnumerable<string> args)
=> $"{prefix}{(model.Awaitable ? "await " : "")}{model.MethodName}({string.Join(", ", args)});";
}
}
@@ -0,0 +1,292 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace PCL.Core.SourceGenerators;
[Generator(LanguageNames.CSharp)]
public class LifecycleServiceTypesGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 查找 LifecycleState.cs 文件以获取有效的枚举值
var lifecycleStateProvider = context.AdditionalTextsProvider
.Where(static file => file.Path.EndsWith("LifecycleState.cs"))
.Select(static (text, cancellationToken) =>
{
var content = text.GetText(cancellationToken)?.ToString();
return _GetValidLifecycleStates(content);
})
.Where(static states => states?.Count > 0)
.Collect();
// 查找带有 LifecycleService 属性的类
var serviceClassProvider = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (s, _) => s is ClassDeclarationSyntax { AttributeLists.Count: > 0 },
transform: static (ctx, _) => _GetLifecycleServiceInfo(ctx))
.Where(static x => x is not null);
// 收集所有服务信息
var servicesProvider = serviceClassProvider.Collect();
// 合并枚举状态和服务信息
var combinedProvider = lifecycleStateProvider.Combine(servicesProvider);
// 生成代码
context.RegisterSourceOutput(combinedProvider,
static (spc, data) => _Execute(spc, data.Left.FirstOrDefault() ?? new List<string>(), [..data.Right.Where(x => x is not null).Select(x => x!)]));
}
private static List<string>? _GetValidLifecycleStates(string? content)
{
if (string.IsNullOrEmpty(content))
return null;
var validStates = new List<string>();
// 提取枚举定义块
var enumPattern = @"(?s)public\s+enum\s+LifecycleState\s*\{(.*?)\}";
var enumMatch = Regex.Match(content, enumPattern);
if (!enumMatch.Success)
return null;
var enumContent = enumMatch.Groups[1].Value;
// 按行分割并处理每一行
var lines = enumContent.Split('\n');
foreach (var line in lines)
{
var trimmedLine = line.Trim();
// 跳过空行、注释行和花括号
if (string.IsNullOrEmpty(trimmedLine) ||
trimmedLine.StartsWith("///") ||
trimmedLine.StartsWith("//") ||
trimmedLine.StartsWith("/*") ||
trimmedLine.StartsWith("*") ||
trimmedLine == "{" ||
trimmedLine == "}")
{
continue;
}
// 匹配枚举成员(可能包含逗号)
var memberMatch = Regex.Match(trimmedLine, @"^(\w+)\s*,?\s*$");
if (memberMatch.Success)
{
var enumValue = memberMatch.Groups[1].Value;
if (!string.IsNullOrEmpty(enumValue) && enumValue != "LifecycleState")
{
validStates.Add(enumValue);
}
}
}
return validStates.Count > 0 ? validStates : null;
}
private static LifecycleServiceInfo? _GetLifecycleServiceInfo(GeneratorSyntaxContext context)
{
var classDeclaration = (ClassDeclarationSyntax)context.Node;
// 查找 LifecycleService 属性
var lifecycleAttribute = classDeclaration.AttributeLists
.SelectMany(al => al.Attributes)
.FirstOrDefault(a => a.Name.ToString().Contains("LifecycleService"));
if (lifecycleAttribute is null)
return null;
// 获取语义模型信息
var semanticModel = context.SemanticModel;
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
if (classSymbol is null)
return null;
// 解析属性参数
var state = "Unknown";
var priority = 0;
if (lifecycleAttribute.ArgumentList?.Arguments.Count > 0)
{
// 解析第一个参数(状态)
var firstArg = lifecycleAttribute.ArgumentList.Arguments[0];
if (firstArg.Expression is MemberAccessExpressionSyntax memberAccess)
{
state = memberAccess.Name.Identifier.ValueText;
}
// 查找 Priority 参数(支持命名参数和位置参数)
var priorityArg = lifecycleAttribute.ArgumentList.Arguments
.FirstOrDefault(arg => arg.NameEquals?.Name.Identifier.ValueText == "Priority");
// 如果没有找到命名的Priority参数,检查第二个位置参数
if (priorityArg is null && lifecycleAttribute.ArgumentList.Arguments.Count > 1)
{
priorityArg = lifecycleAttribute.ArgumentList.Arguments[1];
}
if (priorityArg is not null)
{
priority = _ParsePriorityExpression(priorityArg.Expression, semanticModel);
}
}
return new LifecycleServiceInfo(
classSymbol.ToDisplayString(),
classSymbol.Name,
state,
priority);
}
private static int _ParsePriorityExpression(ExpressionSyntax expression, SemanticModel semanticModel)
{
switch (expression)
{
case LiteralExpressionSyntax literal:
if (int.TryParse(literal.Token.ValueText, out var literalValue))
return literalValue;
break;
case MemberAccessExpressionSyntax memberAccess:
var memberName = memberAccess.ToString();
if (memberName == "int.MaxValue")
return int.MaxValue;
if (memberName == "int.MinValue")
return int.MinValue;
break;
case PrefixUnaryExpressionSyntax unary when unary.IsKind(SyntaxKind.UnaryMinusExpression):
// 处理负数
if (unary.Operand is LiteralExpressionSyntax negLiteral &&
int.TryParse(negLiteral.Token.ValueText, out var negValue))
{
return -negValue;
}
break;
case BinaryExpressionSyntax binary:
// 简单的数学表达式支持
var left = _ParsePriorityExpression(binary.Left, semanticModel);
var right = _ParsePriorityExpression(binary.Right, semanticModel);
return binary.OperatorToken.Kind() switch
{
SyntaxKind.PlusToken => left + right,
SyntaxKind.MinusToken => left - right,
SyntaxKind.AsteriskToken => left * right,
SyntaxKind.SlashToken => right != 0 ? left / right : 0,
_ => 0
};
}
// 尝试获取常量值
var constantValue = semanticModel.GetConstantValue(expression);
return constantValue is { HasValue: true, Value: int intValue } ? intValue : 0;
}
private static void _Execute(SourceProductionContext context, List<string> validStates, ImmutableArray<LifecycleServiceInfo> services)
{
// 过滤服务,只保留有效状态的服务
var filteredServices = services.Where(s => validStates.Count == 0 || validStates.Contains(s.State)).ToList();
// 按状态分组并排序
var groupedServices = filteredServices
.GroupBy(s => s.State)
.OrderBy(g => g.Key)
.ToList();
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("// 此文件由 Source Generator 自动生成,请勿手动修改");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine();
sb.AppendLine("namespace PCL.Core.App.IoC;");
sb.AppendLine();
sb.AppendLine("/// <summary>");
sb.AppendLine("/// 包含所有使用 LifecycleService 注解的类型,按 StartState 分类并按 Priority 降序排序");
sb.AppendLine("/// </summary>");
sb.AppendLine("public static class LifecycleServiceTypes");
sb.AppendLine("{");
// 为每个状态生成数组
foreach (var group in groupedServices)
{
var sortedServices = group.OrderByDescending(s => s.Priority).ToList();
sb.AppendLine($" /// <summary>");
sb.AppendLine($" /// {group.Key} 状态的生命周期服务类型");
sb.AppendLine($" /// </summary>");
sb.AppendLine($" public static readonly Type[] {group.Key} = [");
foreach (var service in sortedServices)
{
sb.AppendLine($" typeof({service.FullName}), // Priority: {service.Priority}");
}
sb.AppendLine(" ];");
sb.AppendLine();
}
// 生成 GetServiceTypes 方法
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 获取指定生命周期状态的所有服务类型");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"state\">生命周期状态</param>");
sb.AppendLine(" /// <returns>该状态下的所有服务类型数组</returns>");
sb.AppendLine(" public static Type[] GetServiceTypes(LifecycleState state) => state switch");
sb.AppendLine(" {");
foreach (var group in groupedServices)
{
sb.AppendLine($" LifecycleState.{group.Key} => {group.Key},");
}
sb.AppendLine(" _ => new Type[0]");
sb.AppendLine(" };");
sb.AppendLine();
// 生成 GetAllServiceTypes 方法
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 获取所有生命周期服务类型的状态映射");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <returns>状态到类型数组的字典</returns>");
sb.AppendLine(" public static System.Collections.Generic.Dictionary<LifecycleState, Type[]> GetAllServiceTypes() => new()");
sb.AppendLine(" {");
foreach (var group in groupedServices)
{
sb.AppendLine($" [LifecycleState.{group.Key}] = {group.Key},");
}
sb.AppendLine(" };");
sb.AppendLine();
// 生成统计信息方法
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 获取生命周期服务的统计信息");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <returns>包含状态数量和总服务数量的统计信息</returns>");
sb.AppendLine($" public static (int StateCount, int TotalServices) GetStatistics() => ({groupedServices.Count}, {filteredServices.Count});");
sb.AppendLine("}");
context.AddSource("LifecycleServiceTypes.g.cs", sb.ToString());
}
}
// 破烂 .NET Standard 用不了 init 修饰没法 record,先这样吧
public class LifecycleServiceInfo(string fullName, string className, string state, int priority)
{
public string FullName { get; } = fullName;
public string ClassName { get; } = className;
public string State { get; } = state;
public int Priority { get; } = priority;
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<Platforms>AnyCPU;x64;ARM64</Platforms>
<Configurations>Debug;CI;Release;Beta</Configurations>
<!-- 确保源代码生成器在所有平台架构下都能正常工作 -->
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.6.0" PrivateAssets="all" />
</ItemGroup>
</Project>
@@ -0,0 +1,14 @@
namespace PCL.Core.SourceGenerators;
public static class SharedConstants
{
public const string AppNamespace = "PCL.Core.App";
public const string IocNamespace = $"{AppNamespace}.IoC";
public const string DependencyCollectorAttribute = $"{IocNamespace}.DependencyCollectorAttribute";
public const string DependencyInjectionPointAttribute = $"{IocNamespace}.DependencyInjectionPointAttribute";
public const string LifecycleScopeAttribute = $"{IocNamespace}.LifecycleScopeAttribute";
public const string LifecycleStartAttribute = $"{IocNamespace}.LifecycleStartAttribute";
public const string LifecycleStopAttribute = $"{IocNamespace}.LifecycleStopAttribute";
public const string LifecycleCommandHandlerAttribute = $"{IocNamespace}.LifecycleCommandHandlerAttribute";
public const string LifecycleDependencyInjectionAttribute = $"{IocNamespace}.LifecycleDependencyInjectionAttribute";
}
@@ -0,0 +1,252 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace PCL.Core.SourceGenerators;
public static class SharedExtensions
{
public static string ToLiteral(this string str) => SymbolDisplay.FormatLiteral(str, true);
public static string? ToPrimitive(this object? obj) => SymbolDisplay.FormatPrimitive(obj, true, false);
public static int GetDeclarationOrder(this ISymbol symbol)
{
var loc = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation();
return loc?.SourceSpan.Start ?? int.MaxValue;
}
extension(INamedTypeSymbol type)
{
public bool IsPartial()
{
foreach (var decl in type.DeclaringSyntaxReferences)
{
if (decl.GetSyntax() is ClassDeclarationSyntax { Modifiers: { } modifiers } &&
modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
return true;
}
return false;
}
public bool IsNestedWithin(INamedTypeSymbol potentialContainer)
{
var t = type.ContainingType;
while (t is not null)
{
if (SymbolEqualityComparer.Default.Equals(t, potentialContainer))
return true;
t = t.ContainingType;
}
return false;
}
public bool IsAttribute()
{
var baseType = type.BaseType;
while (baseType is not null)
{
if (baseType.ToDisplayString() == "System.Attribute") return true;
baseType = baseType.BaseType;
}
return false;
}
public int GenerateTypeHeader(StringBuilder sb)
{
var ctnTypes = new Stack<INamedTypeSymbol>();
for (var ctnType = type.ContainingType; ctnType is not null; ctnType = ctnType.ContainingType) ctnTypes.Push(ctnType);
// namespace
var ns = type.ContainingNamespace?.ToDisplayString();
var indent = 0;
if (!string.IsNullOrEmpty(ns))
{
sb.Append("namespace ").Append(ns).AppendLine();
sb.AppendLine("{");
indent++;
}
// outer classes
foreach (var containingType in ctnTypes)
{
sb.Append(' ', indent * 4).Append("partial class ").Append(containingType.Name).AppendLine();
sb.Append(' ', indent * 4).AppendLine("{");
indent++;
}
// class
sb.Append(' ', indent * 4).Append("partial class ").Append(type.Name).AppendLine();
sb.Append(' ', indent * 4).AppendLine("{");
return indent + 1;
}
}
public static string RenderDefaultValueCode(this SemanticModel sm, ExpressionSyntax expr)
{
if (expr is LiteralExpressionSyntax || expr.IsNegativeNumeric())
return expr.ToString();
if (expr is TypeOfExpressionSyntax toe)
{
var type = sm.GetTypeInfo(toe.Type).Type;
if (type is not null)
return "typeof(" + type.GetFullyQualifiedName() + ")";
return expr.ToString();
}
if (expr is InvocationExpressionSyntax
{
Expression: IdentifierNameSyntax { Identifier.ValueText: "nameof" },
ArgumentList.Arguments.Count: 1
} inv)
{
var targetExpr = inv.ArgumentList.Arguments[0].Expression;
var sym = sm.GetSymbolInfo(targetExpr).Symbol;
if (sym is not null)
{
return "nameof(" + sym.GetQualifiedSymbolName() + ")";
}
return expr.ToString();
}
var s = sm.GetSymbolInfo(expr).Symbol;
if (s is IFieldSymbol fs)
{
return fs.GetQualifiedSymbolName();
}
return expr.ToString();
}
public static bool IsNegativeNumeric(this ExpressionSyntax expr)
{
return expr is PrefixUnaryExpressionSyntax p
&& p.IsKind(SyntaxKind.UnaryMinusExpression)
&& p.Operand is LiteralExpressionSyntax l
&& l.IsKind(SyntaxKind.NumericLiteralExpression);
}
extension(ISymbol symbol)
{
public string GetQualifiedSymbolName()
{
if (symbol is ITypeSymbol ts) return ts.GetFullyQualifiedName();
var parts = new Stack<string>();
parts.Push(symbol.Name);
var t = symbol.ContainingType;
while (t is not null)
{
parts.Push(t.Name);
t = t.ContainingType;
}
var ns = symbol.ContainingNamespace?.ToDisplayString();
if (!string.IsNullOrEmpty(ns)) parts.Push(ns!);
return string.Join(".", parts);
}
}
private static readonly SymbolDisplayFormat _SimplifiedTypeNameFormat = new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
miscellaneousOptions:
SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
SymbolDisplayMiscellaneousOptions.CollapseTupleTypes |
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier |
SymbolDisplayMiscellaneousOptions.UseSpecialTypes,
genericsOptions: SymbolDisplayGenericsOptions.None
);
private static readonly SymbolDisplayFormat _FullQualifiedNameFormat = new(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions:
SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
SymbolDisplayMiscellaneousOptions.UseSpecialTypes
);
extension(ITypeSymbol type)
{
public string GetSimplifiedTypeName()
{
return type.ToDisplayString(_SimplifiedTypeNameFormat);
}
public string GetFullyQualifiedName()
{
if (type is INamedTypeSymbol {
OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
TypeArguments.Length: 1 } nt)
{
var inner = nt.TypeArguments[0];
return inner.GetFullyQualifiedName() + "?";
}
if (type.TryGetSpecialTypeKeyword(out var keyword)) return keyword;
return type.ToDisplayString(_FullQualifiedNameFormat);
}
public bool TryGetSpecialTypeKeyword(out string keyword)
{
switch (type.SpecialType)
{
case SpecialType.System_Boolean: keyword = "bool"; return true;
case SpecialType.System_Byte: keyword = "byte"; return true;
case SpecialType.System_SByte: keyword = "sbyte"; return true;
case SpecialType.System_Int16: keyword = "short"; return true;
case SpecialType.System_UInt16: keyword = "ushort"; return true;
case SpecialType.System_Int32: keyword = "int"; return true;
case SpecialType.System_UInt32: keyword = "uint"; return true;
case SpecialType.System_Int64: keyword = "long"; return true;
case SpecialType.System_UInt64: keyword = "ulong"; return true;
case SpecialType.System_IntPtr: keyword = "nint"; return true;
case SpecialType.System_UIntPtr: keyword = "nuint"; return true;
case SpecialType.System_Char: keyword = "char"; return true;
case SpecialType.System_String: keyword = "string"; return true;
case SpecialType.System_Object: keyword = "object"; return true;
case SpecialType.System_Single: keyword = "float"; return true;
case SpecialType.System_Double: keyword = "double"; return true;
case SpecialType.System_Decimal: keyword = "decimal"; return true;
default: keyword = ""; return false;
}
}
}
public static string GetQualifiedPropertyAccess(this IPropertySymbol prop)
{
var owner = prop.ContainingType.GetFullyQualifiedName();
return owner + "." + prop.Name;
}
public static bool IsAwaitable(this IMethodSymbol method)
{
// TODO this is a very naive implementation.
return method.ReturnType.GetSimplifiedTypeName() == "System.Threading.Tasks.Task";
}
public static string CorrectConfigTypeName(this string typeName, out string? fullTypeName)
{
var isArgConfig = typeName.StartsWith("PCL.Core.App.Configuration.ArgConfig<");
if (isArgConfig)
{
fullTypeName = typeName;
typeName = typeName.Substring(37, typeName.Length - 38);
}
else fullTypeName = null;
return typeName;
}
extension(string str)
{
public string SnakeIdToPascal()
{
var sb = new StringBuilder();
foreach (var part in str.Split('-'))
{
if (part.Length == 0) continue;
sb.Append(char.ToUpper(part[0])).Append(part.Substring(1));
}
return sb.ToString();
}
}
}
@@ -0,0 +1,10 @@
namespace System.Runtime.CompilerServices;
using ComponentModel;
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit;