ClassPartialModifierAnalyzer.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. using System.Collections.Immutable;
  2. using System.Linq;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using Microsoft.CodeAnalysis;
  6. using Microsoft.CodeAnalysis.CodeActions;
  7. using Microsoft.CodeAnalysis.CodeFixes;
  8. using Microsoft.CodeAnalysis.CSharp;
  9. using Microsoft.CodeAnalysis.CSharp.Syntax;
  10. using Microsoft.CodeAnalysis.Diagnostics;
  11. namespace Godot.SourceGenerators
  12. {
  13. [DiagnosticAnalyzer(LanguageNames.CSharp)]
  14. public sealed class ClassPartialModifierAnalyzer : DiagnosticAnalyzer
  15. {
  16. public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
  17. ImmutableArray.Create(Common.ClassPartialModifierRule, Common.OuterClassPartialModifierRule);
  18. public override void Initialize(AnalysisContext context)
  19. {
  20. context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
  21. context.EnableConcurrentExecution();
  22. context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
  23. }
  24. private void AnalyzeNode(SyntaxNodeAnalysisContext context)
  25. {
  26. if (context.Node is not ClassDeclarationSyntax classDeclaration)
  27. return;
  28. if (context.ContainingSymbol is not INamedTypeSymbol typeSymbol)
  29. return;
  30. if (!typeSymbol.InheritsFrom("GodotSharp", GodotClasses.GodotObject))
  31. return;
  32. if (!classDeclaration.IsPartial())
  33. context.ReportDiagnostic(Diagnostic.Create(
  34. Common.ClassPartialModifierRule,
  35. classDeclaration.Identifier.GetLocation(),
  36. typeSymbol.ToDisplayString()));
  37. var outerClassDeclaration = context.Node.Parent as ClassDeclarationSyntax;
  38. while (outerClassDeclaration is not null)
  39. {
  40. var outerClassTypeSymbol = context.SemanticModel.GetDeclaredSymbol(outerClassDeclaration);
  41. if (outerClassTypeSymbol == null)
  42. return;
  43. if (!outerClassDeclaration.IsPartial())
  44. context.ReportDiagnostic(Diagnostic.Create(
  45. Common.OuterClassPartialModifierRule,
  46. outerClassDeclaration.Identifier.GetLocation(),
  47. outerClassTypeSymbol.ToDisplayString()));
  48. outerClassDeclaration = outerClassDeclaration.Parent as ClassDeclarationSyntax;
  49. }
  50. }
  51. }
  52. [ExportCodeFixProvider(LanguageNames.CSharp)]
  53. public sealed class ClassPartialModifierCodeFixProvider : CodeFixProvider
  54. {
  55. public override ImmutableArray<string> FixableDiagnosticIds =>
  56. ImmutableArray.Create(Common.ClassPartialModifierRule.Id);
  57. public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
  58. public override async Task RegisterCodeFixesAsync(CodeFixContext context)
  59. {
  60. // Get the syntax root of the document.
  61. var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
  62. // Get the diagnostic to fix.
  63. var diagnostic = context.Diagnostics.First();
  64. // Get the location of code issue.
  65. var diagnosticSpan = diagnostic.Location.SourceSpan;
  66. // Use that location to find the containing class declaration.
  67. var classDeclaration = root?.FindToken(diagnosticSpan.Start)
  68. .Parent?
  69. .AncestorsAndSelf()
  70. .OfType<ClassDeclarationSyntax>()
  71. .First();
  72. if (classDeclaration == null)
  73. return;
  74. context.RegisterCodeFix(
  75. CodeAction.Create(
  76. "Add partial modifier",
  77. cancellationToken => AddPartialModifierAsync(context.Document, classDeclaration, cancellationToken),
  78. classDeclaration.ToFullString()),
  79. context.Diagnostics);
  80. }
  81. private static async Task<Document> AddPartialModifierAsync(Document document,
  82. ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken)
  83. {
  84. // Create a new partial modifier.
  85. var partialModifier = SyntaxFactory.Token(SyntaxKind.PartialKeyword);
  86. var modifiedClassDeclaration = classDeclaration.AddModifiers(partialModifier);
  87. var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
  88. // Replace the old class declaration with the modified one in the syntax root.
  89. var newRoot = root!.ReplaceNode(classDeclaration, modifiedClassDeclaration);
  90. var newDocument = document.WithSyntaxRoot(newRoot);
  91. return newDocument;
  92. }
  93. }
  94. }