You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-07 12:30:27 +00:00
C#: Add a Roslyn analyzer for global classes
Co-Authored-By: Raul Santos <raulsntos@gmail.com>
This commit is contained in:
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user