public ParameterResolution(IParameterSymbol parameterSymbol, TypeResolver typeResolver, ResolutionContext context, Options options) { var p = parameterSymbol; var type = p.Type; //it it is generic type, try get some constraint type var cTypes = (type as ITypeParameterSymbol)?.ConstraintTypes; if (cTypes.HasValue) { type = cTypes.Value.First(); } var attrs = p.GetAttributes(); //Removing tuple support...it' not worth it //var hasNamedTupleAttr = attrs.Any(a => a.AttributeClass.Name == nameof(NamedTupleAttribute)); var res = typeResolver.Resolve(type, context /*, hasNamedTupleAttr*/); this.Name = p.Name; this.FromName = p.Name; this.SearchRelayFormat = this.Name; var fromAttr = attrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("From")); // if (fromAttr != null) { switch (fromAttr.AttributeClass.Name) { case "FromUriAttribute": case "FromQueryAttribute": case "FromBodyAttribute": case "FromRouteAttribute": this.From = (ParameterFromKind)typeof(ParameterFromKind).GetField(fromAttr.AttributeClass.Name.Replace("Attribute", "")).GetValue(null); switch (fromAttr.AttributeClass.Name) { case "FromUriAttribute": case "FromQueryAttribute": KeyValuePair <string, TypedConstant>?nameArg = fromAttr.NamedArguments.ToList().FirstOrDefault(na => na.Key == "Name"); if (nameArg.HasValue) { var tConst = nameArg.Value.Value; if (tConst.Value != null) { this.FromName = tConst.Value.ToString(); this.SearchRelayFormat = $"{this.FromName}: {this.Name}"; } } break; } break; } } //Check if it is a Model being used to catch query/route parameters if (type.IsReferenceType) { var props = new List <string>(); var outProps = new List <string>(); var hasModifications = false; List <ISymbol> members = new List <ISymbol>(); var t = type; while (t != null) { members.AddRange(t.GetMembers()); t = t.BaseType; } foreach (var m in members) { if (m.Kind != SymbolKind.Field && m.Kind != SymbolKind.Property) { continue; } if (m.DeclaredAccessibility != Accessibility.Public) { continue; } if (((m as IFieldSymbol)?.IsConst).GetValueOrDefault()) { continue; } var name = m.Name; if (!options.KeepPropsCase && !((m as IFieldSymbol)?.IsConst).GetValueOrDefault()) { name = name.ToCamelCase(); } if (m is IPropertySymbol) { //allProps.Add(m.Name); var propAttrs = m.GetAttributes(); var propFromAttr = propAttrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("From")); if (propFromAttr != null) { switch (propFromAttr.AttributeClass.Name) { case "FromRouteAttribute": hasModifications = true; //ignoredQueryProps.Add(m.Name); break; case "FromUriAttribute": case "FromQueryAttribute": KeyValuePair <string, TypedConstant>?nameArg = propFromAttr.NamedArguments.ToList().FirstOrDefault(na => na.Key == "Name"); if (nameArg.HasValue) { var tConst = nameArg.Value.Value; if (tConst.Value != null) { //renamedQueryProps.Add(p.Name, tConst.Value.ToString()); outProps.Add($"{tConst.Value.ToString()}: {this.Name}.{name}"); hasModifications = true; } } break; default: props.Add($"{name}: {this.Name}.{name}"); break; } } else { props.Add($"{name}: {this.Name}.{name}"); } } } if (hasModifications) { this.SearchRelayFormat = ""; if (props.Any()) { //Pensar melhor, no asp.net podemos colocar como parametros varias models com props de nomes iguais. //Por esse motivo fazemos o obj: {} //Ao mesmo tempo, isso é uma modelagem esquisita de api. Talvez devemos dar preferencia mesmo para a segunda opção //onde fica tudo na "raiz". Além disso, não testei ainda o comportamento do asp.net quando multiplos parametros //que clasheiam sujas props //O maior motivo, é que no caso de uma model que possui alguns itens na raiz, ficando ora model.coisa e $coisa2 //por ex, o asp.net se perde em seu modelbinding, considerando apenas no model.<algo>. //this.SearchRelayFormat = $"...({this.Name} ? {{ {this.FromName}: {{ {string.Join(", ", props)} }} }} : {{}})"; this.SearchRelayFormat = $"...({this.Name} ? {{ {string.Join(", ", props)} }} : {{}})"; } if (outProps.Any()) { if (props.Any()) { //Add comma this.SearchRelayFormat += ", "; } this.SearchRelayFormat += $"...({this.Name} ? {{{string.Join(", ", outProps)}}} : {{}})"; } } } string typeName = res.Declaration; if (TsEnum.IsEnum(type)) { if (res.IsEnum) { var enumNames = string .Join( " | ", type.GetMembers() .Where(m => m.Kind == SymbolKind.Field) .Select(m => $"'{m.Name}'")); if (!string.IsNullOrEmpty(enumNames)) { typeName = $"{typeName} | {enumNames}"; } } } this.Type = res; this.IsOptional = p.IsOptional; this.Signature = $"{p.Name}{(p.IsOptional ? "?" : "")}: {typeName}" + (res.IsNullable ? " | null" : ""); this.Ignore = p.GetAttributes().Any(a => a.AttributeClass.Name == "FromServices"); }
//Abstrafting public override OutputFileAbstraction GetAbstraction() { //If external if (this.HasCustomMap) { return(null); } //Static if (TypeSymbol.IsStatic) { return(null); } var abstraction = new ModelAbstraction(); abstraction.Path = AbstractPath; abstraction.Fields = new List <FieldAbstraction>(); abstraction.AllFields = new List <FieldAbstraction>(); var context = new ResolutionContext(this); abstraction.Type = TypeResolver.Resolve(TypeSymbol, context, true); //Members var currentTypeSymbol = TypeSymbol; HashSet <string> existingFields = new HashSet <string>(); do { foreach (var m in currentTypeSymbol.GetMembers()) { if (existingFields.Contains(m.Name)) { continue; } if (m.Kind != SymbolKind.Field && m.Kind != SymbolKind.Property) { continue; } if (m.IsStatic) { continue; } if (m.DeclaredAccessibility != Accessibility.Public) { continue; } var fieldAbstraction = new FieldAbstraction(); var prop = m as IPropertySymbol; var isNullable = TypeResolver.IsNullable(prop.Type) || !prop.Type.IsValueType; var name = Options.KeepPropsCase ? m.Name : m.Name.ToCamelCase(); var summary = prop.GetDocumentationCommentXml(); if (!string.IsNullOrWhiteSpace(summary)) { summary = summary.Split("<summary>").Last().Split("</summary>").First().Trim(); fieldAbstraction.Summary = summary; } var typeResolution = TypeResolver.Resolve(prop.Type, context); fieldAbstraction.IsNullable = isNullable; fieldAbstraction.Name = name; fieldAbstraction.TypeDeclaration = typeResolution.Declaration; fieldAbstraction.TypeResolution = typeResolution; //fieldAbstraction.Attributes = m.GetAttributes().Select(a => new AttributeAbstraction { // ConstructedFrom = a.AttributeClass.ConstructedFrom.ToString(), // NamedArguments = a.NamedArguments // .Select(na => new KeyValuePair<string, NamedArgumentAbstraction>( // na.Key, // new NamedArgumentAbstraction // { // Kind = na.Value.Kind.ToString(), // Value = na.Value.Value, // IsNull = na.Value.IsNull // } // )) // .ToList() //}).ToList(); fieldAbstraction.Attributes = new List <AttributeAbstraction>(); foreach (var attr in m.GetAttributes()) { var attrAbstraction = new AttributeAbstraction(); //attrAbstraction.ArgumentsExpressions = new List<string>(); attrAbstraction.Arguments = new List <AttributeArgumentAbstraction>(); if (attr.ApplicationSyntaxReference == null) //From assembly { attrAbstraction.Name = attr.AttributeClass.Name; if (attrAbstraction.Name.EndsWith("Attribute")) { attrAbstraction.Name = attrAbstraction.Name.Substring(0, attrAbstraction.Name.Length - 9); } foreach (var cArg in attr.ConstructorArguments) { attrAbstraction.Arguments.Add(new AttributeArgumentAbstraction { Value = cArg.Value.ToString() }); //attrAbstraction.ArgumentsExpressions.Add(cArg.Value.ToString()); } foreach (var nArg in attr.NamedArguments) { attrAbstraction.Arguments.Add(new AttributeArgumentAbstraction { //IsNull = nArg.Value.IsNull, //Kind = nArg.Value.Kind.ToString(), Name = nArg.Key, Value = nArg.Value.Value.ToString(), }); //attrAbstraction.ArgumentsExpressions.Add($"{nArg.Key} = {nArg.Value.Value.ToString()}"); } } else //From Code { var attrSyntax = (attr.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax); attrAbstraction.Name = attrSyntax.Name.ToString(); //attrAbstraction.NamedArguments = new List<KeyValuePair<string, NamedArgumentAbstraction>>(); //attrAbstraction.NamedArguments = new Dictionary<string, NamedArgumentAbstraction>(); if (attrSyntax.ArgumentList != null) { foreach (var arg in attrSyntax.ArgumentList.Arguments) { if (arg.NameEquals != null) { attrAbstraction.Arguments.Add(new AttributeArgumentAbstraction { Name = arg.NameEquals.Name.Identifier.ValueText, Value = arg.Expression.ToFullString() }); } else { } //attrAbstraction.ArgumentsExpressions.Add(arg.ToFullString()); attrAbstraction.Arguments.Add(new AttributeArgumentAbstraction { Value = arg.ToFullString() }); } } } fieldAbstraction.Attributes.Add(attrAbstraction); } abstraction.AllFields.Add(fieldAbstraction); if (currentTypeSymbol == TypeSymbol) { abstraction.Fields.Add(fieldAbstraction); } } currentTypeSymbol = currentTypeSymbol.BaseType; } while (currentTypeSymbol != null); abstraction.Imports = context.GetImports(); return(abstraction); }
private (string file, string content) GenerateOutputModule() { //var hasConsts = false; //var subClasses = new List<INamedTypeSymbol>(); var sb = new StringBuilder(); var context = new ResolutionContext(this); var level = 0; if (!TypeSymbol.IsStatic) { string inheritance = ""; if (TypeSymbol.BaseType != null && TypeSymbol.BaseType.SpecialType != SpecialType.System_Object) { var inheritanceTypeResolution = TypeResolver.Resolve(TypeSymbol.BaseType, context); if (inheritanceTypeResolution.IsAny) { if (inheritanceTypeResolution.Declaration.Contains("/*")) { inheritance = $"/*extends {inheritanceTypeResolution.Declaration.Replace("any/*", "").Replace("*/", "")}*/"; } } else { inheritance = $"extends {inheritanceTypeResolution.Declaration} "; } } string genericArguments = ""; if (TypeSymbol.TypeArguments.Any()) { genericArguments = $"<{string.Join(", ", TypeSymbol.TypeArguments.Select(t => t.Name))}>"; } sb.AppendLine(level, $"export interface {ClassName}{genericArguments} {inheritance}{{"); foreach (var m in TypeSymbol.GetMembers()) { if (m.Kind == SymbolKind.NamedType) { //subClasses.Add(m as INamedTypeSymbol); continue; } if (m.IsStatic) { //if (m.Kind == SymbolKind.Field && (m as IFieldSymbol).IsConst) //{ // hasConsts = true; //} continue; } if (m.Kind != SymbolKind.Property) { continue; } if (m.DeclaredAccessibility != Accessibility.Public) { continue; } var prop = m as IPropertySymbol; var isNullable = TypeResolver.IsNullable(prop.Type) || !prop.Type.IsValueType; var name = Options.KeepPropsCase ? m.Name : m.Name.ToCamelCase(); var summary = prop.GetDocumentationCommentXml(); if (!string.IsNullOrWhiteSpace(summary)) { summary = summary.Split("<summary>").Last().Split("</summary>").First().Trim(); sb.AppendLine(level + 1, "/**"); var lines = summary.Split(System.Environment.NewLine); foreach (var l in lines) { sb.AppendLine(level + 1, $"*{l.Trim()}"); } sb.AppendLine(level + 1, "**/"); } sb.AppendLine(level + 1, $"{name}{(isNullable ? "?" : "")}: {TypeResolver.Resolve(prop.Type, context).Declaration};"); } sb.AppendLine(level, "}"); //if (hasConsts) //{ // sb.AppendLine(); //} } //else //{ // hasConsts = true; //} ////Static part //if (hasConsts // //removing const support for now // //If we enable it again, we should merge these members with KeysAndNames... // && false // ) { // //https://github.com/Microsoft/TypeScript/issues/17372 // //Currently we can't merge namespaces and const enums. // //This is supposed to be a bug. // //Besides, even if this is fixed, I think enums will never be mergeable with interfaces, // //so I think it will be always necessary to discriminate with something like $. // //$ can't be used in C# classes, so will never happen a conflict. // //We transpile C# Consts to TypeScript const enums because this is currently // //the only way to inline values. Consts values direclty inside namespaces/modules are not inlined... // //---R: Now, supporting only scoped types, maybe values dont need to be inlined... // sb.AppendLine(level, $"export class {ClassName} {{"); // level++; // foreach (var m in TypeSymbol.GetMembers()) { // var fieldSymbol = (m as IFieldSymbol); // if (fieldSymbol != null && fieldSymbol.IsConst) { // //Consts names should not be changed, they are commonly uppercased both in client and server... // var name = m.Name; // sb.AppendLine(level, $"static readonly {name} = {JsonConvert.SerializeObject(fieldSymbol.ConstantValue)};"); // } // } // level--; // sb.AppendLine(level, "}"); //} level--; AppendKeysAndNames(sb); sb.Insert(0, context.GetImportsText()); string content = sb.ToString(); return(OutputFilePath, content); }
public Model(INamedTypeSymbol modelType, TypeResolver typeResolver, Options options) : base(modelType, typeResolver, options) { }
public OutputFileAbstraction GetAbstraction() { var context = new ResolutionContext(this); var serviceAbstraction = new ServiceAbstraction(); serviceAbstraction.Path = AbstractPath; serviceAbstraction.ClassName = ClassName; serviceAbstraction.ControllerName = TypeSymbol.Name; serviceAbstraction.Actions = new List <ActionAbstraction>(); //Resolve endpoint var routeAttr = TypeSymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.Name == "Route" || a.AttributeClass.Name.ToString() == "RoutePrefix"); var arg = (routeAttr.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax).ArgumentList.Arguments[0].ToString().Replace("\"", ""); var path = arg.Replace("[controller]", ControllerName.ToCamelCase()); serviceAbstraction.Endpoint = path; //Actions var existingMethods = new Dictionary <string, int>(); foreach (var m in TypeSymbol.GetMembers()) { if (m.Kind == SymbolKind.NamedType) { continue; } if (m.DeclaredAccessibility != Accessibility.Public) { continue; } if (m.IsStatic) { continue; } if (m.Kind != SymbolKind.Method) { continue; } if (m.IsImplicitlyDeclared) { continue; } if (!m.IsDefinition) { continue; } var mtd = m as IMethodSymbol; if (m.Name == ".ctor") { continue; } var actionAbstraction = new ActionAbstraction(); serviceAbstraction.Actions.Add(actionAbstraction); var mtdAttrs = mtd.GetAttributes(); var returnType = TypeResolver.Resolve(mtd.ReturnType as ITypeSymbol, context); var returnTypeName = returnType.Declaration; //Not marked actions will accept posts var httpMethod = "Post"; string action = ""; //Get http method from method name pattern if (mtd.Name.StartsWith("Get")) { httpMethod = "Get"; } if (mtd.Name.StartsWith("Post")) { httpMethod = "Post"; } if (mtd.Name.StartsWith("Put")) { httpMethod = "Put"; } if (mtd.Name.StartsWith("Delete")) { httpMethod = "Delete"; } if (mtd.Name.StartsWith("Patch")) { httpMethod = "Patch"; } var methodName = mtd.Name.ToCamelCase(); if (existingMethods.ContainsKey(methodName)) { existingMethods[methodName]++; methodName = $"{methodName}_{existingMethods[methodName]}"; } else { existingMethods.Add(methodName, 0); } actionAbstraction.FunctionName = methodName; var httpAttr = mtdAttrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("Http")); var routeMethodAttr = mtdAttrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("Route")); //If has Http attribute if (httpAttr != null) { httpMethod = httpAttr.AttributeClass.Name.Substring(4); //Check if it contains route info var args = (httpAttr.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax)?.ArgumentList?.Arguments; if (args != null && args.Value.Count > 0) { action = args.Value[0].ToString().Replace("\"", ""); } } //Check if contains route attr if (routeMethodAttr != null) { var args = (routeMethodAttr.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax).ArgumentList.Arguments; if (args.Count > 0) { action = args[0].ToString().Replace("\"", ""); } } //Replace route variables action = action.Replace("[action]", methodName); var regex = new Regex(@"\{(?<paramName>\w+)(:\w+(\(.*?\))?)?\??}"); action = regex.Replace(action, match => { return($"${{{match.Groups["paramName"].Value}}}"); }); //Resolve how parameters are sent var pendingParameters = new List <ParameterResolution>(); var parameterResolutions = mtd.Parameters.Select(p => new ParameterResolution(p, TypeResolver, context, Options)).Where(p => !p.Ignore); foreach (var pr in parameterResolutions) { //[FromRoute] if (action.Contains($"{{{pr.Name}}}")) { //Casting to any because encodeURIComponent expects string action = action.Replace($"{{{pr.Name}}}", $"{{encodeURIComponent(<any>{pr.Name})}}"); continue; } //[FromBody] if (pr.From == ParameterFromKind.FromBody) { actionAbstraction.BodyParameterName = pr.Name; continue; } pendingParameters.Add(pr); } var strParameters = string.Join(", ", parameterResolutions.Select(pr => pr.Signature)); actionAbstraction.Parameters = parameterResolutions.Select(p => new ParameterAbstraction { Name = p.Name, IsOptional = p.IsOptional, //TypeDescription = p.TypeDescription Type = p.Type }).ToList(); actionAbstraction.ReturnType = returnType; //action actionAbstraction.ActionName = action; //httpMethod actionAbstraction.HttpMethod = httpMethod; actionAbstraction.SearchParametersNames = new List <string>(); //Body switch (httpMethod) { case "Put": case "Patch": case "Post": break; default: actionAbstraction.BodyParameterName = null; break; } //Search actionAbstraction.SearchParametersNames = pendingParameters.Select(pr => pr.Name).ToList(); } serviceAbstraction.Imports = context.GetImports(); return(serviceAbstraction); }
public (string file, string content) GenerateOutput() { var sb = new StringBuilder(); var context = new ResolutionContext(this); sb.AppendLine("import { WebTypedCallInfo, WebTypedFunction, WebTypedInvoker } from '@guimabdo/webtyped-common';"); string genericReturnType = "Promise"; if (Options.GenericReturnType != null) { if (!string.IsNullOrWhiteSpace(Options.GenericReturnType.Name)) { genericReturnType = Options.GenericReturnType.Name; } if (!string.IsNullOrWhiteSpace(Options.GenericReturnType.Module)) { sb.AppendLine($"import {{ {Options.GenericReturnType.Name} }} from '{Options.GenericReturnType.Module}';"); } } var routeAttr = TypeSymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.Name == "Route" || a.AttributeClass.Name.ToString() == "RoutePrefix"); var arg = (routeAttr.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax).ArgumentList.Arguments[0].ToString().Replace("\"", ""); var path = arg.Replace("[controller]", ControllerName.ToCamelCase()); int level = 0; if (Options.Inject?.BeforeServiceClass != null) { foreach (var line in Options.Inject.BeforeServiceClass) { sb.AppendLine(line); } } sb.AppendLine(level, $"export class {ClassName} {{"); sb.AppendLine(level, $" static readonly controllerName = '{TypeSymbol.Name}';"); sb.AppendLine(level, $" private api = '{path}';"); sb.AppendLine(level, " constructor(private invoker: WebTypedInvoker) {}"); var existingMethods = new Dictionary <string, int>(); List <string> typeAliases = new List <string>(); foreach (var m in TypeSymbol.GetMembers()) { if (m.Kind == SymbolKind.NamedType) { continue; } if (m.DeclaredAccessibility != Accessibility.Public) { continue; } if (m.IsStatic) { continue; } if (m.Kind != SymbolKind.Method) { continue; } if (m.IsImplicitlyDeclared) { continue; } if (!m.IsDefinition) { continue; } var mtd = m as IMethodSymbol; if (m.Name == ".ctor") { continue; } var mtdAttrs = mtd.GetAttributes(); var returnType = TypeResolver.Resolve(mtd.ReturnType as ITypeSymbol, context); var returnTypeName = returnType.Declaration; //Not marked actions will accept posts var httpMethod = "Post"; string action = ""; //Get http method from method name pattern if (mtd.Name.StartsWith("Get")) { httpMethod = "Get"; } if (mtd.Name.StartsWith("Post")) { httpMethod = "Post"; } if (mtd.Name.StartsWith("Put")) { httpMethod = "Put"; } if (mtd.Name.StartsWith("Delete")) { httpMethod = "Delete"; } if (mtd.Name.StartsWith("Patch")) { httpMethod = "Patch"; } var methodName = mtd.Name.ToCamelCase(); if (existingMethods.ContainsKey(methodName)) { existingMethods[methodName]++; methodName = $"{methodName}_{existingMethods[methodName]}"; } else { existingMethods.Add(methodName, 0); } var httpAttr = mtdAttrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("Http")); var routeMethodAttr = mtdAttrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("Route")); //If has Http attribute if (httpAttr != null) { httpMethod = httpAttr.AttributeClass.Name.Substring(4); //Check if it contains route info var args = (httpAttr.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax)?.ArgumentList?.Arguments; if (args != null && args.Value.Count > 0) { action = args.Value[0].ToString().Replace("\"", ""); } } //Check if contains route attr if (routeMethodAttr != null) { var args = (routeMethodAttr.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax).ArgumentList.Arguments; if (args.Count > 0) { action = args[0].ToString().Replace("\"", ""); } } //Replace route variables action = action.Replace("[action]", methodName); var regex = new Regex(@"\{(?<paramName>\w+)(:\w+(\(.*?\))?)?\??}"); action = regex.Replace(action, match => { return($"${{{match.Groups["paramName"].Value}}}"); }); //Resolve how parameters are sent var pendingParameters = new List <ParameterResolution>(); var parameterResolutions = mtd.Parameters.Select(p => new ParameterResolution(p, TypeResolver, context, Options)).Where(p => !p.Ignore); var bodyParam = "null"; foreach (var pr in parameterResolutions) { //[FromRoute] if (action.Contains($"{{{pr.Name}}}")) { //Casting to any because encodeURIComponent expects string action = action.Replace($"{{{pr.Name}}}", $"{{encodeURIComponent(<any>{pr.Name})}}"); continue; } //[FromBody] if (pr.From == ParameterFromKind.FromBody) { bodyParam = pr.Name; continue; } pendingParameters.Add(pr); } var strParameters = string.Join(", ", parameterResolutions.Select(pr => pr.Signature)); var upperMethodName = methodName[0].ToString().ToUpper() + methodName.Substring(1); typeAliases.Add($"export type {upperMethodName}Parameters = {{{strParameters}{(parameterResolutions.Any() ? ", " : "")}_wtKind: '{upperMethodName}' }};"); typeAliases.Add($"export interface {upperMethodName}CallInfo extends WebTypedCallInfo<{upperMethodName}Parameters, {returnTypeName}> {{ kind: '{upperMethodName}'; }}"); typeAliases.Add($"export type {upperMethodName}FunctionBase = ({strParameters}) => {genericReturnType}<{returnTypeName}>;"); typeAliases.Add($"export interface {upperMethodName}Function extends WebTypedFunction<{upperMethodName}Parameters, {returnTypeName}>, {upperMethodName}FunctionBase {{}}"); sb.AppendLine(level + 1, $"{methodName}: {ClassName}.{upperMethodName}Function = ({strParameters}) : {genericReturnType}<{returnTypeName}> => {{"); sb.AppendLine(level + 2, $"return this.invoker.invoke({{"); //info sb.AppendLine(level + 4, $"returnTypeName: '{returnTypeName}',"); sb.AppendLine(level + 4, $"kind: '{upperMethodName}',"); sb.AppendLine(level + 4, $"func: this.{methodName},"); sb.AppendLine(level + 4, $"parameters: {{ {string.Join(", ", parameterResolutions.Select(p => p.Name))}{(parameterResolutions.Any() ? ", " : "")}_wtKind: '{upperMethodName}' }}"); sb.AppendLine(level + 3, "},"); //api sb.AppendLine(level + 3, $"this.api,"); //action sb.AppendLine(level + 3, $"`{action}`,"); //httpMethod sb.AppendLine(level + 3, $"`{httpMethod.ToLower()}`,"); //Body switch (httpMethod) { case "Put": case "Patch": case "Post": sb.AppendLine(level + 3, $"{bodyParam},"); break; default: sb.AppendLine(level + 3, "undefined,"); break; } //Search var search = pendingParameters.Any() ? $"{{ {string.Join(", ", pendingParameters.Select(pr => pr.SearchRelayFormat))} }}" : "undefined"; sb.AppendLine(level + 3, $"{search},"); //Expects var genericReturnTypeDescriptor = Options.GenericReturnType ?? new ClientType { Name = "Promise" }; var genericReturnTypeDescriptorJson = JsonConvert.SerializeObject(genericReturnTypeDescriptor, new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() } }); sb.AppendLine(level + 3, $"{genericReturnTypeDescriptorJson}"); sb.AppendLine(level + 2, ");"); sb.AppendLine(level + 1, "};"); } sb.AppendLine(level, "}"); sb.AppendLine(level, $"export declare namespace {ClassName} {{"); typeAliases.ForEach(ta => sb.AppendLine(level + 1, ta)); sb.AppendLine(level, "}"); sb.Insert(0, context.GetImportsText()); string content = sb.ToString(); return(OutputFilePath, content); }