CommandNameListGenerator.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. using System.Text;
  2. using Microsoft.CodeAnalysis;
  3. using Microsoft.CodeAnalysis.CSharp.Syntax;
  4. namespace PixiEditorGen;
  5. [Generator(LanguageNames.CSharp)]
  6. public class CommandNameListGenerator : IIncrementalGenerator
  7. {
  8. private const string Command = "PixiEditor.Models.Commands.Attributes.Commands";
  9. private const string Evaluators = "PixiEditor.Models.Commands.Attributes.Evaluators.Evaluator";
  10. private const string Groups = "PixiEditor.Models.Commands.Attributes.Commands.Command.GroupAttribute";
  11. public void Initialize(IncrementalGeneratorInitializationContext context)
  12. {
  13. var commandList = context.SyntaxProvider.CreateSyntaxProvider(
  14. (x, token) =>
  15. {
  16. return x is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
  17. }, static (context, cancelToken) =>
  18. {
  19. var method = (MethodDeclarationSyntax)context.Node;
  20. if (!HasCommandAttribute(method, context, cancelToken, Command))
  21. return (null, null, null);
  22. var symbol = context.SemanticModel.GetDeclaredSymbol(method, cancelToken);
  23. if (symbol is IMethodSymbol methodSymbol)
  24. {
  25. if (methodSymbol.ReceiverType == null)
  26. return (null, null, null);
  27. return (methodSymbol.ReceiverType.ToDisplayString(), methodSymbol.Name, methodSymbol.Parameters.Select(x => x.ToDisplayString()));
  28. }
  29. else
  30. {
  31. return (null, null, null);
  32. }
  33. }).Where(x => x.Item1 != null);
  34. var evaluatorList = context.SyntaxProvider.CreateSyntaxProvider(
  35. (x, token) =>
  36. {
  37. return x is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
  38. }, static (context, cancelToken) =>
  39. {
  40. var method = (MethodDeclarationSyntax)context.Node;
  41. if (!HasCommandAttribute(method, context, cancelToken, Evaluators))
  42. return (null, null, null);
  43. var symbol = context.SemanticModel.GetDeclaredSymbol(method, cancelToken);
  44. if (symbol is IMethodSymbol methodSymbol)
  45. {
  46. return (methodSymbol.ReceiverType.ToDisplayString(), methodSymbol.Name, methodSymbol.Parameters.Select(x => x.ToDisplayString()));
  47. }
  48. else
  49. {
  50. return (null, null, null);
  51. }
  52. }).Where(x => x.Item1 != null);
  53. var groupList = context.SyntaxProvider.CreateSyntaxProvider(
  54. (x, token) =>
  55. {
  56. return x is TypeDeclarationSyntax type && type.AttributeLists.Count > 0;
  57. }, static (context, cancelToken) =>
  58. {
  59. var method = (TypeDeclarationSyntax)context.Node;
  60. if (!HasCommandAttribute(method, context, cancelToken, Groups))
  61. return null;
  62. var symbol = context.SemanticModel.GetDeclaredSymbol(method, cancelToken);
  63. if (symbol is ITypeSymbol methodSymbol)
  64. {
  65. return methodSymbol.ToDisplayString();
  66. }
  67. else
  68. {
  69. return null;
  70. }
  71. }).Where(x => x != null);
  72. context.RegisterSourceOutput(commandList.Collect(), static (context, methodNames) =>
  73. {
  74. var code = new StringBuilder(
  75. @"namespace PixiEditor.Models.Commands;
  76. internal partial class CommandNameList {
  77. partial void AddCommands() {");
  78. List<string> createdClasses = new List<string>();
  79. foreach (var method in methodNames)
  80. {
  81. if (!createdClasses.Contains(method.Item1))
  82. {
  83. code.AppendLine($" Commands.Add(typeof({method.Item1}), new());");
  84. createdClasses.Add(method.Item1);
  85. }
  86. var parameters = string.Join(",", method.Item3.Select(x => $"typeof({x})"));
  87. bool hasParameters = parameters.Length > 0;
  88. string paramString = hasParameters ? $"new Type[] {{ {parameters} }}" : "Array.Empty<Type>()";
  89. code.AppendLine($" Commands[typeof({method.Item1})].Add((\"{method.Item2}\", {paramString}));");
  90. }
  91. code.Append(" }\n}");
  92. context.AddSource("CommandNameList+Commands", code.ToString());
  93. });
  94. context.RegisterSourceOutput(evaluatorList.Collect(), static (context, methodNames) =>
  95. {
  96. var code = new StringBuilder(
  97. @"namespace PixiEditor.Models.Commands;
  98. internal partial class CommandNameList {
  99. partial void AddEvaluators() {");
  100. List<string> createdClasses = new List<string>();
  101. foreach (var method in methodNames)
  102. {
  103. if (!createdClasses.Contains(method.Item1))
  104. {
  105. code.AppendLine($" Evaluators.Add(typeof({method.Item1}), new());");
  106. createdClasses.Add(method.Item1);
  107. }
  108. if (method.Item3 == null || !method.Item3.Any())
  109. {
  110. code.AppendLine($" Evaluators[typeof({method.Item1})].Add((\"{method.Item2}\", Array.Empty<Type>()));");
  111. }
  112. else
  113. {
  114. var parameters = string.Join(",", method.Item3.Select(x => $"typeof({x})"));
  115. string paramString = parameters.Length > 0 ? $"new Type[] {{ {parameters} }}" : "Array.Empty<Type>()";
  116. code.AppendLine($" Evaluators[typeof({method.Item1})].Add((\"{method.Item2}\", {paramString}));");
  117. }
  118. }
  119. code.Append(" }\n}");
  120. context.AddSource("CommandNameList+Evaluators", code.ToString());
  121. });
  122. context.RegisterSourceOutput(groupList.Collect(), static (context, typeNames) =>
  123. {
  124. var code = new StringBuilder(
  125. @"namespace PixiEditor.Models.Commands;
  126. internal partial class CommandNameList {
  127. partial void AddGroups() {");
  128. foreach (var name in typeNames)
  129. {
  130. code.AppendLine($" Groups.Add(typeof({name}));");
  131. }
  132. code.Append(" }\n}");
  133. context.AddSource("CommandNameList+Groups", code.ToString());
  134. });
  135. }
  136. private static bool HasCommandAttribute(MemberDeclarationSyntax declaration, GeneratorSyntaxContext context, CancellationToken token, string commandAttributeStart)
  137. {
  138. foreach (var attrList in declaration.AttributeLists)
  139. {
  140. foreach (var attribute in attrList.Attributes)
  141. {
  142. token.ThrowIfCancellationRequested();
  143. var symbol = context.SemanticModel.GetSymbolInfo(attribute, token);
  144. if (symbol.Symbol is not IMethodSymbol methodSymbol)
  145. continue;
  146. if (!methodSymbol.ContainingType.ToDisplayString()
  147. .StartsWith(commandAttributeStart))
  148. continue;
  149. return true;
  150. }
  151. }
  152. return false;
  153. }
  154. }