コード例 #1
0
        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");
        }
コード例 #2
0
        //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);
        }
コード例 #3
0
        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);
        }
コード例 #4
0
 public Model(INamedTypeSymbol modelType, TypeResolver typeResolver, Options options) : base(modelType, typeResolver, options)
 {
 }
コード例 #5
0
        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);
        }
コード例 #6
0
        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);
        }