2021-12-28 23:25:16 +01:00
using System ;
2021-03-06 00:12:42 +01:00
using System.Collections.Generic ;
2022-02-27 21:57:30 +01:00
using System.Collections.Immutable ;
2021-03-06 00:12:42 +01:00
using System.Linq ;
2022-11-12 04:34:43 +01:00
using System.Text ;
2021-03-06 00:12:42 +01:00
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
namespace Godot.SourceGenerators
{
2024-02-19 22:15:37 +01:00
internal static class ExtensionMethods
2021-03-06 00:12:42 +01:00
{
public static bool TryGetGlobalAnalyzerProperty (
this GeneratorExecutionContext context , string property , out string? value
) = > context . AnalyzerConfigOptions . GlobalOptions
. TryGetValue ( "build_property." + property , out value ) ;
2021-12-28 23:25:16 +01:00
public static bool AreGodotSourceGeneratorsDisabled ( this GeneratorExecutionContext context )
= > context . TryGetGlobalAnalyzerProperty ( "GodotSourceGenerators" , out string? toggle ) & &
toggle ! = null & &
toggle . Equals ( "disabled" , StringComparison . OrdinalIgnoreCase ) ;
2021-12-28 23:25:16 +01:00
public static bool IsGodotToolsProject ( this GeneratorExecutionContext context )
= > context . TryGetGlobalAnalyzerProperty ( "IsGodotToolsProject" , out string? toggle ) & &
toggle ! = null & &
toggle . Equals ( "true" , StringComparison . OrdinalIgnoreCase ) ;
2023-01-08 02:04:15 +01:00
public static bool IsGodotSourceGeneratorDisabled ( this GeneratorExecutionContext context , string generatorName ) = >
AreGodotSourceGeneratorsDisabled ( context ) | |
( context . TryGetGlobalAnalyzerProperty ( "GodotDisabledSourceGenerators" , out string? disabledGenerators ) & &
disabledGenerators ! = null & &
disabledGenerators . Split ( ';' ) . Contains ( generatorName ) ) ;
2023-10-06 18:12:16 +02:00
public static bool InheritsFrom ( this ITypeSymbol ? symbol , string assemblyName , string typeFullName )
2021-03-06 00:12:42 +01:00
{
2022-02-27 21:57:30 +01:00
while ( symbol ! = null )
2021-03-06 00:12:42 +01:00
{
2022-09-22 12:41:38 +02:00
if ( symbol . ContainingAssembly ? . Name = = assemblyName & &
2023-07-06 12:43:23 +02:00
symbol . FullQualifiedNameOmitGlobal ( ) = = typeFullName )
2021-03-06 00:12:42 +01:00
{
return true ;
}
2022-02-27 21:57:30 +01:00
symbol = symbol . BaseType ;
2021-03-06 00:12:42 +01:00
}
return false ;
}
2022-02-27 21:57:30 +01:00
public static INamedTypeSymbol ? GetGodotScriptNativeClass ( this INamedTypeSymbol classTypeSymbol )
{
var symbol = classTypeSymbol ;
while ( symbol ! = null )
{
2022-09-22 12:41:38 +02:00
if ( symbol . ContainingAssembly ? . Name = = "GodotSharp" )
2022-02-27 21:57:30 +01:00
return symbol ;
symbol = symbol . BaseType ;
}
return null ;
}
public static string? GetGodotScriptNativeClassName ( this INamedTypeSymbol classTypeSymbol )
{
var nativeType = classTypeSymbol . GetGodotScriptNativeClass ( ) ;
if ( nativeType = = null )
return null ;
var godotClassNameAttr = nativeType . GetAttributes ( )
. FirstOrDefault ( a = > a . AttributeClass ? . IsGodotClassNameAttribute ( ) ? ? false ) ;
string? godotClassName = null ;
if ( godotClassNameAttr is { ConstructorArguments : { Length : > 0 } } )
godotClassName = godotClassNameAttr . ConstructorArguments [ 0 ] . Value ? . ToString ( ) ;
return godotClassName ? ? nativeType . Name ;
}
2023-07-04 04:35:54 +02:00
private static bool TryGetGodotScriptClass (
2021-03-06 00:12:42 +01:00
this ClassDeclarationSyntax cds , Compilation compilation ,
out INamedTypeSymbol ? symbol
)
{
var sm = compilation . GetSemanticModel ( cds . SyntaxTree ) ;
var classTypeSymbol = sm . GetDeclaredSymbol ( cds ) ;
if ( classTypeSymbol ? . BaseType = = null
2022-12-07 16:16:51 +01:00
| | ! classTypeSymbol . BaseType . InheritsFrom ( "GodotSharp" , GodotClasses . GodotObject ) )
2021-03-06 00:12:42 +01:00
{
symbol = null ;
return false ;
}
symbol = classTypeSymbol ;
return true ;
}
public static IEnumerable < ( ClassDeclarationSyntax cds , INamedTypeSymbol symbol ) > SelectGodotScriptClasses (
this IEnumerable < ClassDeclarationSyntax > source ,
Compilation compilation
)
{
foreach ( var cds in source )
{
2023-07-04 04:35:54 +02:00
if ( cds . TryGetGodotScriptClass ( compilation , out var symbol ) )
2021-03-06 00:12:42 +01:00
yield return ( cds , symbol ! ) ;
}
}
2021-12-28 23:25:16 +01:00
public static bool IsNested ( this TypeDeclarationSyntax cds )
= > cds . Parent is TypeDeclarationSyntax ;
public static bool IsPartial ( this TypeDeclarationSyntax cds )
2021-03-06 00:12:42 +01:00
= > cds . Modifiers . Any ( SyntaxKind . PartialKeyword ) ;
2021-12-28 23:25:16 +01:00
public static bool AreAllOuterTypesPartial (
this TypeDeclarationSyntax cds ,
out TypeDeclarationSyntax ? typeMissingPartial
)
{
SyntaxNode ? outerSyntaxNode = cds . Parent ;
while ( outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax )
{
if ( ! outerTypeDeclSyntax . IsPartial ( ) )
{
typeMissingPartial = outerTypeDeclSyntax ;
return false ;
}
outerSyntaxNode = outerSyntaxNode . Parent ;
}
typeMissingPartial = null ;
return true ;
}
public static string GetDeclarationKeyword ( this INamedTypeSymbol namedTypeSymbol )
{
string? keyword = namedTypeSymbol . DeclaringSyntaxReferences
. OfType < TypeDeclarationSyntax > ( ) . FirstOrDefault ( ) ?
. Keyword . Text ;
return keyword ? ? namedTypeSymbol . TypeKind switch
{
TypeKind . Interface = > "interface" ,
TypeKind . Struct = > "struct" ,
_ = > "class"
} ;
}
2022-02-27 21:57:30 +01:00
public static string NameWithTypeParameters ( this INamedTypeSymbol symbol )
{
return symbol . IsGenericType ?
string . Concat ( symbol . Name , "<" , string . Join ( ", " , symbol . TypeParameters ) , ">" ) :
symbol . Name ;
}
2022-11-24 01:04:15 +01:00
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get ; } =
SymbolDisplayFormat . FullyQualifiedFormat
. WithGlobalNamespaceStyle ( SymbolDisplayGlobalNamespaceStyle . Omitted ) ;
private static SymbolDisplayFormat FullyQualifiedFormatIncludeGlobal { get ; } =
SymbolDisplayFormat . FullyQualifiedFormat
. WithGlobalNamespaceStyle ( SymbolDisplayGlobalNamespaceStyle . Included ) ;
public static string FullQualifiedNameOmitGlobal ( this ITypeSymbol symbol )
= > symbol . ToDisplayString ( NullableFlowState . NotNull , FullyQualifiedFormatOmitGlobal ) ;
public static string FullQualifiedNameOmitGlobal ( this INamespaceSymbol namespaceSymbol )
2021-03-13 01:04:55 +01:00
= > namespaceSymbol . ToDisplayString ( FullyQualifiedFormatOmitGlobal ) ;
2022-02-27 21:57:30 +01:00
2022-11-24 01:04:15 +01:00
public static string FullQualifiedNameIncludeGlobal ( this ITypeSymbol symbol )
= > symbol . ToDisplayString ( NullableFlowState . NotNull , FullyQualifiedFormatIncludeGlobal ) ;
public static string FullQualifiedNameIncludeGlobal ( this INamespaceSymbol namespaceSymbol )
= > namespaceSymbol . ToDisplayString ( FullyQualifiedFormatIncludeGlobal ) ;
2022-11-12 04:34:43 +01:00
public static string FullQualifiedSyntax ( this SyntaxNode node , SemanticModel sm )
{
StringBuilder sb = new ( ) ;
2022-11-24 01:04:15 +01:00
FullQualifiedSyntax ( node , sm , sb , true ) ;
2022-11-12 04:34:43 +01:00
return sb . ToString ( ) ;
}
2022-11-24 01:04:15 +01:00
private static void FullQualifiedSyntax ( SyntaxNode node , SemanticModel sm , StringBuilder sb , bool isFirstNode )
2022-11-12 04:34:43 +01:00
{
if ( node is NameSyntax ns & & isFirstNode )
{
SymbolInfo nameInfo = sm . GetSymbolInfo ( ns ) ;
2022-11-24 01:04:15 +01:00
sb . Append ( nameInfo . Symbol ? . ToDisplayString ( FullyQualifiedFormatIncludeGlobal ) ? ? ns . ToString ( ) ) ;
2022-11-12 04:34:43 +01:00
return ;
}
bool innerIsFirstNode = true ;
foreach ( var child in node . ChildNodesAndTokens ( ) )
{
if ( child . HasLeadingTrivia )
{
sb . Append ( child . GetLeadingTrivia ( ) ) ;
}
if ( child . IsNode )
{
2022-11-24 01:04:15 +01:00
FullQualifiedSyntax ( child . AsNode ( ) ! , sm , sb , isFirstNode : innerIsFirstNode ) ;
2022-11-12 04:34:43 +01:00
innerIsFirstNode = false ;
}
else
{
sb . Append ( child ) ;
}
if ( child . HasTrailingTrivia )
{
sb . Append ( child . GetTrailingTrivia ( ) ) ;
}
}
}
2022-02-27 21:57:30 +01:00
public static string SanitizeQualifiedNameForUniqueHint ( this string qualifiedName )
= > qualifiedName
// AddSource() doesn't support angle brackets
. Replace ( "<" , "(Of " )
. Replace ( ">" , ")" ) ;
public static bool IsGodotExportAttribute ( this INamedTypeSymbol symbol )
2023-07-06 12:43:23 +02:00
= > symbol . FullQualifiedNameOmitGlobal ( ) = = GodotClasses . ExportAttr ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
public static bool IsGodotSignalAttribute ( this INamedTypeSymbol symbol )
2023-07-06 12:43:23 +02:00
= > symbol . FullQualifiedNameOmitGlobal ( ) = = GodotClasses . SignalAttr ;
2022-07-28 17:41:47 +02:00
2022-08-15 05:57:52 +02:00
public static bool IsGodotMustBeVariantAttribute ( this INamedTypeSymbol symbol )
2023-07-06 12:43:23 +02:00
= > symbol . FullQualifiedNameOmitGlobal ( ) = = GodotClasses . MustBeVariantAttr ;
2022-08-15 05:57:52 +02:00
2022-02-27 21:57:30 +01:00
public static bool IsGodotClassNameAttribute ( this INamedTypeSymbol symbol )
2023-07-06 12:43:23 +02:00
= > symbol . FullQualifiedNameOmitGlobal ( ) = = GodotClasses . GodotClassNameAttr ;
2022-02-27 21:57:30 +01:00
2023-02-02 00:54:16 +01:00
public static bool IsGodotGlobalClassAttribute ( this INamedTypeSymbol symbol )
2023-07-06 12:43:23 +02:00
= > symbol . FullQualifiedNameOmitGlobal ( ) = = GodotClasses . GlobalClassAttr ;
2023-02-02 00:54:16 +01:00
2022-02-27 21:57:30 +01:00
public static bool IsSystemFlagsAttribute ( this INamedTypeSymbol symbol )
2023-07-06 12:43:23 +02:00
= > symbol . FullQualifiedNameOmitGlobal ( ) = = GodotClasses . SystemFlagsAttr ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
public static GodotMethodData ? HasGodotCompatibleSignature (
this IMethodSymbol method ,
2022-02-27 21:57:30 +01:00
MarshalUtils . TypeCache typeCache
)
{
2022-07-28 17:41:47 +02:00
if ( method . IsGenericMethod )
return null ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
var retSymbol = method . ReturnType ;
var retType = method . ReturnsVoid ?
null :
MarshalUtils . ConvertManagedTypeToMarshalType ( method . ReturnType , typeCache ) ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
if ( retType = = null & & ! method . ReturnsVoid )
return null ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
var parameters = method . Parameters ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
var paramTypes = parameters
// Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may)
. Where ( p = > p . RefKind = = RefKind . None )
// Attempt to determine the variant type
. Select ( p = > MarshalUtils . ConvertManagedTypeToMarshalType ( p . Type , typeCache ) )
// Discard parameter types that couldn't be determined (null entries)
. Where ( t = > t ! = null ) . Cast < MarshalType > ( ) . ToImmutableArray ( ) ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
// If any parameter type was incompatible, it was discarded so the length won't match
if ( parameters . Length > paramTypes . Length )
return null ; // Ignore incompatible method
2022-12-01 01:45:11 +01:00
return new GodotMethodData ( method , paramTypes ,
parameters . Select ( p = > p . Type ) . ToImmutableArray ( ) ,
retType ! = null ? ( retType . Value , retSymbol ) : null ) ;
2022-07-28 17:41:47 +02:00
}
public static IEnumerable < GodotMethodData > WhereHasGodotCompatibleSignature (
this IEnumerable < IMethodSymbol > methods ,
MarshalUtils . TypeCache typeCache
)
{
foreach ( var method in methods )
{
var methodData = HasGodotCompatibleSignature ( method , typeCache ) ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
if ( methodData ! = null )
yield return methodData . Value ;
2022-02-27 21:57:30 +01:00
}
}
public static IEnumerable < GodotPropertyData > WhereIsGodotCompatibleType (
this IEnumerable < IPropertySymbol > properties ,
MarshalUtils . TypeCache typeCache
)
{
foreach ( var property in properties )
{
var marshalType = MarshalUtils . ConvertManagedTypeToMarshalType ( property . Type , typeCache ) ;
if ( marshalType = = null )
continue ;
yield return new GodotPropertyData ( property , marshalType . Value ) ;
}
}
public static IEnumerable < GodotFieldData > WhereIsGodotCompatibleType (
this IEnumerable < IFieldSymbol > fields ,
MarshalUtils . TypeCache typeCache
)
{
foreach ( var field in fields )
{
2022-05-28 04:56:46 +02:00
// TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
2022-02-27 21:57:30 +01:00
var marshalType = MarshalUtils . ConvertManagedTypeToMarshalType ( field . Type , typeCache ) ;
if ( marshalType = = null )
continue ;
yield return new GodotFieldData ( field , marshalType . Value ) ;
}
}
2022-08-15 10:53:08 +02:00
Clean diagnostic rules
Move the following diagnostics into static readonly fields: GD0101, GD0102, GD0103, GD0104, GD0105, GD0106, GD0107, GD0201, GD0202, GD0203, GD0301, GD0302, GD0303, GD0401, GD0402.
To be more consistent, the titles for the following diagnostics were modified: GD0101, GD0105, GD0106, GD0302, GD0303, GD0401, GD0402. A subsequent update of the documentation repo is needed.
Tests for the following diagnostics were created: GD0201, GD0202, GD0203.
2024-02-17 21:12:06 +01:00
public static Location ? FirstLocationWithSourceTreeOrDefault ( this IEnumerable < Location > locations )
{
return locations . FirstOrDefault ( location = > location . SourceTree ! = null ) ? ? locations . FirstOrDefault ( ) ;
}
2022-08-15 10:53:08 +02:00
public static string Path ( this Location location )
= > location . SourceTree ? . GetLineSpan ( location . SourceSpan ) . Path
2022-12-01 01:45:11 +01:00
? ? location . GetLineSpan ( ) . Path ;
2022-08-15 10:53:08 +02:00
public static int StartLine ( this Location location )
= > location . SourceTree ? . GetLineSpan ( location . SourceSpan ) . StartLinePosition . Line
2022-12-01 01:45:11 +01:00
? ? location . GetLineSpan ( ) . StartLinePosition . Line ;
2021-03-06 00:12:42 +01:00
}
}