using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Godot.SourceGenerators { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassPartialModifierAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Common.ClassPartialModifierRule, Common.OuterClassPartialModifierRule); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); } private void AnalyzeNode(SyntaxNodeAnalysisContext context) { if (context.Node is not ClassDeclarationSyntax classDeclaration) return; if (context.ContainingSymbol is not INamedTypeSymbol typeSymbol) return; if (!typeSymbol.InheritsFrom("GodotSharp", GodotClasses.GodotObject)) return; if (!classDeclaration.IsPartial()) context.ReportDiagnostic(Diagnostic.Create( Common.ClassPartialModifierRule, classDeclaration.Identifier.GetLocation(), typeSymbol.ToDisplayString())); var outerClassDeclaration = context.Node.Parent as ClassDeclarationSyntax; while (outerClassDeclaration is not null) { var outerClassTypeSymbol = context.SemanticModel.GetDeclaredSymbol(outerClassDeclaration); if (outerClassTypeSymbol == null) return; if (!outerClassDeclaration.IsPartial()) context.ReportDiagnostic(Diagnostic.Create( Common.OuterClassPartialModifierRule, outerClassDeclaration.Identifier.GetLocation(), outerClassTypeSymbol.ToDisplayString())); outerClassDeclaration = outerClassDeclaration.Parent as ClassDeclarationSyntax; } } } [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class ClassPartialModifierCodeFixProvider : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(Common.ClassPartialModifierRule.Id); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { // Get the syntax root of the document. var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); // Get the diagnostic to fix. var diagnostic = context.Diagnostics.First(); // Get the location of code issue. var diagnosticSpan = diagnostic.Location.SourceSpan; // Use that location to find the containing class declaration. var classDeclaration = root?.FindToken(diagnosticSpan.Start) .Parent? .AncestorsAndSelf() .OfType() .First(); if (classDeclaration == null) return; context.RegisterCodeFix( CodeAction.Create( "Add partial modifier", cancellationToken => AddPartialModifierAsync(context.Document, classDeclaration, cancellationToken), classDeclaration.ToFullString()), context.Diagnostics); } private static async Task AddPartialModifierAsync(Document document, ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken) { // Create a new partial modifier. var partialModifier = SyntaxFactory.Token(SyntaxKind.PartialKeyword); var modifiedClassDeclaration = classDeclaration.AddModifiers(partialModifier); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Replace the old class declaration with the modified one in the syntax root. var newRoot = root!.ReplaceNode(classDeclaration, modifiedClassDeclaration); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; } } }