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);
}
}