예제 #1
0
        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);
        }
예제 #2
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");
        }