Browse Source

C#: Add a Roslyn analyzer for global classes

Co-Authored-By: Raul Santos <[email protected]>
398utubzyt 2 years ago
parent
commit
8e56c807cc

+ 60 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs

@@ -384,5 +384,65 @@ namespace Godot.SourceGenerators
                 typeArgumentSyntax.GetLocation(),
                 typeArgumentSyntax.GetLocation(),
                 typeArgumentSyntax.SyntaxTree.FilePath));
                 typeArgumentSyntax.SyntaxTree.FilePath));
         }
         }
+
+        public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule =
+            new DiagnosticDescriptor(id: "GD0401",
+                title: "The class must derive from GodotObject or a derived class",
+                messageFormat: "The class '{0}' must derive from GodotObject or a derived class.",
+                category: "Usage",
+                DiagnosticSeverity.Error,
+                isEnabledByDefault: true,
+                "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute.");
+
+        public static void ReportGlobalClassMustDeriveFromGodotObject(
+            SyntaxNodeAnalysisContext context,
+            SyntaxNode classSyntax,
+            ISymbol typeSymbol)
+        {
+            string message = $"The class '{typeSymbol.ToDisplayString()}' must derive from GodotObject or a derived class";
+
+            string description = $"{message}. Change the base class or remove the '[GlobalClass]' attribute.";
+
+            context.ReportDiagnostic(Diagnostic.Create(
+                new DiagnosticDescriptor(id: "GD0401",
+                    title: message,
+                    messageFormat: message,
+                    category: "Usage",
+                    DiagnosticSeverity.Error,
+                    isEnabledByDefault: true,
+                    description),
+                classSyntax.GetLocation(),
+                classSyntax.SyntaxTree.FilePath));
+        }
+
+        public static readonly DiagnosticDescriptor GlobalClassMustNotBeGenericRule =
+            new DiagnosticDescriptor(id: "GD0402",
+                title: "The class must not contain generic arguments",
+                messageFormat: "The class '{0}' must not contain generic arguments",
+                category: "Usage",
+                DiagnosticSeverity.Error,
+                isEnabledByDefault: true,
+                "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute.");
+
+        public static void ReportGlobalClassMustNotBeGeneric(
+            SyntaxNodeAnalysisContext context,
+            SyntaxNode classSyntax,
+            ISymbol typeSymbol)
+        {
+            string message = $"The class '{typeSymbol.ToDisplayString()}' must not contain generic arguments";
+
+            string description = $"{message}. Remove the generic arguments or the '[GlobalClass]' attribute.";
+
+            context.ReportDiagnostic(Diagnostic.Create(
+                new DiagnosticDescriptor(id: "GD0402",
+                    title: message,
+                    messageFormat: message,
+                    category: "Usage",
+                    DiagnosticSeverity.Error,
+                    isEnabledByDefault: true,
+                    description),
+                classSyntax.GetLocation(),
+                classSyntax.SyntaxTree.FilePath));
+        }
     }
     }
 }
 }

+ 2 - 2
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs

@@ -81,7 +81,7 @@ namespace Godot.SourceGenerators
             return godotClassName ?? nativeType.Name;
             return godotClassName ?? nativeType.Name;
         }
         }
 
 
-        private static bool IsGodotScriptClass(
+        private static bool TryGetGodotScriptClass(
             this ClassDeclarationSyntax cds, Compilation compilation,
             this ClassDeclarationSyntax cds, Compilation compilation,
             out INamedTypeSymbol? symbol
             out INamedTypeSymbol? symbol
         )
         )
@@ -108,7 +108,7 @@ namespace Godot.SourceGenerators
         {
         {
             foreach (var cds in source)
             foreach (var cds in source)
             {
             {
-                if (cds.IsGodotScriptClass(compilation, out var symbol))
+                if (cds.TryGetGodotScriptClass(compilation, out var symbol))
                     yield return (cds, symbol!);
                     yield return (cds, symbol!);
             }
             }
         }
         }

+ 42 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs

@@ -0,0 +1,42 @@
+using System.Collections.Immutable;
+using System.Linq;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Godot.SourceGenerators
+{
+    [DiagnosticAnalyzer(LanguageNames.CSharp)]
+    public class GlobalClassAnalyzer : DiagnosticAnalyzer
+    {
+        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
+            => ImmutableArray.Create(
+                Common.GlobalClassMustDeriveFromGodotObjectRule,
+                Common.GlobalClassMustNotBeGenericRule);
+
+        public override void Initialize(AnalysisContext context)
+        {
+            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+            context.EnableConcurrentExecution();
+            context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
+        }
+
+        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
+        {
+            var typeClassDecl = (ClassDeclarationSyntax)context.Node;
+
+            // Return if not a type symbol or the type is not a global class.
+            if (context.ContainingSymbol is not INamedTypeSymbol typeSymbol ||
+                !typeSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotGlobalClassAttribute() ?? false))
+                return;
+
+            if (typeSymbol.IsGenericType)
+                Common.ReportGlobalClassMustNotBeGeneric(context, typeClassDecl, typeSymbol);
+
+            if (!typeSymbol.InheritsFrom("GodotSharp", GodotClasses.GodotObject))
+                Common.ReportGlobalClassMustDeriveFromGodotObject(context, typeClassDecl, typeSymbol);
+        }
+    }
+}