Skip to content

Commit

Permalink
Merge pull request #79007 from 398utubzyt/dotnet/globalclass-analyzer
Browse files Browse the repository at this point in the history
C#: Add a Roslyn analyzer for global classes
  • Loading branch information
YuriSizov committed Jul 14, 2023
2 parents 4adc3e1 + 8e56c80 commit bb6879e
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 2 deletions.
60 changes: 60 additions & 0 deletions modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
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);
}
}
}

0 comments on commit bb6879e

Please sign in to comment.