public void SymbolUsage(Action <ISymbol> shallowUsage, Action <ISymbol> deepUsage)
        {
            DoTypeSymbol(deepUsage, TypeSymbol.BaseType);

            foreach (var symbol in TypeSymbol.AllInterfaces)
            {
                DoTypeSymbol(deepUsage, symbol);
            }

            AttributeDataSymbolUsage(TypeSymbol.GetAttributes(), deepUsage);

            foreach (var member in TypeSymbol.GetMembers())
            {
                AttributeDataSymbolUsage(member.GetAttributes(), deepUsage);

                var propertyMember = member as IPropertySymbol;
                var fieldMember    = member as IFieldSymbol;
                var methodMember   = member as IMethodSymbol;

                if (propertyMember != null)
                {
                    DoTypeSymbol(shallowUsage, propertyMember.Type);
                }

                if (fieldMember != null)
                {
                    DoTypeSymbol(shallowUsage, fieldMember.Type);
                }

                if (methodMember != null)
                {
                    DoTypeSymbol(shallowUsage, methodMember.ReturnType);
                    AttributeDataSymbolUsage(methodMember.GetReturnTypeAttributes(), deepUsage);
                    foreach (var parameter in methodMember.Parameters)
                    {
                        DoTypeSymbol(shallowUsage, parameter.Type);
                        AttributeDataSymbolUsage(parameter.GetAttributes(), deepUsage);
                    }
                }
            }
        }
Example #2
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);
        }
Example #3
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);
        }