virtualx-engine/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
Raul Santos aa4dba5b56
C#: Improve diagnostic messages and add help link
- Reword diagnostic messages.
- Add help link to diagnostics.
2023-10-17 19:45:09 +02:00

471 lines
21 KiB
C#

using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Godot.SourceGenerators
{
public static partial class Common
{
private static readonly string _helpLinkFormat = $"{VersionDocsUrl}/tutorials/scripting/c_sharp/diagnostics/{{0}}.html";
public static void ReportNonPartialGodotScriptClass(
GeneratorExecutionContext context,
ClassDeclarationSyntax cds, INamedTypeSymbol symbol
)
{
string message =
"Missing partial modifier on declaration of type '" +
$"{symbol.FullQualifiedNameOmitGlobal()}' that derives from '{GodotClasses.GodotObject}'";
string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' " +
"must be declared with the partial modifier.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0001",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0001")),
cds.GetLocation(),
cds.SyntaxTree.FilePath));
}
public static void ReportNonPartialGodotScriptOuterClass(
GeneratorExecutionContext context,
TypeDeclarationSyntax outerTypeDeclSyntax
)
{
var outerSymbol = context.Compilation
.GetSemanticModel(outerTypeDeclSyntax.SyntaxTree)
.GetDeclaredSymbol(outerTypeDeclSyntax);
string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ?
namedTypeSymbol.FullQualifiedNameOmitGlobal() :
"type not found";
string message =
$"Missing partial modifier on declaration of type '{fullQualifiedName}', " +
$"which contains nested classes that derive from '{GodotClasses.GodotObject}'";
string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' and their " +
"containing types must be declared with the partial modifier.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0002",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0002")),
outerTypeDeclSyntax.GetLocation(),
outerTypeDeclSyntax.SyntaxTree.FilePath));
}
public static void ReportExportedMemberIsStatic(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
bool isField = exportedMemberSymbol is IFieldSymbol;
string message = $"Attempted to export static {(isField ? "field" : "property")}: " +
$"'{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Only instance fields and properties can be exported." +
" Remove the 'static' modifier or the '[Export]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0101",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0101")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberTypeNotSupported(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
bool isField = exportedMemberSymbol is IFieldSymbol;
string message = $"The type of the exported {(isField ? "field" : "property")} " +
$"is not supported: '{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Use a supported type or remove the '[Export]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0102",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0102")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberIsReadOnly(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
bool isField = exportedMemberSymbol is IFieldSymbol;
string message = $"The exported {(isField ? "field" : "property")} " +
$"is read-only: '{exportedMemberSymbol.ToDisplayString()}'";
string description = isField ?
$"{message}. Exported fields cannot be read-only." :
$"{message}. Exported properties must be writable.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0103",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD1003")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberIsWriteOnly(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = $"The exported property is write-only: '{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Exported properties must be readable.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0104",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0104")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberIsIndexer(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = $"Attempted to export indexer property: " +
$"'{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Indexer properties can't be exported." +
" Remove the '[Export]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0105",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0105")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberIsExplicitInterfaceImplementation(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = $"Attempted to export explicit interface property implementation: " +
$"'{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Explicit interface implementations can't be exported." +
" Remove the '[Export]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0106",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0106")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportSignalDelegateMissingSuffix(
GeneratorExecutionContext context,
INamedTypeSymbol delegateSymbol)
{
var locations = delegateSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = "The name of the delegate must end with 'EventHandler': " +
delegateSymbol.ToDisplayString() +
$". Did you mean '{delegateSymbol.Name}EventHandler'?";
string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0201",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0201")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportSignalParameterTypeNotSupported(
GeneratorExecutionContext context,
IParameterSymbol parameterSymbol)
{
var locations = parameterSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = "The parameter of the delegate signature of the signal " +
$"is not supported: '{parameterSymbol.ToDisplayString()}'";
string description = $"{message}. Use supported types only or remove the '[Signal]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0202",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0202")),
location,
location?.SourceTree?.FilePath));
}
public static void ReportSignalDelegateSignatureMustReturnVoid(
GeneratorExecutionContext context,
INamedTypeSymbol delegateSymbol)
{
var locations = delegateSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = "The delegate signature of the signal " +
$"must return void: '{delegateSymbol.ToDisplayString()}'";
string description = $"{message}. Return void or remove the '[Signal]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0203",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0203")),
location,
location?.SourceTree?.FilePath));
}
public static readonly DiagnosticDescriptor GenericTypeArgumentMustBeVariantRule =
new DiagnosticDescriptor(id: "GD0301",
title: "The generic type argument must be a Variant compatible type",
messageFormat: "The generic type argument must be a Variant compatible type: {0}",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0301"));
public static void ReportGenericTypeArgumentMustBeVariant(
SyntaxNodeAnalysisContext context,
SyntaxNode typeArgumentSyntax,
ISymbol typeArgumentSymbol)
{
string message = "The generic type argument " +
$"must be a Variant compatible type: '{typeArgumentSymbol.ToDisplayString()}'";
string description = $"{message}. Use a Variant compatible type as the generic type argument.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0301",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0301")),
typeArgumentSyntax.GetLocation(),
typeArgumentSyntax.SyntaxTree.FilePath));
}
public static readonly DiagnosticDescriptor GenericTypeParameterMustBeVariantAnnotatedRule =
new DiagnosticDescriptor(id: "GD0302",
title: "The generic type parameter must be annotated with the MustBeVariant attribute",
messageFormat: "The generic type argument must be a Variant type: {0}",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The generic type argument must be a Variant type. Use a Variant type as the generic type argument.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0302"));
public static void ReportGenericTypeParameterMustBeVariantAnnotated(
SyntaxNodeAnalysisContext context,
SyntaxNode typeArgumentSyntax,
ISymbol typeArgumentSymbol)
{
string message = "The generic type parameter must be annotated with the MustBeVariant attribute";
string description = $"{message}. Add the MustBeVariant attribute to the generic type parameter.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0302",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0302")),
typeArgumentSyntax.GetLocation(),
typeArgumentSyntax.SyntaxTree.FilePath));
}
public static readonly DiagnosticDescriptor TypeArgumentParentSymbolUnhandledRule =
new DiagnosticDescriptor(id: "GD0303",
title: "The generic type parameter must be annotated with the MustBeVariant attribute",
messageFormat: "The generic type argument must be a Variant type: {0}",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The generic type argument must be a Variant type. Use a Variant type as the generic type argument.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0303"));
public static void ReportTypeArgumentParentSymbolUnhandled(
SyntaxNodeAnalysisContext context,
SyntaxNode typeArgumentSyntax,
ISymbol parentSymbol)
{
string message = $"Symbol '{parentSymbol.ToDisplayString()}' parent of a type argument " +
"that must be Variant compatible was not handled.";
string description = $"{message}. Handle type arguments that are children of the unhandled symbol type.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GD0303",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description,
helpLinkUri: string.Format(_helpLinkFormat, "GD0303")),
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.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0401"));
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,
helpLinkUri: string.Format(_helpLinkFormat, "GD0401")),
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.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0401"));
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,
helpLinkUri: string.Format(_helpLinkFormat, "GD0402")),
classSyntax.GetLocation(),
classSyntax.SyntaxTree.FilePath));
}
}
}