EntitySystemCodeFixProvider.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. using System;
  2. using System.Collections.Immutable;
  3. using System.Composition;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Microsoft.CodeAnalysis;
  8. using Microsoft.CodeAnalysis.CodeActions;
  9. using Microsoft.CodeAnalysis.CodeFixes;
  10. using Microsoft.CodeAnalysis.CSharp;
  11. using Microsoft.CodeAnalysis.CSharp.Syntax;
  12. using Microsoft.CodeAnalysis.Diagnostics;
  13. using Microsoft.CodeAnalysis.Editing;
  14. using Microsoft.CodeAnalysis.Formatting;
  15. using Microsoft.CodeAnalysis.Options;
  16. using Microsoft.CodeAnalysis.Simplification;
  17. namespace ET.Analyzer;
  18. [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(EntitySystemCodeFixProvider)), Shared]
  19. public class EntitySystemCodeFixProvider:CodeFixProvider
  20. {
  21. public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIds.EntitySystemAnalyzerRuleId);
  22. public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
  23. public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
  24. {
  25. SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
  26. Diagnostic diagnostic = context.Diagnostics.First();
  27. Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;
  28. ClassDeclarationSyntax? classDeclaration = root?.FindToken(diagnosticSpan.Start).Parent?.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().First();
  29. CodeAction codeAction = CodeAction.Create(
  30. "Generate Entity System",
  31. cancelToken => GenerateEntitySystemAsync(context.Document,classDeclaration,diagnostic,cancelToken),
  32. equivalenceKey: nameof(EntitySystemCodeFixProvider));
  33. context.RegisterCodeFix(codeAction,diagnostic);
  34. }
  35. private static async Task<Document> GenerateEntitySystemAsync(Document document, ClassDeclarationSyntax? classDeclaration,Diagnostic diagnostic,
  36. CancellationToken cancellationToken)
  37. {
  38. await Task.CompletedTask;
  39. SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
  40. ImmutableDictionary<string, string?> properties = diagnostic.Properties;
  41. if (classDeclaration==null || root==null)
  42. {
  43. return document;
  44. }
  45. var newMembers = new SyntaxList<MemberDeclarationSyntax>();
  46. string? seuqenceStr = properties[Definition.EntitySystemInterfaceSequence];
  47. if (seuqenceStr==null)
  48. {
  49. return document;
  50. }
  51. string[] sequenceArr = seuqenceStr.Split('/');
  52. for (int i = 0; i < sequenceArr.Length; i++)
  53. {
  54. string methodName = sequenceArr[i];
  55. string? methodArgs = properties[methodName];
  56. if (methodArgs==null)
  57. {
  58. continue;
  59. }
  60. var methodSyntax = CreateEntitySystemMethodSyntax(methodName, methodArgs);
  61. if (methodSyntax!=null)
  62. {
  63. newMembers = newMembers.Add(methodSyntax);
  64. }
  65. else
  66. {
  67. throw new Exception("methodSyntax==null");
  68. }
  69. }
  70. if (newMembers.Count==0)
  71. {
  72. throw new Exception("newMembers.Count==0");
  73. }
  74. var newClassDeclaration = classDeclaration.WithMembers(classDeclaration.Members.AddRange(newMembers)).WithAdditionalAnnotations(Formatter.Annotation);
  75. document = document.WithSyntaxRoot(root.ReplaceNode(classDeclaration, newClassDeclaration));
  76. document = await CleanupDocumentAsync(document, cancellationToken);
  77. return document;
  78. }
  79. private static MethodDeclarationSyntax? CreateEntitySystemMethodSyntax(string methodName,string methodArgs)
  80. {
  81. string[] methodNameArr = methodName.Split('`');
  82. string[] methodArgsArr = methodArgs.Split('/');
  83. string systemAttr = methodArgsArr[1];
  84. string args = String.Empty;
  85. if (methodArgsArr.Length>2)
  86. {
  87. for (int i = 2; i < methodArgsArr.Length; i++)
  88. {
  89. args += $", {methodArgsArr[i]} args{i}";
  90. }
  91. }
  92. string code = $$"""
  93. [{{systemAttr}}]
  94. private static void {{methodNameArr[0]}}(this {{methodArgsArr[0]}} self{{args}})
  95. {
  96. }
  97. """;
  98. return SyntaxFactory.ParseMemberDeclaration(code) as MethodDeclarationSyntax;
  99. }
  100. internal static async Task<Document> CleanupDocumentAsync(
  101. Document document, CancellationToken cancellationToken)
  102. {
  103. if (document.SupportsSyntaxTree)
  104. {
  105. document = await ImportAdder.AddImportsAsync(
  106. document, Simplifier.AddImportsAnnotation, cancellationToken: cancellationToken).ConfigureAwait(false);
  107. document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
  108. // format any node with explicit formatter annotation
  109. document = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
  110. // format any elastic whitespace
  111. document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, cancellationToken: cancellationToken).ConfigureAwait(false);
  112. }
  113. return document;
  114. }
  115. }