File: InvocationParser.cs
Web Access
Project: src\src\Controls\src\BindingSourceGen\Controls.BindingSourceGen.csproj (Microsoft.Maui.Controls.BindingSourceGen)
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.Maui.Controls.BindingSourceGen;
 
internal class InvocationParser
{
	private readonly GeneratorSyntaxContext _context;
 
	internal InvocationParser(GeneratorSyntaxContext context)
	{
		_context = context;
	}
 
	private class InterceptedMethodsNames
	{
		internal const string SetBinding = nameof(SetBinding);
		internal const string Create = nameof(Create);
	}
 
	internal Result<InterceptedMethodType> ParseInvocation(InvocationExpressionSyntax invocationSyntax, CancellationToken t)
	{
		return ((MemberAccessExpressionSyntax)invocationSyntax.Expression).Name.Identifier.Text switch
		{
			InterceptedMethodsNames.SetBinding => VerifyCorrectOverloadSetBinding(invocationSyntax, t),
			InterceptedMethodsNames.Create => VerifyCorrectOverloadBindingCreate(invocationSyntax, t),
			_ => throw new NotSupportedException()
		};
	}
 
	private Result<InterceptedMethodType> VerifyCorrectOverloadBindingCreate(InvocationExpressionSyntax invocation, CancellationToken t)
	{
		var argumentList = invocation.ArgumentList.Arguments;
 
		var symbol = _context.SemanticModel.GetSymbolInfo(invocation.Expression).Symbol;
		if ((symbol?.ContainingType?.Name != "Binding" && symbol?.ContainingType?.Name != "BindingBase")
			|| symbol?.ContainingType?.ContainingNamespace.ToDisplayString() is not "Microsoft.Maui.Controls")
		{
			return Result<InterceptedMethodType>.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation()));
		}
 
		if (argumentList.Count == 0)
		{
			throw new ArgumentOutOfRangeException(nameof(invocation));
		}
 
		var firstArgument = argumentList[0].Expression;
		if (firstArgument is IdentifierNameSyntax)
		{
			var type = _context.SemanticModel.GetTypeInfo(firstArgument, cancellationToken: t).Type;
			if (type != null && type.Name == "Func")
			{
				return Result<InterceptedMethodType>.Failure(DiagnosticsFactory.GetterIsNotLambda(firstArgument.GetLocation()));
			}
			else // String and Binding
			{
				return Result<InterceptedMethodType>.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(firstArgument.GetLocation()));
			}
		}
 
		return Result<InterceptedMethodType>.Success(InterceptedMethodType.Create);
	}
 
	private Result<InterceptedMethodType> VerifyCorrectOverloadSetBinding(InvocationExpressionSyntax invocation, CancellationToken t)
	{
		var symbol = _context.SemanticModel.GetSymbolInfo(invocation.Expression).Symbol;
		if (symbol is not null)
		{
			if (symbol is not IMethodSymbol methodSymbol
				|| methodSymbol.Kind != SymbolKind.Method
				|| methodSymbol.Name != "SetBinding"
				|| !methodSymbol.IsGenericMethod
				|| methodSymbol.TypeParameters.Length != 2
				|| methodSymbol.Parameters.Length != 9
				|| methodSymbol.ContainingType?.Name != "BindableObjectExtensions"
				|| methodSymbol.ContainingType?.ContainingNamespace.ToDisplayString() is not "Microsoft.Maui.Controls")
			{
				// ignore this method invocation
				return Result<InterceptedMethodType>.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation()));
			}
		}
		else
		{
			// It is not possible to resolve the method symbol when the bindable object (the first argument or the object that the extension method
			// is called on) is referenced by a field that will be generated via XamlG based on the x:Name attributes. In that case, this source generator
			// cannot see the outputs of the other source generator and we have incomplete information about the method invocation and we can only work with
			// the syntax tree and not the semantic model.
 
			var argumentsList = invocation.ArgumentList.Arguments;
			if (argumentsList.Count < 2)
			{
				return Result<InterceptedMethodType>.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation()));
			}
 
			var secondArgument = argumentsList[1].Expression;
			if (secondArgument is not LambdaExpressionSyntax)
			{
				var secondArgumentType = _context.SemanticModel.GetTypeInfo(secondArgument, cancellationToken: t).Type;
				return secondArgumentType switch
				{
					{ Name: "Func", ContainingNamespace.Name: "System" } => Result<InterceptedMethodType>.Failure(DiagnosticsFactory.GetterIsNotLambda(secondArgument.GetLocation())),
					_ => Result<InterceptedMethodType>.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(secondArgument.GetLocation())),
				};
			}
		}
 
		return Result<InterceptedMethodType>.Success(InterceptedMethodType.SetBinding);
	}
}