|
@@ -0,0 +1,112 @@
|
|
|
+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<DiagnosticDescriptor> 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<string> 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<ClassDeclarationSyntax>()
|
|
|
+ .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<Document> 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|