Merge pull request #79007 from 398utubzyt/dotnet/globalclass-analyzer
C#: Add a Roslyn analyzer for global classes
This commit is contained in:
commit
bb6879e7cc
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user