using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace ZB.MOM.WW.CBDD.SourceGenerators.Helpers; public static class SyntaxHelper { /// /// Determines whether a symbol inherits from a base type with the specified name. /// /// The symbol to inspect. /// The base type name to match. /// if the symbol inherits from the base type; otherwise, . public static bool InheritsFrom(INamedTypeSymbol symbol, string baseTypeName) { var current = symbol.BaseType; while (current != null) { if (current.Name == baseTypeName) return true; current = current.BaseType; } return false; } /// /// Finds method invocations with a matching method name under the provided syntax node. /// /// The root syntax node to search. /// The method name to match. /// A list of matching invocation expressions. public static List FindMethodInvocations(SyntaxNode node, string methodName) { return node.DescendantNodes() .OfType() .Where(invocation => { if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) return memberAccess.Name.Identifier.Text == methodName; return false; }) .ToList(); } /// /// Gets the first generic type argument from an invocation, if present. /// /// The invocation to inspect. /// The generic type argument text, or when not available. public static string? GetGenericTypeArgument(InvocationExpressionSyntax invocation) { if (invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Name is GenericNameSyntax genericName && genericName.TypeArgumentList.Arguments.Count > 0) return genericName.TypeArgumentList.Arguments[0].ToString(); return null; } /// /// Extracts a property name from an expression. /// /// The expression to analyze. /// The property name when resolved; otherwise, . public static string? GetPropertyName(ExpressionSyntax? expression) { if (expression == null) return null; if (expression is LambdaExpressionSyntax lambda) return GetPropertyName(lambda.Body as ExpressionSyntax); if (expression is MemberAccessExpressionSyntax memberAccess) return memberAccess.Name.Identifier.Text; if (expression is PrefixUnaryExpressionSyntax prefixUnary && prefixUnary.Operand is MemberAccessExpressionSyntax prefixMember) return prefixMember.Name.Identifier.Text; if (expression is PostfixUnaryExpressionSyntax postfixUnary && postfixUnary.Operand is MemberAccessExpressionSyntax postfixMember) return postfixMember.Name.Identifier.Text; return null; } /// /// Gets the fully-qualified type name without the global prefix. /// /// The symbol to format. /// The formatted full type name. public static string GetFullName(INamedTypeSymbol symbol) { return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) .Replace("global::", ""); } /// /// Gets a display name for a type symbol. /// /// The type symbol to format. /// The display name. public static string GetTypeName(ITypeSymbol type) { if (type is INamedTypeSymbol namedType && namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { var underlyingType = namedType.TypeArguments[0]; return GetTypeName(underlyingType) + "?"; } if (type is IArrayTypeSymbol arrayType) return GetTypeName(arrayType.ElementType) + "[]"; if (type is INamedTypeSymbol nt && nt.IsTupleType) return type.ToDisplayString(); return type.ToDisplayString(); } /// /// Determines whether a type is nullable. /// /// The type to evaluate. /// if the type is nullable; otherwise, . public static bool IsNullableType(ITypeSymbol type) { if (type is INamedTypeSymbol namedType && namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) return true; return type.NullableAnnotation == NullableAnnotation.Annotated; } /// /// Determines whether a type is a collection and returns its item type when available. /// /// The type to evaluate. /// When this method returns, contains the collection item type if the type is a collection. /// if the type is a collection; otherwise, . public static bool IsCollectionType(ITypeSymbol type, out ITypeSymbol? itemType) { itemType = null; // Exclude string (it's IEnumerable but not a collection for our purposes) if (type.SpecialType == SpecialType.System_String) return false; // Handle arrays if (type is IArrayTypeSymbol arrayType) { itemType = arrayType.ElementType; return true; } // Check if the type itself is IEnumerable if (type is INamedTypeSymbol namedType && namedType.IsGenericType) { string typeDefName = namedType.OriginalDefinition.ToDisplayString(); if (typeDefName == "System.Collections.Generic.IEnumerable" && namedType.TypeArguments.Length == 1) { itemType = namedType.TypeArguments[0]; return true; } } // Check if the type implements IEnumerable by walking all interfaces var enumerableInterface = type.AllInterfaces .FirstOrDefault(i => i.IsGenericType && i.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.IEnumerable"); if (enumerableInterface != null && enumerableInterface.TypeArguments.Length == 1) { itemType = enumerableInterface.TypeArguments[0]; return true; } return false; } /// /// Determines whether a type should be treated as a primitive value. /// /// The type to evaluate. /// if the type is primitive-like; otherwise, . public static bool IsPrimitiveType(ITypeSymbol type) { if (type is INamedTypeSymbol namedType && namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) type = namedType.TypeArguments[0]; if (type.SpecialType != SpecialType.None && type.SpecialType != SpecialType.System_Object) return true; string typeName = type.Name; if (typeName == "Guid" || typeName == "DateTime" || typeName == "DateTimeOffset" || typeName == "TimeSpan" || typeName == "DateOnly" || typeName == "TimeOnly" || typeName == "Decimal" || typeName == "ObjectId") return true; if (type.TypeKind == TypeKind.Enum) return true; if (type is INamedTypeSymbol nt && nt.IsTupleType) return true; return false; } /// /// Determines whether a type should be treated as a nested object. /// /// The type to evaluate. /// if the type is a nested object; otherwise, . public static bool IsNestedObjectType(ITypeSymbol type) { if (IsPrimitiveType(type)) return false; if (type.SpecialType == SpecialType.System_String) return false; if (IsCollectionType(type, out _)) return false; if (type.SpecialType == SpecialType.System_Object) return false; if (type is INamedTypeSymbol nt && nt.IsTupleType) return false; return type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct; } /// /// Determines whether a property has an associated backing field. /// /// The property to inspect. /// if a backing field is found; otherwise, . public static bool HasBackingField(IPropertySymbol property) { // Auto-properties have compiler-generated backing fields // Check if there's a field with the pattern k__BackingField return property.ContainingType.GetMembers() .OfType() .Any(f => f.AssociatedSymbol?.Equals(property, SymbolEqualityComparer.Default) == true); } }