public TypeResolution Resolve( ITypeSymbol typeSymbol, ResolutionContext context, bool fillInheritance = false ) { var type = typeSymbol as INamedTypeSymbol; var result = new TypeResolution(); result.ConstructedFrom = type?.ConstructedFrom?.ToString(); //Normalize type identifier if (!string.IsNullOrWhiteSpace(result.ConstructedFrom)) { var splitted = result.ConstructedFrom.Split('<'); var baseName = splitted[0]; if (splitted.Length > 1) { splitted = splitted[1].Split(','); //Normalize constructedFrom. when analyzing compiled assemblies, generic args are omitted result.ConstructedFrom = baseName + "<" + new StringBuilder().Append(',', splitted.Length - 1) + ">"; } } //Get generics if (type != null && type.IsGenericType) { result.GenericArguments = type .TypeArguments .Select(t => t is INamedTypeSymbol ? /* When type is defined */ (object)Resolve(t as INamedTypeSymbol, context) //Just the argument name : t.Name ) .ToList(); } //Base Type if (fillInheritance && type != null && type.BaseType != null && type.BaseType.SpecialType != SpecialType.System_Object) { result.Inherits = Resolve( type.BaseType, context, //Avoid creating unecessary extra import int the current context false); } //User custom type mapping if (Options.CustomMap != null && result.ConstructedFrom != null && Options.CustomMap.ContainsKey(result.ConstructedFrom)) { //Found definition with this full type name var clientType = Options.CustomMap[result.ConstructedFrom]; var externalModule = clientType.Module; var externalName = clientType.Name; if (string.IsNullOrWhiteSpace(externalModule)) { result.Declaration = externalName; } else { var alias = context.GetAliasByModule(externalModule); result.ModuleAlias = alias; result.Declaration = $"{alias}.{externalName}"; } result.IsKnown = true; return(result); } //Generic part of a generic class if (typeSymbol is ITypeParameterSymbol) { result.Declaration = typeSymbol.Name; return(result); } result.IsNullable = IsNullable(typeSymbol); var tsType = TypeFiles.ContainsKey(typeSymbol) ? TypeFiles[typeSymbol] //When inheriting from another generic model : TypeFiles.ContainsKey(typeSymbol.OriginalDefinition) ? TypeFiles[typeSymbol.OriginalDefinition] : null; if (tsType != null) { //result.TsType = tsType; result.IsEnum = tsType is TsEnum; result.IsKnown = true; result.Declaration = tsType.ClassName; if (tsType is TsModelBase && tsType != context.Target //auto-reference ) { var tsModel = tsType as TsModelBase; var c = new Uri("C:\\", UriKind.Absolute); var uriOther = new Uri(c, new Uri(tsModel.OutputFilePath, UriKind.Relative)); var uriMe = new Uri(c, new Uri(context.Target.OutputFilePath, UriKind.Relative)); var module = uriMe.MakeRelativeUri(uriOther).ToString(); module = module.Substring(0, module.Length - 3); if (module[0] != '.') { module = "./" + module; } var alias = context.GetAliasByModule(module); result.ModuleAlias = alias; result.Declaration = $"{alias}.{result.Declaration}"; } //When inheriting from another generic model if (type.IsGenericType) { var gp_ = GetGenericPart(type, result.Declaration, context); result.Declaration = $"{result.Declaration}{gp_.genericPart}"; } return(result); } //Array if (typeSymbol is IArrayTypeSymbol) { var arrTypeSymbol = typeSymbol as IArrayTypeSymbol; //This generic type does not really exists, just the base type "System.Array", but it will help generators easily identify arrays result.ConstructedFrom = "System.Array<>"; result.GenericArguments = new List <object> { Resolve(arrTypeSymbol.ElementType, context) }; if (arrTypeSymbol.ElementType.SpecialType == SpecialType.System_Byte) { result.Declaration = "string"; } else { var elementTypeRes = Resolve(arrTypeSymbol.ElementType, context); result.Declaration = $"Array<{elementTypeRes.Declaration}>"; } return(result); } //tuples if (type.IsTupleType) { var tupleElements = type.TupleElements .Select(te => new { field = (Options.KeepPropsCase ? te.Name : te.Name.ToCamelCase()), tupleField = (Options.KeepPropsCase ? te.CorrespondingTupleField.Name : te.CorrespondingTupleField.Name.ToCamelCase()), type = Resolve(te.Type as INamedTypeSymbol, context).Declaration }); var tupleProps = tupleElements .Select(te => $"/** field:{te.field} */{te.tupleField}: {te.type}"); result.Declaration = $"{{{string.Join(", ", tupleProps)}}}"; return(result); } //string name = type.Name; var members = type.GetTypeMembers(); string typeName = type.Name; //In case of nested types var parent = type.ContainingType; while (parent != null) { //Check if parent type is controller > service var parentName = parent.Name; //Adjust to check prerequisites if (Service.IsService(parent)) { //parentName = parentName.Replace("Controller", "Service"); parentName = parentName.Replace("Controller", Options.ServiceSuffix); } //For now, we'll just check if ends with "Controller" suffix //if (parentName.EndsWith("Controller")) { // parentName = parentName.Replace("Controller", "Service"); //} typeName = $"{parentName}.{typeName}"; parent = parent.ContainingType; } //Change type to ts type var tsTypeName = ToTsTypeName(type, context); //If contains "{" or "}" then it was converted to anonymous type, so no need to do anything else. if (tsTypeName.Contains("{")) { result.Declaration = tsTypeName; return(result); } var gp = GetGenericPart(type, tsTypeName, context); result.Declaration = $"{gp.modifiedTsTypeName}{gp.genericPart}"; return(result); }
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"); }