CfgSectionGenerator.cs 13 KB

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