Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#: Add a Roslyn analyzer for global classes #79007

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -384,5 +384,65 @@ public static void ReportTypeArgumentParentSymbolUnhandled(
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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyNa
return godotClassName ?? nativeType.Name;
}

private static bool IsGodotScriptClass(
private static bool TryGetGodotScriptClass(
this ClassDeclarationSyntax cds, Compilation compilation,
out INamedTypeSymbol? symbol
)
Expand All @@ -108,7 +108,7 @@ Compilation compilation
{
foreach (var cds in source)
{
if (cds.IsGodotScriptClass(compilation, out var symbol))
if (cds.TryGetGodotScriptClass(compilation, out var symbol))
yield return (cds, symbol!);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}