public void CreateInvokeMethodExpression_VariableDeclaration()
        {
            var v      = new VariableDeclarationStatement("a", typeof(string));
            var member = v.InvokeMethod(LiteralExpression.Null());

            var csharp = new CSharpCodeGenerator().Write(member);

            Assert.Equal("a(null)", csharp);
        }
        private void GenerateParameterEntity(ParameterEntity entity)
        {
            var unit = CreateUnit("Models/" + entity.Name + ".cs");
            var ns   = unit.AddNamespace(RootNamespace);
            var type = ns.AddType(new StructDeclaration(entity.Name));

            type.Modifiers = Modifiers.Public | Modifiers.ReadOnly | Modifiers.Partial;

            type.Implements.Add(WellKnownTypes.IGitLabObjectReferenceTypeReference.MakeGeneric(entity.FinalType.ToArgumentTypeReference()));
            type.CustomAttributes.Add(new CustomAttribute(typeof(JsonConverterAttribute))
            {
                Arguments =
                {
                    new CustomAttributeArgument(new TypeOfExpression(WellKnownTypes.GitLabObjectReferenceJsonConverterFactoryTypeReference)),
                },
            });

            // Add Value Property (readonly)
            var valueField    = new FieldDeclaration("_value", GetPropertyTypeRef(entity.FinalType), Modifiers.Private | Modifiers.ReadOnly);
            var valueProperty = new PropertyDeclaration("Value", GetPropertyTypeRef(entity.FinalType))
            {
                Modifiers = Modifiers.Public,
                Getter    = new PropertyAccessorDeclaration
                {
                    Statements = new ReturnStatement(valueField),
                },
            };

            type.AddMember(valueField);
            type.AddMember(valueProperty);

            foreach (var entityRef in entity.Refs)
            {
                var addNullCheck = entityRef.ModelRef.Model != null;

                // Add constructor
                var ctor = type.AddMember(new ConstructorDeclaration()
                {
                    Modifiers = Modifiers.Private,
                    Arguments =
                    {
                        new MethodArgumentDeclaration(GetPropertyTypeRef(entityRef.ModelRef), ToArgumentName(entityRef.Name)),
                    },
                    Statements =
                    {
                        new AssignStatement(valueField, GetAssignExpression()),
                    },
                });

                if (addNullCheck)
                {
                    ctor.Statements.Insert(0, ctor.Arguments[0].CreateThrowIfNullStatement());
                }

                // FromXXX
                var fromMethod = type.AddMember(new MethodDeclaration()
                {
                    Name       = "From" + ToPropertyName(entityRef.Name),
                    Modifiers  = Modifiers.Public | Modifiers.Static,
                    ReturnType = type,
                    Arguments  =
                    {
                        new MethodArgumentDeclaration(GetPropertyTypeRef(entityRef.ModelRef), ToArgumentName(entityRef.Name)),
                    },
                    Statements = new ReturnStatement(new NewObjectExpression(type, new ArgumentReferenceExpression(ToArgumentName(entityRef.Name)))),
                });

                if (addNullCheck)
                {
                    fromMethod.Statements.Insert(0, fromMethod.Arguments[0].CreateThrowIfNullStatement());
                }

                // Add implicit converter
                type.AddMember(new OperatorDeclaration
                {
                    Modifiers  = Modifiers.Public | Modifiers.Static | Modifiers.Implicit,
                    ReturnType = type,
                    Arguments  =
                    {
                        new MethodArgumentDeclaration(GetPropertyTypeRef(entityRef.ModelRef), ToArgumentName(entityRef.Name)),
                    },
                    Statements =
                    {
                        new ReturnStatement(new MethodInvokeExpression(new MemberReferenceExpression(type, fromMethod.Name), new ArgumentReferenceExpression(ToArgumentName(entityRef.Name)))),
                    },
                });

                // Add implicit converter nullable
                var nullableImplicitConverter = type.AddMember(new OperatorDeclaration
                {
                    Modifiers  = Modifiers.Public | Modifiers.Static | Modifiers.Implicit,
                    ReturnType = new TypeReference(type).MakeNullable(),
                    Arguments  =
                    {
                        new MethodArgumentDeclaration(GetPropertyTypeRef(entityRef.ModelRef).MakeNullable(), ToArgumentName(entityRef.Name)),
                    },
                });

                if (entityRef.ModelRef.IsValueType)
                {
                    nullableImplicitConverter.Statements.Add(
                        new ConditionStatement
                    {
                        Condition      = new MemberReferenceExpression(new ArgumentReferenceExpression(ToArgumentName(entityRef.Name)), "HasValue"),
                        TrueStatements = new ReturnStatement(
                            new MethodInvokeExpression(
                                new MemberReferenceExpression(type, fromMethod.Name),
                                new MemberReferenceExpression(new ArgumentReferenceExpression(ToArgumentName(entityRef.Name)), "Value"))),
                        FalseStatements = new ReturnStatement(LiteralExpression.Null()),
                    });
                }
                else
                {
                    nullableImplicitConverter.Statements.Add(
                        new ConditionStatement
                    {
                        Condition = new MethodInvokeExpression(
                            new MemberReferenceExpression(typeof(object), nameof(object.ReferenceEquals)),
                            new ArgumentReferenceExpression(ToArgumentName(entityRef.Name)),
                            LiteralExpression.Null()),
                        TrueStatements  = new ReturnStatement(LiteralExpression.Null()),
                        FalseStatements = new ReturnStatement(new MethodInvokeExpression(new MemberReferenceExpression(type, fromMethod.Name), new ArgumentReferenceExpression(ToArgumentName(entityRef.Name)))),
                    });
                }

                Expression GetAssignExpression()
                {
                    Expression value = new ArgumentReferenceExpression(ToArgumentName(entityRef.Name));

                    foreach (var member in entityRef.PropertyPath)
                    {
                        value = value.CreateMemberReferenceExpression(ToPropertyName(member));
                    }

                    return(value);
                }
            }

            // ToString
            GenerateParameterEntityToString(entity, type, valueProperty);

            // Equals, GetHashCode, ==, !=
            type.Implements.Add(new TypeReference(typeof(IEquatable <>)).MakeGeneric(type));
            GenerateParameterEntityEqualMethod(type);
            GenerateParameterEntityEqualTypedMethod(type, valueProperty);
            GenerateParameterEntityEqualityOperators(type);
            GenerateParameterEntityGetHashCode(type, valueProperty);
        }
        private static MethodDeclaration GenerateMethod(ClassDeclaration clientClass, Method method, TypeReference requestType)
        {
            var m = clientClass.AddMember(new MethodDeclaration(method.MethodGroup.Name + '_' + GetMethodName(method)));

            m.SetData("Method", method);
            var buildUrlMethod = GenerateBuildUrlMethod(clientClass, method, requestType);

            GenerateMethodSignature(method, m, requestType, out var requestArgument, out var requestOptionsArgument, out var cancellationTokenArgument);
            m.Modifiers = Modifiers.Private;
            if (method.MethodType != MethodType.GetPaged)
            {
                m.Modifiers |= Modifiers.Async;
            }

            // Method body
            m.Statements = new StatementCollection();

            // 1. Create url from parameters
            var buildUrlExpression = new MethodInvokeExpression(new MemberReferenceExpression(new TypeReference(clientClass), buildUrlMethod));

            if (buildUrlMethod.Arguments.Count > 0)
            {
                buildUrlExpression.Arguments.Add(requestArgument);
            }

            var urlVariable = new VariableDeclarationStatement(typeof(string), "url", buildUrlExpression);

            m.Statements.Add(urlVariable);

            if (method.MethodType == MethodType.GetPaged)
            {
                // return new Meziantou.GitLab.PagedResponse<MergeRequest>(this, url, requestOptions);
                m.Statements.Add(new ReturnStatement(new NewObjectExpression(m.ReturnType !.Clone(), new ThisExpression(), urlVariable, requestOptionsArgument)));
            }
            else
            {
                // 2. Create HttpRequestMessage object
                var requestVariable = new VariableDeclarationStatement(typeof(HttpRequestMessage), "requestMessage", new NewObjectExpression(typeof(HttpRequestMessage)));
                var usingStatement  = new UsingStatement()
                {
                    Statement = requestVariable, Body = new StatementCollection()
                };
                m.Statements.Add(usingStatement);
                var statements = usingStatement.Body;

                statements.Add(new AssignStatement(new MemberReferenceExpression(requestVariable, nameof(HttpRequestMessage.Method)), GetHttpMethod(method.MethodType)));
                statements.Add(new AssignStatement(new MemberReferenceExpression(requestVariable, nameof(HttpRequestMessage.RequestUri)), new NewObjectExpression(typeof(Uri), urlVariable, new MemberReferenceExpression(typeof(UriKind), nameof(UriKind.RelativeOrAbsolute)))));

                CreateBodyArgument(method, statements, requestArgument, requestVariable);

                // 3. Send request
                // var response = await SendAsync(request, options, cancellationToken).ConfigureAwait(false);
                var responseVariable = new VariableDeclarationStatement(WellKnownTypes.HttpResponseTypeReference.MakeNullable(), "response", LiteralExpression.Null());
                statements.Add(responseVariable);
                var responseTry = new TryCatchFinallyStatement()
                {
                    Try = new StatementCollection()
                };
                statements.Add(responseTry);
                responseTry.Try.Add(new AssignStatement(responseVariable, new AwaitExpression(new MethodInvokeExpression(new MemberReferenceExpression(new ThisExpression(), "SendAsync"), requestVariable, requestOptionsArgument, cancellationTokenArgument)).ConfigureAwait(false)));

                // Dispose reponse in catch if Stream or in finally if not stream
                var disposeReponseStatements = new ConditionStatement()
                {
                    Condition      = new BinaryExpression(BinaryOperator.NotEquals, responseVariable, LiteralExpression.Null()),
                    TrueStatements = responseVariable.CreateInvokeMethodExpression("Dispose"),
                };

                if (method.ReturnType == ModelRef.File)
                {
                    responseTry.Catch = new CatchClauseCollection
                    {
                        new CatchClause()
                        {
                            Body = disposeReponseStatements,
                        },
                    };

                    responseTry.Catch[0].Body.Add(new ThrowStatement());
                }
                else
                {
                    responseTry.Finally = disposeReponseStatements;
                }

                // 4. Convert and return response object
                //    if (response.StatusCode == HttpStatusCode.NotFound) return default;
                if (method.MethodType == MethodType.Get)
                {
                    responseTry.Try.Add(new ConditionStatement()
                    {
                        Condition      = new BinaryExpression(BinaryOperator.Equals, responseVariable.CreateMemberReferenceExpression("StatusCode"), new MemberReferenceExpression(typeof(HttpStatusCode), "NotFound")),
                        TrueStatements = new ReturnStatement(new DefaultValueExpression()),
                    });
                }

                // await response.EnsureStatusCodeAsync(cancellationToken).ConfigureAwait(false);
                responseTry.Try.Add(new AwaitExpression(new MethodInvokeExpression(new MemberReferenceExpression(responseVariable, "EnsureStatusCodeAsync"), cancellationTokenArgument)).ConfigureAwait(false));

                if (method.ReturnType != null)
                {
                    // var result = await response.ToObjectAsync<T>(cancellationToken).ConfigureAwait(false);
                    var resultVariable = new VariableDeclarationStatement(m.ReturnType.Parameters[0].MakeNullable(), "result");
                    responseTry.Try.Add(resultVariable);
                    if (method.MethodType == MethodType.Get && method.ReturnType == ModelRef.File)
                    {
                        resultVariable.InitExpression = new AwaitExpression(responseVariable.CreateInvokeMethodExpression("ToStreamAsync", cancellationTokenArgument)).ConfigureAwait(false);
                    }
                    else if (method.MethodType == MethodType.GetCollection)
                    {
                        resultVariable.InitExpression = new AwaitExpression(responseVariable.CreateInvokeMethodExpression("ToCollectionAsync", new TypeReference[] { method.ReturnType.ToPropertyTypeReference() }, cancellationTokenArgument)).ConfigureAwait(false);
                    }
                    else
                    {
                        resultVariable.InitExpression = new AwaitExpression(responseVariable.CreateInvokeMethodExpression("ToObjectAsync", new TypeReference[] { method.ReturnType.ToPropertyTypeReference() }, cancellationTokenArgument)).ConfigureAwait(false);
                    }

                    // if (result is null)
                    //   throw new GitLabException(response.RequestMethod, response.RequestUri, response.StatusCode, $"The response cannot be converted to '{typeof(T)}' because the body is null or empty");
                    if (method.MethodType != MethodType.Get)
                    {
                        responseTry.Try.Add(new ConditionStatement()
                        {
                            Condition      = new BinaryExpression(BinaryOperator.Equals, resultVariable, LiteralExpression.Null()),
                            TrueStatements = new ThrowStatement(new NewObjectExpression(WellKnownTypes.GitLabExceptionTypeReference,
                                                                                        responseVariable.CreateMemberReferenceExpression("RequestMethod"),
                                                                                        responseVariable.CreateMemberReferenceExpression("RequestUri"),
                                                                                        responseVariable.CreateMemberReferenceExpression("StatusCode"),
                                                                                        new LiteralExpression($"The response cannot be converted to '{method.ReturnType.ToPropertyTypeReference().ClrFullTypeName}' because the body is null or empty"))),
                        });
                    }

                    responseTry.Try.Add(new ReturnStatement(resultVariable));
                }
            }

            return(m);
        private static MethodDeclaration GenerateBuildUrlMethod(ClassDeclaration clientClass, Method method, TypeReference requestType)
        {
            var m = clientClass.AddMember(new MethodDeclaration(method.MethodGroup.Name + '_' + GetMethodName(method) + "_BuildUrl"));

            m.Modifiers  = Modifiers.Private | Modifiers.Static;
            m.ReturnType = typeof(string);

            // Method body
            m.Statements = new StatementCollection();

            var urlVariable = new VariableDeclarationStatement(typeof(string), "url");

            m.Statements.Add(urlVariable);

            // 1. Create url from parameters
            var parameters = method.Parameters.Where(p => GetParameterLocation(method, p) == ParameterLocation.Url).ToList();

            if (parameters.Any())
            {
                var requestArgument = m.AddArgument("request", requestType);

                // [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>")]
                m.CustomAttributes.Add(new CustomAttribute(typeof(SuppressMessageAttribute))
                {
                    Arguments =
                    {
                        new CustomAttributeArgument(new LiteralExpression("Reliability")),
                        new CustomAttributeArgument(new LiteralExpression("CA2000:Dispose objects before losing scope")),
                        new CustomAttributeArgument("Justification",                                                     new LiteralExpression("The rule doesn't understand ref struct")),
                    },
                });

                var segments   = GetSegments(method.UrlTemplate);
                var urlBuilder = new VariableDeclarationStatement(
                    WellKnownTypes.UrlBuilderTypeReference, "urlBuilder",
                    new NewObjectExpression(WellKnownTypes.UrlBuilderTypeReference));
                var urlUsingStatement = new UsingStatement()
                {
                    Statement = urlBuilder,
                    Body      = new StatementCollection(),
                };
                var usingStatements = urlUsingStatement.Body;
                m.Statements.Add(urlUsingStatement);

                foreach (var segment in segments)
                {
                    if (segment[0] == ':')
                    {
                        var param = parameters.SingleOrDefault(p => p.Name == segment[1..]);
                        if (param == null)
                        {
                            throw new InvalidOperationException($"Parameter '{segment}' is not mapped for method '{method.UrlTemplate}'");
                        }

                        AddParameter(param, parameterDelimiterVariable: null, encoded: true);
                        parameters.Remove(param);
                    }
                    else if (segment[0] == '*')
                    {
                        var param = parameters.SingleOrDefault(p => p.Name == segment[1..]);
                        if (param == null)
                        {
                            throw new InvalidOperationException($"Parameter '{segment}' is not mapped for method '{method.UrlTemplate}'");
                        }

                        AddParameter(param, parameterDelimiterVariable: null, encoded: false);
                        parameters.Remove(param);
                    }
                    else if (segment.StartsWith("[.", StringComparison.Ordinal))
                    {
                        var param = parameters.SingleOrDefault(p => p.Name == segment[2..^ 1]);
                        if (param == null)
                        {
                            throw new InvalidOperationException($"Parameter '{segment}' is not mapped for method '{method.UrlTemplate}'");
                        }

                        AddParameter(param, parameterDelimiterVariable: null, encoded: false, prefix: ".");
                        parameters.Remove(param);
                    }
                    else
                    {
                        usingStatements.Add(urlBuilder.CreateInvokeMethodExpression("Append", new LiteralExpression(segment)));
                    }
                }

                if (parameters.Any())
                {
                    var separator = new VariableDeclarationStatement(typeof(char), "separator", new LiteralExpression('?'));
                    usingStatements.Add(separator);

                    foreach (var param in parameters)
                    {
                        AddParameter(param, separator, encoded: true);
                    }
                }

                usingStatements.Add(new AssignStatement(urlVariable, urlBuilder.CreateInvokeMethodExpression("ToString")));

                void AddParameter(MethodParameter param, VariableDeclarationStatement parameterDelimiterVariable, bool encoded, string prefix = null)
                {
                    var appendParameterMethodName = encoded ? "AppendParameter" : "AppendRawParameter";

                    void AddSeparator(StatementCollection statements)
                    {
                        if (parameterDelimiterVariable != null)
                        {
                            statements.Add(urlBuilder.CreateInvokeMethodExpression("Append", parameterDelimiterVariable));
                            statements.Add(new AssignStatement(parameterDelimiterVariable, new LiteralExpression('&')));

                            if (!param.Options.HasFlag(MethodParameterOptions.CustomMapping))
                            {
                                statements.Add(urlBuilder.CreateInvokeMethodExpression("Append", new LiteralExpression(param.Name + '=')));
                            }
                        }
                    }

                    void AppendPrefix(StatementCollection statements)
                    {
                        if (!string.IsNullOrEmpty(prefix))
                        {
                            statements.Add(urlBuilder.CreateInvokeMethodExpression("Append", prefix));
                        }
                    }

                    if (param.Type.IsParameterEntity)
                    {
                        var propertyName      = param.Type.ParameterEntity.FinalType == ModelRef.Object ? "ValueAsString" : "Value";
                        var hasValueCondition = new ConditionStatement
                        {
                            Condition      = CreatePropertyReference().CreateMemberReferenceExpression(nameof(Nullable <int> .HasValue)),
                            TrueStatements = new StatementCollection(),
                        };

                        AddSeparator(hasValueCondition.TrueStatements);
                        AppendPrefix(hasValueCondition.TrueStatements);
                        var parameterValue = FormatValue(param.Type, CreatePropertyReference().CreateInvokeMethodExpression("GetValueOrDefault").CreateMemberReferenceExpression(propertyName));

                        if (param.Options.HasFlag(MethodParameterOptions.CustomMapping))
                        {
                            hasValueCondition.TrueStatements.Add(urlBuilder
                                                                 .CreateInvokeMethodExpression(
                                                                     "AppendParameter",
                                                                     param.Name,
                                                                     parameterValue));
                        }
                        else
                        {
                            hasValueCondition.TrueStatements.Add(urlBuilder
                                                                 .CreateInvokeMethodExpression(
                                                                     appendParameterMethodName,
                                                                     parameterValue));
                        }

                        urlUsingStatement.Body.Add(hasValueCondition);
                    }
                    else
                    {
                        var isValueType = param.Type.IsValueType && !param.Type.IsCollection;

                        var hasValueCondition = new ConditionStatement
                        {
                            Condition = isValueType ? CreatePropertyReference().CreateMemberReferenceExpression("HasValue") :
                                        new UnaryExpression(UnaryOperator.Not,
                                                            new MethodInvokeExpression(
                                                                new MemberReferenceExpression(new TypeReference(typeof(object)), "ReferenceEquals"),
                                                                CreatePropertyReference(), LiteralExpression.Null())),
                            TrueStatements = new StatementCollection(),
                        };

                        AddSeparator(hasValueCondition.TrueStatements);
                        AppendPrefix(hasValueCondition.TrueStatements);

                        Expression parameterValue = isValueType ? CreatePropertyReference().CreateInvokeMethodExpression("GetValueOrDefault") : CreatePropertyReference();

                        if (param.Options.HasFlag(MethodParameterOptions.CustomMapping))
                        {
                            hasValueCondition.TrueStatements.Add(urlBuilder
                                                                 .CreateInvokeMethodExpression(
                                                                     "AppendParameter",
                                                                     param.Name,
                                                                     parameterValue));
                        }
                        else
                        {
                            var appendMethod = new MethodInvokeExpression(
                                new MemberReferenceExpression(urlBuilder, appendParameterMethodName),
                                FormatValue(param.Type, parameterValue));

                            hasValueCondition.TrueStatements.Add(appendMethod);
                        }

                        urlUsingStatement.Body.Add(hasValueCondition);
                    }

                    MemberReferenceExpression CreatePropertyReference()
                    {
                        return(requestArgument.CreateMemberReferenceExpression(ToPropertyName(param.Name)));
                    }
                }
            }
            else
            {
                m.Statements.Add(new AssignStatement(urlVariable, new LiteralExpression(method.UrlTemplate)));
            }

            m.Statements.Add(new ReturnStatement(urlVariable));
            return(m);
        }