Reformat / cleanup
This commit is contained in:
@@ -1,253 +1,229 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.SourceGenerators.Helpers
|
||||
{
|
||||
public static class SyntaxHelper
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether a symbol inherits from a base type with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol to inspect.</param>
|
||||
/// <param name="baseTypeName">The base type name to match.</param>
|
||||
/// <returns><see langword="true" /> if the symbol inherits from the base type; otherwise, <see langword="false" />.</returns>
|
||||
public static bool InheritsFrom(INamedTypeSymbol symbol, string baseTypeName)
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether a symbol inherits from a base type with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol to inspect.</param>
|
||||
/// <param name="baseTypeName">The base type name to match.</param>
|
||||
/// <returns><see langword="true"/> if the symbol inherits from the base type; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool InheritsFrom(INamedTypeSymbol symbol, string baseTypeName)
|
||||
var current = symbol.BaseType;
|
||||
while (current != null)
|
||||
{
|
||||
var current = symbol.BaseType;
|
||||
while (current != null)
|
||||
{
|
||||
if (current.Name == baseTypeName)
|
||||
return true;
|
||||
current = current.BaseType;
|
||||
}
|
||||
return false;
|
||||
if (current.Name == baseTypeName)
|
||||
return true;
|
||||
current = current.BaseType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds method invocations with a matching method name under the provided syntax node.
|
||||
/// </summary>
|
||||
/// <param name="node">The root syntax node to search.</param>
|
||||
/// <param name="methodName">The method name to match.</param>
|
||||
/// <returns>A list of matching invocation expressions.</returns>
|
||||
public static List<InvocationExpressionSyntax> FindMethodInvocations(SyntaxNode node, string methodName)
|
||||
{
|
||||
return node.DescendantNodes()
|
||||
.OfType<InvocationExpressionSyntax>()
|
||||
.Where(invocation =>
|
||||
{
|
||||
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
|
||||
{
|
||||
return memberAccess.Name.Identifier.Text == methodName;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first generic type argument from an invocation, if present.
|
||||
/// </summary>
|
||||
/// <param name="invocation">The invocation to inspect.</param>
|
||||
/// <returns>The generic type argument text, or <see langword="null"/> when not available.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a property name from an expression.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression to analyze.</param>
|
||||
/// <returns>The property name when resolved; otherwise, <see langword="null"/>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fully-qualified type name without the global prefix.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol to format.</param>
|
||||
/// <returns>The formatted full type name.</returns>
|
||||
public static string GetFullName(INamedTypeSymbol symbol)
|
||||
{
|
||||
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
.Replace("global::", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a display name for a type symbol.
|
||||
/// </summary>
|
||||
/// <param name="type">The type symbol to format.</param>
|
||||
/// <returns>The display name.</returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type is nullable.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <returns><see langword="true"/> if the type is nullable; otherwise, <see langword="false"/>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type is a collection and returns its item type when available.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <param name="itemType">When this method returns, contains the collection item type if the type is a collection.</param>
|
||||
/// <returns><see langword="true"/> if the type is a collection; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool IsCollectionType(ITypeSymbol type, out ITypeSymbol? itemType)
|
||||
{
|
||||
itemType = null;
|
||||
|
||||
// Exclude string (it's IEnumerable<char> but not a collection for our purposes)
|
||||
if (type.SpecialType == SpecialType.System_String)
|
||||
/// <summary>
|
||||
/// Finds method invocations with a matching method name under the provided syntax node.
|
||||
/// </summary>
|
||||
/// <param name="node">The root syntax node to search.</param>
|
||||
/// <param name="methodName">The method name to match.</param>
|
||||
/// <returns>A list of matching invocation expressions.</returns>
|
||||
public static List<InvocationExpressionSyntax> FindMethodInvocations(SyntaxNode node, string methodName)
|
||||
{
|
||||
return node.DescendantNodes()
|
||||
.OfType<InvocationExpressionSyntax>()
|
||||
.Where(invocation =>
|
||||
{
|
||||
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
|
||||
return memberAccess.Name.Identifier.Text == methodName;
|
||||
return false;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (type is IArrayTypeSymbol arrayType)
|
||||
{
|
||||
itemType = arrayType.ElementType;
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the first generic type argument from an invocation, if present.
|
||||
/// </summary>
|
||||
/// <param name="invocation">The invocation to inspect.</param>
|
||||
/// <returns>The generic type argument text, or <see langword="null" /> when not available.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
// Check if the type itself is IEnumerable<T>
|
||||
if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
|
||||
{
|
||||
var typeDefName = namedType.OriginalDefinition.ToDisplayString();
|
||||
if (typeDefName == "System.Collections.Generic.IEnumerable<T>" && namedType.TypeArguments.Length == 1)
|
||||
{
|
||||
itemType = namedType.TypeArguments[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Extracts a property name from an expression.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression to analyze.</param>
|
||||
/// <returns>The property name when resolved; otherwise, <see langword="null" />.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
// Check if the type implements IEnumerable<T> by walking all interfaces
|
||||
var enumerableInterface = type.AllInterfaces
|
||||
.FirstOrDefault(i => i.IsGenericType &&
|
||||
i.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.IEnumerable<T>");
|
||||
/// <summary>
|
||||
/// Gets the fully-qualified type name without the global prefix.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol to format.</param>
|
||||
/// <returns>The formatted full type name.</returns>
|
||||
public static string GetFullName(INamedTypeSymbol symbol)
|
||||
{
|
||||
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
.Replace("global::", "");
|
||||
}
|
||||
|
||||
if (enumerableInterface != null && enumerableInterface.TypeArguments.Length == 1)
|
||||
{
|
||||
itemType = enumerableInterface.TypeArguments[0];
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a display name for a type symbol.
|
||||
/// </summary>
|
||||
/// <param name="type">The type symbol to format.</param>
|
||||
/// <returns>The display name.</returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type is nullable.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <returns><see langword="true" /> if the type is nullable; otherwise, <see langword="false" />.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type is a collection and returns its item type when available.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <param name="itemType">When this method returns, contains the collection item type if the type is a collection.</param>
|
||||
/// <returns><see langword="true" /> if the type is a collection; otherwise, <see langword="false" />.</returns>
|
||||
public static bool IsCollectionType(ITypeSymbol type, out ITypeSymbol? itemType)
|
||||
{
|
||||
itemType = null;
|
||||
|
||||
// Exclude string (it's IEnumerable<char> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type should be treated as a primitive value.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <returns><see langword="true"/> if the type is primitive-like; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool IsPrimitiveType(ITypeSymbol type)
|
||||
// Check if the type itself is IEnumerable<T>
|
||||
if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
|
||||
{
|
||||
if (type is INamedTypeSymbol namedType &&
|
||||
namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
|
||||
{
|
||||
type = namedType.TypeArguments[0];
|
||||
string typeDefName = namedType.OriginalDefinition.ToDisplayString();
|
||||
if (typeDefName == "System.Collections.Generic.IEnumerable<T>" && namedType.TypeArguments.Length == 1)
|
||||
{
|
||||
itemType = namedType.TypeArguments[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.SpecialType != SpecialType.None && type.SpecialType != SpecialType.System_Object)
|
||||
return true;
|
||||
|
||||
var 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type should be treated as a nested object.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <returns><see langword="true"/> if the type is a nested object; otherwise, <see langword="false"/>.</returns>
|
||||
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;
|
||||
// Check if the type implements IEnumerable<T> by walking all interfaces
|
||||
var enumerableInterface = type.AllInterfaces
|
||||
.FirstOrDefault(i => i.IsGenericType &&
|
||||
i.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.IEnumerable<T>");
|
||||
|
||||
return type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct;
|
||||
if (enumerableInterface != null && enumerableInterface.TypeArguments.Length == 1)
|
||||
{
|
||||
itemType = enumerableInterface.TypeArguments[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a property has an associated backing field.
|
||||
/// </summary>
|
||||
/// <param name="property">The property to inspect.</param>
|
||||
/// <returns><see langword="true"/> if a backing field is found; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool HasBackingField(IPropertySymbol property)
|
||||
{
|
||||
// Auto-properties have compiler-generated backing fields
|
||||
// Check if there's a field with the pattern <PropertyName>k__BackingField
|
||||
return property.ContainingType.GetMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Any(f => f.AssociatedSymbol?.Equals(property, SymbolEqualityComparer.Default) == true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type should be treated as a primitive value.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <returns><see langword="true" /> if the type is primitive-like; otherwise, <see langword="false" />.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type should be treated as a nested object.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to evaluate.</param>
|
||||
/// <returns><see langword="true" /> if the type is a nested object; otherwise, <see langword="false" />.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a property has an associated backing field.
|
||||
/// </summary>
|
||||
/// <param name="property">The property to inspect.</param>
|
||||
/// <returns><see langword="true" /> if a backing field is found; otherwise, <see langword="false" />.</returns>
|
||||
public static bool HasBackingField(IPropertySymbol property)
|
||||
{
|
||||
// Auto-properties have compiler-generated backing fields
|
||||
// Check if there's a field with the pattern <PropertyName>k__BackingField
|
||||
return property.ContainingType.GetMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Any(f => f.AssociatedSymbol?.Equals(property, SymbolEqualityComparer.Default) == true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user