Repository: dotnet/maui
File: src/Controls/src/BindingSourceGen/ITypeSymbolExtensions.cs (C#)

Code before:
			}
			baseType = baseType.BaseType;
		}
	}

	private static System.Collections.Generic.IEnumerable<IFieldSymbol> GetAllFields(ITypeSymbol symbol, string name)
	{
		// Search in current type
		foreach (var member in symbol.GetMembers(name))
		{
			if (member is IFieldSymbol field)
				yield return field;
		}

		// Search in base types
		var baseType = symbol.BaseType;
		while (baseType != null)
		{
			foreach (var member in baseType.GetMembers(name))
			{
				if (member is IFieldSymbol field)
					yield return field;
			}
			baseType = baseType.BaseType;
		}
	}
}


Review comment (refactor):
Let's simplify this and unify this into a single while+foreach, we don't need the `baseType`. We might need to make `symbol` nullable so we can assign `BaseType` to it.

Code after:
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Microsoft.Maui.Controls.BindingSourceGen;

public static class ITypeSymbolExtensions
{
	public static bool IsTypeNullable(this ITypeSymbol typeInfo, bool enabledNullable)
	{
		if (!enabledNullable && typeInfo.IsReferenceType)
		{
			return true;
		}

		return typeInfo.IsNullableValueType() || typeInfo.IsNullableReferenceType();
	}

	public static TypeDescription CreateTypeDescription(this ITypeSymbol typeSymbol, bool enabledNullable)
	{
		var isNullable = IsTypeNullable(typeSymbol, enabledNullable);
		return new TypeDescription(
			GlobalName: GetGlobalName(typeSymbol, isNullable, typeSymbol.IsValueType),
			IsNullable: isNullable,
			IsGenericParameter: typeSymbol.Kind == SymbolKind.TypeParameter, //TODO: Add support for generic parameters
			IsValueType: typeSymbol.IsValueType);
	}

	private static bool IsNullableValueType(this ITypeSymbol typeInfo) =>
		typeInfo is INamedTypeSymbol namedTypeSymbol
			&& namedTypeSymbol.IsGenericType
			&& namedTypeSymbol.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;

	private static bool IsNullableReferenceType(this ITypeSymbol typeInfo) =>
		typeInfo.IsReferenceType && typeInfo.NullableAnnotation == NullableAnnotation.Annotated;


	private static string GetGlobalName(this ITypeSymbol typeSymbol, bool isNullable, bool isValueType)
	{
		if (isNullable && isValueType)
		{
			// Strips the "?" from the type name
			return ((INamedTypeSymbol)typeSymbol).TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
		}

		return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
	}

	/// <summary>
	/// Checks if a property name could be generated by CommunityToolkit.Mvvm's [RelayCommand] attribute,
	/// and returns the inferred command type if found.
	/// </summary>
	/// <param name="symbol">The type to search</param>
	/// <param name="propertyName">The name of the property to find (should end with "Command")</param>
	/// <param name="compilation">The compilation (can be null)</param>
	/// <param name="commandType">The inferred ICommand type if a RelayCommand method is found</param>
	/// <returns>True if a RelayCommand method was found that would generate this property</returns>
	public static bool TryGetRelayCommandPropertyType(this ITypeSymbol symbol, string propertyName, Compilation? compilation, out ITypeSymbol? commandType)
	{
		commandType = null;

		if (compilation == null)
			return false;

		// Check if the property name ends with "Command"
		if (!propertyName.EndsWith("Command", System.StringComparison.Ordinal))
			return false;

		// Extract the method name (property name without "Command" suffix)
		var methodName = propertyName.Substring(0, propertyName.Length - "Command".Length);

		// Look for a method with the base name - search in the type and base types
		var methods = GetAllMethods(symbol, methodName);

		foreach (var method in methods)
		{
			// Check if the method has the RelayCommand attribute
			var hasRelayCommand = method.GetAttributes().Any(attr =>
				attr.AttributeClass?.Name == "RelayCommandAttribute" ||
				attr.AttributeClass?.ToDisplayString() == "CommunityToolkit.Mvvm.Input.RelayCommandAttribute");

			if (hasRelayCommand)
			{
				// Try to find the ICommand interface type
				var icommandType = compilation.GetTypeByMetadataName("System.Windows.Input.ICommand");
				if (icommandType != null)
				{
					commandType = icommandType;
					return true;
				}
			}
		}

		return false;
	}

	/// <summary>
	/// Checks if a property name could be generated by CommunityToolkit.Mvvm's [ObservableProperty] attribute,
	/// and returns the inferred property type if found.
	/// </summary>
	/// <param name="symbol">The type to search</param>
	/// <param name="propertyName">The name of the property to find</param>
	/// <param name="compilation">The compilation (can be null)</param>
	/// <param name="propertyType">The inferred property type if an ObservableProperty field is found</param>
	/// <returns>True if an ObservableProperty field was found that would generate this property</returns>
	public static bool TryGetObservablePropertyType(this ITypeSymbol symbol, string propertyName, Compilation? compilation, out ITypeSymbol? propertyType)
	{
		propertyType = null;

		if (compilation == null || string.IsNullOrEmpty(propertyName))
			return false;

		// ObservableProperty generates a PascalCase property from a camelCase, _camelCase, or m_camelCase field
		// Try common field naming patterns
		var possibleFieldNames = new[]
		{
			char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1), // name from Name
			"_" + char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1), // _name from Name
			"m_" + char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1) // m_name from Name
		};

		// Look for a field with one of the possible names - search in the type and base types
		foreach (var fieldName in possibleFieldNames)
		{
			var fields = GetAllFields(symbol, fieldName);

			foreach (var field in fields)
			{
				// Check if the field has the ObservableProperty attribute
				var hasObservableProperty = field.GetAttributes().Any(attr =>
					attr.AttributeClass?.Name == "ObservablePropertyAttribute" ||
					attr.AttributeClass?.ToDisplayString() == "CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute");

				if (hasObservableProperty)
				{
					propertyType = field.Type;
					return true;
				}
			}
		}

		return false;
	}

	private static System.Collections.Generic.IEnumerable<IMethodSymbol> GetAllMethods(ITypeSymbol symbol, string name)
	{
		// Search in current type
		foreach (var member in symbol.GetMembers(name))
		{
			if (member is IMethodSymbol method)
				yield return method;
		}

		// Search in base types
		var baseType = symbol.BaseType;
		while (baseType != null)
		{
			foreach (var member in baseType.GetMembers(name))
			{
				if (member is IMethodSymbol method)
					yield return method;
			}
			baseType = baseType.BaseType;
		}
	}

	private static System.Collections.Generic.IEnumerable<IFieldSymbol> GetAllFields(ITypeSymbol? symbol, string name)
	{
		while (symbol != null)
		{
			foreach (var member in symbol.GetMembers(name))
			{
				if (member is IFieldSymbol field)
					yield return field;
			}
			symbol = symbol.BaseType;
		}
	}
}