C#: Add a Roslyn analyzer for global classes

Co-Authored-By: Raul Santos <raulsntos@gmail.com>
This commit is contained in:
398utubzyt 2023-07-03 19:35:54 -07:00
parent cdd2313ba2
commit 8e56c807cc
3 changed files with 104 additions and 2 deletions

View file

@ -384,5 +384,65 @@ namespace Godot.SourceGenerators
typeArgumentSyntax.GetLocation(),
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));
}
}
}

View file

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

View file

@ -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);
}
}
}