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); }