123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- using System;
- using System.Collections.Generic;
- using System.Collections.Immutable;
- using System.Linq;
- using System.Text;
- using Microsoft.CodeAnalysis;
- using Microsoft.CodeAnalysis.CSharp;
- using Microsoft.CodeAnalysis.CSharp.Syntax;
- namespace Godot.SourceGenerators
- {
- static class ExtensionMethods
- {
- public static bool TryGetGlobalAnalyzerProperty(
- this GeneratorExecutionContext context, string property, out string? value
- ) => context.AnalyzerConfigOptions.GlobalOptions
- .TryGetValue("build_property." + property, out value);
- public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context)
- => context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) &&
- toggle != null &&
- toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase);
- public static bool IsGodotToolsProject(this GeneratorExecutionContext context)
- => context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) &&
- toggle != null &&
- toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
- public static bool IsGodotSourceGeneratorDisabled(this GeneratorExecutionContext context, string generatorName) =>
- AreGodotSourceGeneratorsDisabled(context) ||
- (context.TryGetGlobalAnalyzerProperty("GodotDisabledSourceGenerators", out string? disabledGenerators) &&
- disabledGenerators != null &&
- disabledGenerators.Split(';').Contains(generatorName));
- public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName)
- {
- while (symbol != null)
- {
- if (symbol.ContainingAssembly?.Name == assemblyName &&
- symbol.FullQualifiedNameOmitGlobal() == typeFullName)
- {
- return true;
- }
- symbol = symbol.BaseType;
- }
- return false;
- }
- public static INamedTypeSymbol? GetGodotScriptNativeClass(this INamedTypeSymbol classTypeSymbol)
- {
- var symbol = classTypeSymbol;
- while (symbol != null)
- {
- if (symbol.ContainingAssembly?.Name == "GodotSharp")
- 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;
- }
- private static bool TryGetGodotScriptClass(
- this ClassDeclarationSyntax cds, Compilation compilation,
- out INamedTypeSymbol? symbol
- )
- {
- var sm = compilation.GetSemanticModel(cds.SyntaxTree);
- var classTypeSymbol = sm.GetDeclaredSymbol(cds);
- if (classTypeSymbol?.BaseType == null
- || !classTypeSymbol.BaseType.InheritsFrom("GodotSharp", GodotClasses.GodotObject))
- {
- 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)
- {
- if (cds.TryGetGodotScriptClass(compilation, out var symbol))
- yield return (cds, symbol!);
- }
- }
- public static bool IsNested(this TypeDeclarationSyntax cds)
- => cds.Parent is TypeDeclarationSyntax;
- public static bool IsPartial(this TypeDeclarationSyntax cds)
- => cds.Modifiers.Any(SyntaxKind.PartialKeyword);
- 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"
- };
- }
- public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
- {
- return symbol.IsGenericType ?
- string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") :
- symbol.Name;
- }
- 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)
- => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
- public static string FullQualifiedNameIncludeGlobal(this ITypeSymbol symbol)
- => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatIncludeGlobal);
- public static string FullQualifiedNameIncludeGlobal(this INamespaceSymbol namespaceSymbol)
- => namespaceSymbol.ToDisplayString(FullyQualifiedFormatIncludeGlobal);
- public static string FullQualifiedSyntax(this SyntaxNode node, SemanticModel sm)
- {
- StringBuilder sb = new();
- FullQualifiedSyntax(node, sm, sb, true);
- return sb.ToString();
- }
- private static void FullQualifiedSyntax(SyntaxNode node, SemanticModel sm, StringBuilder sb, bool isFirstNode)
- {
- if (node is NameSyntax ns && isFirstNode)
- {
- SymbolInfo nameInfo = sm.GetSymbolInfo(ns);
- sb.Append(nameInfo.Symbol?.ToDisplayString(FullyQualifiedFormatIncludeGlobal) ?? ns.ToString());
- return;
- }
- bool innerIsFirstNode = true;
- foreach (var child in node.ChildNodesAndTokens())
- {
- if (child.HasLeadingTrivia)
- {
- sb.Append(child.GetLeadingTrivia());
- }
- if (child.IsNode)
- {
- FullQualifiedSyntax(child.AsNode()!, sm, sb, isFirstNode: innerIsFirstNode);
- innerIsFirstNode = false;
- }
- else
- {
- sb.Append(child);
- }
- if (child.HasTrailingTrivia)
- {
- sb.Append(child.GetTrailingTrivia());
- }
- }
- }
- public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName)
- => qualifiedName
- // AddSource() doesn't support angle brackets
- .Replace("<", "(Of ")
- .Replace(">", ")");
- public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol)
- => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ExportAttr;
- public static bool IsGodotSignalAttribute(this INamedTypeSymbol symbol)
- => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SignalAttr;
- public static bool IsGodotMustBeVariantAttribute(this INamedTypeSymbol symbol)
- => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.MustBeVariantAttr;
- public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
- => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GodotClassNameAttr;
- public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol)
- => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GlobalClassAttr;
- public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
- => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr;
- public static GodotMethodData? HasGodotCompatibleSignature(
- this IMethodSymbol method,
- MarshalUtils.TypeCache typeCache
- )
- {
- if (method.IsGenericMethod)
- return null;
- var retSymbol = method.ReturnType;
- var retType = method.ReturnsVoid ?
- null :
- MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
- if (retType == null && !method.ReturnsVoid)
- return null;
- var parameters = method.Parameters;
- 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();
- // 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
- return new GodotMethodData(method, paramTypes,
- parameters.Select(p => p.Type).ToImmutableArray(),
- retType != null ? (retType.Value, retSymbol) : null);
- }
- public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
- this IEnumerable<IMethodSymbol> methods,
- MarshalUtils.TypeCache typeCache
- )
- {
- foreach (var method in methods)
- {
- var methodData = HasGodotCompatibleSignature(method, typeCache);
- if (methodData != null)
- yield return methodData.Value;
- }
- }
- 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)
- {
- // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
- var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
- if (marshalType == null)
- continue;
- yield return new GodotFieldData(field, marshalType.Value);
- }
- }
- public static string Path(this Location location)
- => location.SourceTree?.GetLineSpan(location.SourceSpan).Path
- ?? location.GetLineSpan().Path;
- public static int StartLine(this Location location)
- => location.SourceTree?.GetLineSpan(location.SourceSpan).StartLinePosition.Line
- ?? location.GetLineSpan().StartLinePosition.Line;
- }
- }
|