File: PathParser.cs
Web Access
Project: src\src\Controls\src\BindingSourceGen\Controls.BindingSourceGen.csproj (Microsoft.Maui.Controls.BindingSourceGen)
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.Maui.Controls.BindingSourceGen;
 
internal class PathParser
{
	private readonly GeneratorSyntaxContext _context;
	private readonly bool _enabledNullable;
 
	internal PathParser(GeneratorSyntaxContext context, bool enabledNullable)
	{
		_context = context;
		_enabledNullable = enabledNullable;
	}
 
	internal Result<List<IPathPart>> ParsePath(CSharpSyntaxNode? expressionSyntax)
	{
		return expressionSyntax switch
		{
			IdentifierNameSyntax _ => Result<List<IPathPart>>.Success(new List<IPathPart>()),
			MemberAccessExpressionSyntax memberAccess => HandleMemberAccessExpression(memberAccess),
			ElementAccessExpressionSyntax elementAccess => HandleElementAccessExpression(elementAccess),
			ElementBindingExpressionSyntax elementBinding => HandleElementBindingExpression(elementBinding),
			ConditionalAccessExpressionSyntax conditionalAccess => HandleConditionalAccessExpression(conditionalAccess),
			MemberBindingExpressionSyntax memberBinding => HandleMemberBindingExpression(memberBinding),
			ParenthesizedExpressionSyntax parenthesized => ParsePath(parenthesized.Expression),
			BinaryExpressionSyntax asExpression when asExpression.Kind() == SyntaxKind.AsExpression => HandleBinaryExpression(asExpression),
			CastExpressionSyntax castExpression => HandleCastExpression(castExpression),
			_ => HandleDefaultCase(),
		};
	}
 
	private Result<List<IPathPart>> HandleMemberAccessExpression(MemberAccessExpressionSyntax memberAccess)
	{
		var result = ParsePath(memberAccess.Expression);
		if (result.HasDiagnostics)
		{
			return result;
		}
 
		var member = memberAccess.Name.Identifier.Text;
		var typeInfo = _context.SemanticModel.GetTypeInfo(memberAccess).Type;
		var symbol = _context.SemanticModel.GetSymbolInfo(memberAccess).Symbol;
 
		if (symbol == null || typeInfo == null)
		{
			return Result<List<IPathPart>>.Failure(DiagnosticsFactory.UnableToResolvePath(memberAccess.GetLocation()));
		}
 
		var isReferenceType = typeInfo.IsReferenceType;
		var accessorKind = symbol.ToAccessorKind();
		var memberType = typeInfo.CreateTypeDescription(_enabledNullable);
		var containgType = symbol.ContainingType.CreateTypeDescription(_enabledNullable);
 
		IPathPart part = symbol.IsAccessible()
			? new MemberAccess(member, !isReferenceType)
			: new InaccessibleMemberAccess(containgType, memberType, accessorKind, member, !isReferenceType);
 
		result.Value.Add(part);
		return Result<List<IPathPart>>.Success(result.Value);
	}
 
	private Result<List<IPathPart>> HandleElementAccessExpression(ElementAccessExpressionSyntax elementAccess)
	{
		var result = ParsePath(elementAccess.Expression);
		if (result.HasDiagnostics)
		{
			return result;
		}
 
		var elementAccessSymbol = _context.SemanticModel.GetSymbolInfo(elementAccess).Symbol;
		var elementType = _context.SemanticModel.GetTypeInfo(elementAccess).Type;
 
		var elementAccessResult = CreateIndexAccess(elementAccessSymbol, elementType, elementAccess.ArgumentList.Arguments, elementAccess.GetLocation());
		if (elementAccessResult.HasDiagnostics)
		{
			return elementAccessResult;
		}
		result.Value.AddRange(elementAccessResult.Value);
 
		return Result<List<IPathPart>>.Success(result.Value);
	}
 
	private Result<List<IPathPart>> HandleConditionalAccessExpression(ConditionalAccessExpressionSyntax conditionalAccess)
	{
		var expressionResult = ParsePath(conditionalAccess.Expression);
		if (expressionResult.HasDiagnostics)
		{
			return expressionResult;
		}
 
		var whenNotNullResult = ParsePath(conditionalAccess.WhenNotNull);
		if (whenNotNullResult.HasDiagnostics)
		{
			return whenNotNullResult;
		}
 
		expressionResult.Value.AddRange(whenNotNullResult.Value);
 
		return Result<List<IPathPart>>.Success(expressionResult.Value);
	}
 
	private Result<List<IPathPart>> HandleMemberBindingExpression(MemberBindingExpressionSyntax memberBinding)
	{
		var member = memberBinding.Name.Identifier.Text;
		var typeInfo = _context.SemanticModel.GetTypeInfo(memberBinding).Type;
		var isReferenceType = typeInfo?.IsReferenceType ?? false;
		IPathPart part = new MemberAccess(member, !isReferenceType);
		part = new ConditionalAccess(part);
 
		return Result<List<IPathPart>>.Success(new List<IPathPart>([part]));
	}
 
	private Result<List<IPathPart>> HandleElementBindingExpression(ElementBindingExpressionSyntax elementBinding)
	{
		var elementAccessSymbol = _context.SemanticModel.GetSymbolInfo(elementBinding).Symbol;
		var elementType = _context.SemanticModel.GetTypeInfo(elementBinding).Type;
 
		var elementAccessResult = CreateIndexAccess(elementAccessSymbol, elementType, elementBinding.ArgumentList.Arguments, elementBinding.GetLocation());
		if (elementAccessResult.HasDiagnostics)
		{
			return elementAccessResult;
		}
 
		elementAccessResult.Value[0] = new ConditionalAccess(elementAccessResult.Value[0]);
 
		return Result<List<IPathPart>>.Success(elementAccessResult.Value);
	}
 
	private Result<List<IPathPart>> HandleBinaryExpression(BinaryExpressionSyntax asExpression)
	{
		var leftResult = ParsePath(asExpression.Left);
		if (leftResult.HasDiagnostics)
		{
			return leftResult;
		}
 
		var castTo = asExpression.Right;
		var typeInfo = _context.SemanticModel.GetTypeInfo(castTo).Type;
		if (typeInfo == null)
		{
			return Result<List<IPathPart>>.Failure(DiagnosticsFactory.UnableToResolvePath(castTo.GetLocation()));
		};
 
		leftResult.Value.Add(new Cast(typeInfo.CreateTypeDescription(_enabledNullable)));
 
		return Result<List<IPathPart>>.Success(leftResult.Value);
	}
 
	private Result<List<IPathPart>> HandleCastExpression(CastExpressionSyntax castExpression)
	{
		var result = ParsePath(castExpression.Expression);
		if (result.HasDiagnostics)
		{
			return result;
		}
 
		var typeInfo = _context.SemanticModel.GetTypeInfo(castExpression.Type).Type;
		if (typeInfo == null)
		{
			return Result<List<IPathPart>>.Failure(DiagnosticsFactory.UnableToResolvePath(castExpression.GetLocation()));
		};
 
		result.Value.Add(new Cast(typeInfo.CreateTypeDescription(_enabledNullable)));
 
		return Result<List<IPathPart>>.Success(result.Value);
	}
 
	private Result<List<IPathPart>> HandleDefaultCase()
	{
		return Result<List<IPathPart>>.Failure(DiagnosticsFactory.UnableToResolvePath(_context.Node.GetLocation()));
	}
 
	private Result<List<IPathPart>> CreateIndexAccess(ISymbol? elementAccessSymbol, ITypeSymbol? typeSymbol, SeparatedSyntaxList<ArgumentSyntax> argumentList, Location location)
	{
		if (argumentList.Count != 1)
		{
			return Result<List<IPathPart>>.Failure(DiagnosticsFactory.UnableToResolvePath(location));
		}
 
		var indexExpression = argumentList[0].Expression;
		object? indexValue = _context.SemanticModel.GetConstantValue(indexExpression).Value;
		if (indexValue is null)
		{
			return Result<List<IPathPart>>.Failure(DiagnosticsFactory.UnableToResolvePath(indexExpression.GetLocation()));
		}
 
		var name = elementAccessSymbol.GetIndexerName();
		var isReferenceType = typeSymbol?.IsReferenceType ?? false;
		IPathPart part = new IndexAccess(name, indexValue, !isReferenceType);
 
		return Result<List<IPathPart>>.Success(new List<IPathPart>([part]));
	}
}