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