CfgSectionGenerator.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Immutable;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using Microsoft.CodeAnalysis;
  8. using Microsoft.CodeAnalysis.CSharp;
  9. using Microsoft.CodeAnalysis.CSharp.Syntax;
  10. using Microsoft.CodeAnalysis.Text;
  11. namespace Apq.Cfg.SourceGenerator;
  12. /// <summary>
  13. /// 配置节源生成器,为标记了 [CfgSection] 的类生成零反射绑定代码
  14. /// </summary>
  15. [Generator]
  16. public class CfgSectionGenerator : IIncrementalGenerator
  17. {
  18. /// <summary>
  19. /// 特性的完全限定名
  20. /// </summary>
  21. private const string CfgSectionAttributeFullName = "Apq.Cfg.CfgSectionAttribute";
  22. /// <summary>
  23. /// 初始化源生成器,注册特性和代码生成逻辑
  24. /// </summary>
  25. /// <param name="context">增量生成器初始化上下文</param>
  26. public void Initialize(IncrementalGeneratorInitializationContext context)
  27. {
  28. // 1. 注册特性源代码(注入到用户项目)
  29. context.RegisterPostInitializationOutput(ctx =>
  30. {
  31. ctx.AddSource("CfgSectionAttribute.g.cs", SourceText.From(AttributeSourceCode, Encoding.UTF8));
  32. });
  33. // 2. 查找所有标记了 [CfgSection] 的类
  34. var classDeclarations = context.SyntaxProvider
  35. .ForAttributeWithMetadataName(
  36. CfgSectionAttributeFullName,
  37. predicate: static (node, _) => node is ClassDeclarationSyntax,
  38. transform: static (ctx, ct) => GetConfigClassInfo(ctx, ct))
  39. .Where(static info => info is not null)
  40. .Select(static (info, _) => info!);
  41. // 3. 收集所有配置类信息用于生成扩展方法
  42. var allClasses = classDeclarations.Collect();
  43. // 4. 为每个配置类生成绑定代码
  44. context.RegisterSourceOutput(classDeclarations, static (spc, classInfo) =>
  45. {
  46. var source = CodeEmitter.EmitBinderClass(classInfo);
  47. spc.AddSource($"{classInfo.FullTypeName.Replace(".", "_")}.Binder.g.cs", SourceText.From(source, Encoding.UTF8));
  48. });
  49. // 5. 生成统一的扩展方法类
  50. context.RegisterSourceOutput(allClasses, static (spc, classes) =>
  51. {
  52. if (classes.IsEmpty) return;
  53. var source = CodeEmitter.EmitExtensionsClass(classes);
  54. spc.AddSource("CfgRootGeneratedExtensions.g.cs", SourceText.From(source, Encoding.UTF8));
  55. });
  56. }
  57. /// <summary>
  58. /// 从语法上下文提取配置类信息
  59. /// </summary>
  60. private static ConfigClassInfo? GetConfigClassInfo(GeneratorAttributeSyntaxContext context, CancellationToken ct)
  61. {
  62. ct.ThrowIfCancellationRequested();
  63. if (context.TargetSymbol is not INamedTypeSymbol classSymbol)
  64. return null;
  65. // 检查是否为 partial 类
  66. var classDeclaration = context.TargetNode as ClassDeclarationSyntax;
  67. if (classDeclaration == null)
  68. return null;
  69. bool isPartial = classDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
  70. if (!isPartial)
  71. {
  72. // 非 partial 类,报告诊断(可选)
  73. return null;
  74. }
  75. // 获取特性参数
  76. var attribute = context.Attributes.FirstOrDefault();
  77. if (attribute == null)
  78. return null;
  79. // 获取 SectionPath
  80. string sectionPath = "";
  81. if (attribute.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is string path)
  82. {
  83. sectionPath = path;
  84. }
  85. // 如果 SectionPath 为空,从类名推断
  86. if (string.IsNullOrEmpty(sectionPath))
  87. {
  88. sectionPath = InferSectionPath(classSymbol.Name);
  89. }
  90. // 获取 GenerateExtension 属性
  91. bool generateExtension = true;
  92. foreach (var namedArg in attribute.NamedArguments)
  93. {
  94. if (namedArg.Key == "GenerateExtension" && namedArg.Value.Value is bool genExt)
  95. {
  96. generateExtension = genExt;
  97. }
  98. }
  99. // 收集属性信息
  100. var properties = new List<PropertyInfo>();
  101. CollectProperties(classSymbol, properties, ct);
  102. // 获取命名空间
  103. var namespaceName = classSymbol.ContainingNamespace.IsGlobalNamespace
  104. ? ""
  105. : classSymbol.ContainingNamespace.ToDisplayString();
  106. return new ConfigClassInfo(
  107. Namespace: namespaceName,
  108. ClassName: classSymbol.Name,
  109. FullTypeName: classSymbol.ToDisplayString(),
  110. SectionPath: sectionPath,
  111. GenerateExtension: generateExtension,
  112. Properties: properties.ToImmutableArray());
  113. }
  114. /// <summary>
  115. /// 从类名推断配置节路径
  116. /// </summary>
  117. private static string InferSectionPath(string className)
  118. {
  119. // 移除常见后缀
  120. string[] suffixes = { "Config", "Configuration", "Settings", "Options" };
  121. foreach (var suffix in suffixes)
  122. {
  123. if (className.EndsWith(suffix, StringComparison.Ordinal) && className.Length > suffix.Length)
  124. {
  125. return className.Substring(0, className.Length - suffix.Length);
  126. }
  127. }
  128. return className;
  129. }
  130. /// <summary>
  131. /// 收集类的所有可写公共属性
  132. /// </summary>
  133. private static void CollectProperties(INamedTypeSymbol classSymbol, List<PropertyInfo> properties, CancellationToken ct)
  134. {
  135. ct.ThrowIfCancellationRequested();
  136. foreach (var member in classSymbol.GetMembers())
  137. {
  138. if (member is not IPropertySymbol prop)
  139. continue;
  140. // 只处理公共、可写、非索引器属性
  141. if (prop.DeclaredAccessibility != Accessibility.Public)
  142. continue;
  143. if (prop.IsReadOnly || prop.IsWriteOnly)
  144. continue;
  145. if (prop.IsIndexer)
  146. continue;
  147. if (prop.IsStatic)
  148. continue;
  149. var propInfo = AnalyzeProperty(prop);
  150. properties.Add(propInfo);
  151. }
  152. }
  153. /// <summary>
  154. /// 分析属性类型
  155. /// </summary>
  156. private static PropertyInfo AnalyzeProperty(IPropertySymbol prop)
  157. {
  158. var propType = prop.Type;
  159. var typeKind = GetTypeKind(propType);
  160. var elementType = GetElementType(propType);
  161. var keyType = GetKeyType(propType);
  162. return new PropertyInfo(
  163. Name: prop.Name,
  164. TypeName: propType.ToDisplayString(),
  165. TypeKind: typeKind,
  166. ElementTypeName: elementType?.ToDisplayString(),
  167. KeyTypeName: keyType?.ToDisplayString(),
  168. IsNullable: propType.NullableAnnotation == NullableAnnotation.Annotated ||
  169. (propType is INamedTypeSymbol named && named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T));
  170. }
  171. /// <summary>
  172. /// 判断类型种类
  173. /// </summary>
  174. private static TypeKind GetTypeKind(ITypeSymbol type)
  175. {
  176. // 处理 Nullable<T>
  177. if (type is INamedTypeSymbol nullable && nullable.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
  178. {
  179. type = nullable.TypeArguments[0];
  180. }
  181. // 简单类型
  182. if (IsSimpleType(type))
  183. return TypeKind.Simple;
  184. // 数组
  185. if (type is IArrayTypeSymbol)
  186. return TypeKind.Array;
  187. // 泛型集合
  188. if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
  189. {
  190. var genericDef = namedType.OriginalDefinition.ToDisplayString();
  191. // Dictionary
  192. if (genericDef.StartsWith("System.Collections.Generic.Dictionary<", StringComparison.Ordinal) ||
  193. genericDef.StartsWith("System.Collections.Generic.IDictionary<", StringComparison.Ordinal))
  194. {
  195. return TypeKind.Dictionary;
  196. }
  197. // List/IList/ICollection/IEnumerable
  198. if (genericDef.StartsWith("System.Collections.Generic.List<", StringComparison.Ordinal) ||
  199. genericDef.StartsWith("System.Collections.Generic.IList<", StringComparison.Ordinal) ||
  200. genericDef.StartsWith("System.Collections.Generic.ICollection<", StringComparison.Ordinal) ||
  201. genericDef.StartsWith("System.Collections.Generic.IEnumerable<", StringComparison.Ordinal))
  202. {
  203. return TypeKind.List;
  204. }
  205. // HashSet/ISet
  206. if (genericDef.StartsWith("System.Collections.Generic.HashSet<", StringComparison.Ordinal) ||
  207. genericDef.StartsWith("System.Collections.Generic.ISet<", StringComparison.Ordinal))
  208. {
  209. return TypeKind.HashSet;
  210. }
  211. }
  212. // 复杂对象
  213. if (type.TypeKind == Microsoft.CodeAnalysis.TypeKind.Class ||
  214. type.TypeKind == Microsoft.CodeAnalysis.TypeKind.Struct)
  215. {
  216. return TypeKind.Complex;
  217. }
  218. return TypeKind.Unknown;
  219. }
  220. /// <summary>
  221. /// 获取集合元素类型
  222. /// </summary>
  223. private static ITypeSymbol? GetElementType(ITypeSymbol type)
  224. {
  225. if (type is IArrayTypeSymbol arrayType)
  226. return arrayType.ElementType;
  227. if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
  228. {
  229. var args = namedType.TypeArguments;
  230. if (args.Length >= 1)
  231. {
  232. var genericDef = namedType.OriginalDefinition.ToDisplayString();
  233. if (genericDef.StartsWith("System.Collections.Generic.Dictionary<", StringComparison.Ordinal) ||
  234. genericDef.StartsWith("System.Collections.Generic.IDictionary<", StringComparison.Ordinal))
  235. {
  236. return args.Length >= 2 ? args[1] : null;
  237. }
  238. return args[0];
  239. }
  240. }
  241. return null;
  242. }
  243. /// <summary>
  244. /// 获取字典键类型
  245. /// </summary>
  246. private static ITypeSymbol? GetKeyType(ITypeSymbol type)
  247. {
  248. if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
  249. {
  250. var genericDef = namedType.OriginalDefinition.ToDisplayString();
  251. if ((genericDef.StartsWith("System.Collections.Generic.Dictionary<", StringComparison.Ordinal) ||
  252. genericDef.StartsWith("System.Collections.Generic.IDictionary<", StringComparison.Ordinal)) &&
  253. namedType.TypeArguments.Length >= 1)
  254. {
  255. return namedType.TypeArguments[0];
  256. }
  257. }
  258. return null;
  259. }
  260. /// <summary>
  261. /// 判断是否为简单类型
  262. /// </summary>
  263. private static bool IsSimpleType(ITypeSymbol type)
  264. {
  265. // 基元类型
  266. switch (type.SpecialType)
  267. {
  268. case SpecialType.System_Boolean:
  269. case SpecialType.System_Byte:
  270. case SpecialType.System_SByte:
  271. case SpecialType.System_Int16:
  272. case SpecialType.System_UInt16:
  273. case SpecialType.System_Int32:
  274. case SpecialType.System_UInt32:
  275. case SpecialType.System_Int64:
  276. case SpecialType.System_UInt64:
  277. case SpecialType.System_Single:
  278. case SpecialType.System_Double:
  279. case SpecialType.System_Decimal:
  280. case SpecialType.System_Char:
  281. case SpecialType.System_String:
  282. return true;
  283. }
  284. // 枚举
  285. if (type.TypeKind == Microsoft.CodeAnalysis.TypeKind.Enum)
  286. return true;
  287. // 其他常见简单类型
  288. var fullName = type.ToDisplayString();
  289. return fullName switch
  290. {
  291. "System.DateTime" => true,
  292. "System.DateTimeOffset" => true,
  293. "System.TimeSpan" => true,
  294. "System.Guid" => true,
  295. "System.Uri" => true,
  296. "System.DateOnly" => true,
  297. "System.TimeOnly" => true,
  298. _ => false
  299. };
  300. }
  301. /// <summary>
  302. /// 特性源代码(注入到用户项目)
  303. /// </summary>
  304. private const string AttributeSourceCode = @"// <auto-generated/>
  305. #nullable enable
  306. namespace Apq.Cfg
  307. {
  308. /// <summary>
  309. /// 标记一个类为配置节,源生成器将为其生成零反射的绑定代码
  310. /// </summary>
  311. [global::System.AttributeUsage(global::System.AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
  312. [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
  313. internal sealed class CfgSectionAttribute : global::System.Attribute
  314. {
  315. /// <summary>
  316. /// 配置节路径
  317. /// </summary>
  318. public string SectionPath { get; }
  319. /// <summary>
  320. /// 是否生成扩展方法
  321. /// </summary>
  322. public bool GenerateExtension { get; set; } = true;
  323. /// <summary>
  324. /// 创建配置节特性
  325. /// </summary>
  326. public CfgSectionAttribute(string sectionPath = """")
  327. {
  328. SectionPath = sectionPath;
  329. }
  330. }
  331. }
  332. ";
  333. }
  334. /// <summary>
  335. /// 配置类信息
  336. /// </summary>
  337. internal sealed record ConfigClassInfo(
  338. string Namespace,
  339. string ClassName,
  340. string FullTypeName,
  341. string SectionPath,
  342. bool GenerateExtension,
  343. ImmutableArray<PropertyInfo> Properties);
  344. /// <summary>
  345. /// 属性信息
  346. /// </summary>
  347. internal sealed record PropertyInfo(
  348. string Name,
  349. string TypeName,
  350. TypeKind TypeKind,
  351. string? ElementTypeName,
  352. string? KeyTypeName,
  353. bool IsNullable);
  354. /// <summary>
  355. /// 类型种类
  356. /// </summary>
  357. internal enum TypeKind
  358. {
  359. Unknown,
  360. Simple,
  361. Array,
  362. List,
  363. HashSet,
  364. Dictionary,
  365. Complex
  366. }