private string GenerateUniqueInterfaceName(TsTypeSymbol typeSymbol)
        {
            TsTypeSymbol unwrappedTypeSymbol = typeSymbol.UnwrapArray(out _);

            string preparedName = unwrappedTypeSymbol.Name;

            if (unwrappedTypeSymbol.IsInterface &&
                preparedName.StartsWith("I") &&
                preparedName.Length > 1)
            {
                preparedName = preparedName.Substring(1);
            }

            string originalInterfaceName = $"I{preparedName}Dto";

            if (this.dtoInterfaceNameTracker.Add(originalInterfaceName))
            {
                // The first name attempt is unique.
                return(originalInterfaceName);
            }

            string candidateName = originalInterfaceName;

            for (int i = 1; this.dtoInterfaceNameTracker.Contains(candidateName); i++)
            {
                candidateName = originalInterfaceName + i;
            }

            this.dtoInterfaceNameTracker.Add(candidateName);

            return(candidateName);
        }
        /// <summary>
        /// Determines if the specified <see cref="TsTypeSymbol"/> requires a data transfer
        /// object transformation.
        /// </summary>
        /// <param name="typeSymbol">The type symbol.</param>
        /// <returns><c>true</c> if a DTO transorm is required, <c>false</c> otherwise.</returns>
        /// <exception cref="ArgumentNullException">typeSymbol</exception>
        public static bool RequiresDtoTransform(TsTypeSymbol typeSymbol)
        {
            if (typeSymbol == null)
            {
                throw new ArgumentNullException(nameof(typeSymbol));
            }

            TsTypeSymbol unwrappedTypeSymbol = typeSymbol.UnwrapArray();

            if (unwrappedTypeSymbol.IsClass && unwrappedTypeSymbol.HasDtoInterface)
            {
                return(true);
            }

            if (dtoPropertyTypeSymbolMap.ContainsKey(unwrappedTypeSymbol))
            {
                return(true);
            }

            if (unwrappedTypeSymbol.Properties.Any(p => RequiresDtoTransform(p.Type)))
            {
                return(true);
            }

            if (unwrappedTypeSymbol.Base != null && RequiresDtoTransform(unwrappedTypeSymbol.Base))
            {
                return(true);
            }

            return(false);
        }
Пример #3
0
 /// <summary>
 /// Creates an array symbol.
 /// </summary>
 /// <param name="elementType">The type of the element.</param>
 /// <returns>A <see cref="TsTypeSymbol"/>.</returns>
 internal static TsTypeSymbol CreateArraySymbol(TsTypeSymbol elementType)
 {
     return(new TsTypeSymbol($"{elementType.Name}[]", TsSymbolKind.Array)
     {
         ElementType = elementType
     });
 }
        private TsPropertySymbol GetPropertySymbol(PropertyInfo propertyInfo, DtoTypeSymbolMap dtoSymbolMap)
        {
            TsPropertySymbol result = TsPropertySymbol.LoadFrom(propertyInfo, this.symbolLookup, this.options);

            TsTypeSymbol unwrappedTypeSymbol = result.Type.UnwrapArray(out int arrayRank);

            if (dtoPropertyTypeSymbolMap.TryGetValue(unwrappedTypeSymbol, out TsTypeSymbol replacementType))
            {
                // We need a type replacement.
                if (result.Type.IsArray)
                {
                    replacementType = TsTypeSymbol.CreateArraySymbol(replacementType, arrayRank);
                }

                result = new TsPropertySymbol(result.Name, replacementType, result.PropertyMetadata);
            }
            else if ((unwrappedTypeSymbol.IsClass || unwrappedTypeSymbol.IsInterface) &&
                     this.symbolLookup.ContainsType(unwrappedTypeSymbol.TypeMetadata.Type))
            {
                // We need a DTO Interface for this type.
                TsTypeSymbol dtoInterfaceType = this.GetOrCreateDtoInterface(unwrappedTypeSymbol, dtoSymbolMap);

                if (result.Type.IsArray)
                {
                    dtoInterfaceType = TsTypeSymbol.CreateArraySymbol(dtoInterfaceType, arrayRank);
                }

                result = new TsPropertySymbol(result.Name, dtoInterfaceType, result.PropertyMetadata);
            }

            return(result);
        }
        /// <summary>
        /// Writes the symbol dto transformation.
        /// </summary>
        /// <param name="propertySymbol">The property symbol.</param>
        /// <param name="valueAccessor">The value accessor.</param>
        /// <param name="variableName">The name of the variable to write.</param>
        /// <param name="interfaceTransformLookup">The interface transform lookup.</param>
        /// <param name="writer">The writer.</param>
        /// <exception cref="InvalidOperationException">The specified type does not require DTO transformation: {type.Name}</exception>
        /// <exception cref="NotSupportedException">Unable to write transformation for the specified type: {type.Name}</exception>
        public static void WriteSymbolDtoTransformation(TsPropertySymbol propertySymbol, string valueAccessor,
                                                        string variableName, DtoInterfaceTransformLookup interfaceTransformLookup, SourceWriter writer)
        {
            if (!RequiresDtoTransform(propertySymbol.Type))
            {
                throw new InvalidOperationException($"The specified type does not require DTO transformation: {propertySymbol.Type.Name}.");
            }

            bool         isArray    = propertySymbol.Type.IsArray;
            bool         isOptional = propertySymbol.IsOptional;
            TsTypeSymbol type       = propertySymbol.Type.UnwrapArray();

            writer.Write($"const {variableName} = ");

            if (isOptional)
            {
                writer.Write($"{valueAccessor} === {TsTypeSymbol.Undefined.Name} ? {TsTypeSymbol.Undefined.Name} : ");
            }

            if (type.IsInterface && type.HasDtoInterface)
            {
                DtoInterfaceTransformMetadata dtoInterfaceMetadata = interfaceTransformLookup[type.DtoInterface];

                if (isArray)
                {
                    writer.WriteLine($"{valueAccessor}.map({dtoInterfaceMetadata.TransformMethodAccessor});");
                }
                else
                {
                    writer.WriteLine($"{dtoInterfaceMetadata.TransformMethodAccessor}({valueAccessor});");
                }
            }
            else if (type.IsClass && type.HasDtoInterface)
            {
                if (isArray)
                {
                    writer.WriteLine($"{valueAccessor}.map({type.Name}.fromDto);");
                }
                else
                {
                    writer.WriteLine($"{type.Name}.fromDto({valueAccessor});");
                }
            }
            else if (type == TsTypeSymbol.Date)
            {
                if (isArray)
                {
                    writer.WriteLine($"{valueAccessor}.map(s => new Date(s));");
                }
                else
                {
                    writer.WriteLine($"new Date({valueAccessor});");
                }
            }
            else
            {
                throw new NotSupportedException($"Unable to write transformation for the specified type: {type.Name}.");
            }
        }
        private void MakeDtoInterfaces(TsTypeSymbol type, DtoTypeSymbolMap dtoSymbolMap)
        {
            if (!(type.TypeMetadata?.IsDto ?? false))
            {
                throw new ArgumentException("Can't create a DTO from the specified type symbol.", nameof(type));
            }

            this.GetOrCreateDtoInterface(type, dtoSymbolMap);
        }
Пример #7
0
 private static string GetTypeIdentifier(TsTypeSymbol type)
 {
     if (type.IsArray)
     {
         return($"{GetTypeIdentifier(type.ElementType)}[]");
     }
     else
     {
         return(type.Name);
     }
 }
Пример #8
0
 /// <summary>
 /// Initializes a new instance of the <see cref="TsTypeSymbol" /> class.
 /// </summary>
 /// <param name="name">The name.</param>
 /// <param name="symbolKind">The kind of symbol.</param>
 /// <param name="baseTypeSymbol">The base type symbol.</param>
 /// <param name="interfaceTypeSymbols">The interface type symbols.</param>
 /// <param name="propertySymbols">The property symbols.</param>
 /// <param name="typeMetadata">The type metadata.</param>
 internal TsTypeSymbol(string name, TsSymbolKind symbolKind, TsTypeSymbol baseTypeSymbol,
                       IReadOnlyList <TsTypeSymbol> interfaceTypeSymbols, IReadOnlyList <TsPropertySymbol> propertySymbols,
                       TsTypeMetadata typeMetadata)
 {
     this.Name         = name;
     this.Kind         = symbolKind;
     this.Base         = baseTypeSymbol;
     this.Interfaces   = interfaceTypeSymbols;
     this.Properties   = propertySymbols;
     this.TypeMetadata = typeMetadata;
 }
Пример #9
0
        /// <summary>
        /// Loads a <see cref="TsTypeSymbol" /> from the specified <see cref="TsTypeMetadata" /> using the <see cref="TsSymbolLookup" />.
        /// </summary>
        /// <param name="typeMetadata">The type metadata.</param>
        /// <param name="symbolLookup">The symbol lookup.</param>
        /// <param name="options">The options.</param>
        /// <returns>A <see cref="TsTypeSymbol" />.</returns>
        internal static TsTypeSymbol LoadFrom(TsTypeMetadata typeMetadata, TsSymbolLookup symbolLookup, ISymbolLoadOptions options)
        {
            if (typeMetadata.Type.IsGenericType)
            {
                throw new NotSupportedException("Generic types are not currently supported.");
            }

            TsTypeSymbol baseTypeSymbol = null;

            if (typeMetadata.Type.IsClass &&
                !typeMetadata.Flatten &&
                typeMetadata.Type.BaseType != null &&
                typeMetadata.Type.BaseType != typeof(object))
            {
                baseTypeSymbol = symbolLookup.ResolveSymbol(typeMetadata.Type.BaseType);
            }

            List <TsTypeSymbol> interfaceTypeSymbols = new List <TsTypeSymbol>();

            if (!typeMetadata.Flatten)
            {
                foreach (Type interfaceType in typeMetadata.Type.GetInterfaces())
                {
                    if (symbolLookup.TryResolveSymbol(interfaceType, out TsTypeSymbol interfaceTypeSymbol))
                    {
                        interfaceTypeSymbols.Add(interfaceTypeSymbol);
                    }
                }
            }

            List <TsPropertySymbol> propertySymbols = new List <TsPropertySymbol>();

            foreach (PropertyInfo property in typeMetadata.GetProperties())
            {
                propertySymbols.Add(TsPropertySymbol.LoadFrom(property, symbolLookup, options));
            }

            TsTypeSymbol symbol = new TsTypeSymbol
                                  (
                name: typeMetadata.Name,
                symbolKind: GetSymbolKind(typeMetadata),
                baseTypeSymbol,
                interfaceTypeSymbols,
                propertySymbols,
                typeMetadata
                                  );

            return(symbol);
        }
        private TsTypeSymbol MakeAndRegisterDtoInterface(TsTypeSymbol typeSymbol, DtoTypeSymbolMap dtoSymbolMap)
        {
            TsTypeSymbol unwrappedTypeSymbol = typeSymbol.UnwrapArray(out int rank);

            Type type = unwrappedTypeSymbol.TypeMetadata.Type;

            string interfaceName = this.GenerateUniqueInterfaceName(unwrappedTypeSymbol);

            List <TsTypeSymbol> interfaceTypeSymbols = unwrappedTypeSymbol.Interfaces
                                                       .Select(i => this.GetOrCreateDtoInterface(i, dtoSymbolMap))
                                                       .ToList();

            if (unwrappedTypeSymbol.Base != null)
            {
                // Inject the base DTO interface into the interfaces.
                interfaceTypeSymbols.Insert(0, this.GetOrCreateDtoInterface(unwrappedTypeSymbol.Base, dtoSymbolMap));
            }

            IReadOnlyList <TsPropertySymbol> propertySymbols = type
                                                               .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
                                                               .Select(p => this.GetPropertySymbol(p, dtoSymbolMap))
                                                               .Apply();

            if (type.IsClass)
            {
                // We need to remove any properties defined in interfaces
                // to avoid readonly collisions.
                ISet <string> interfacePropertyNames = this.GetInterfacePropertyNames(type);

                propertySymbols = propertySymbols
                                  .Where(p => !interfacePropertyNames.Contains(p.PropertyMetadata.Property.Name))
                                  .Apply();
            }

            TsTypeSymbol dtoInterfaceSymbol = new TsTypeSymbol(
                interfaceName,
                TsSymbolKind.Interface,
                baseTypeSymbol: null,
                interfaceTypeSymbols,
                propertySymbols,
                typeMetadata: null);

            unwrappedTypeSymbol.SetDtoInterface(dtoInterfaceSymbol);

            dtoSymbolMap.Add(unwrappedTypeSymbol, dtoInterfaceSymbol);

            return(dtoInterfaceSymbol);
        }
        private TsTypeSymbol GetOrCreateDtoInterface(TsTypeSymbol typeSymbol, DtoTypeSymbolMap dtoSymbolMap)
        {
            TsTypeSymbol unwrappedTypeSymbol = typeSymbol.UnwrapArray(out int rank);

            if (!dtoSymbolMap.TryGetValue(unwrappedTypeSymbol, out TsTypeSymbol dtoInterfaceSymbol))
            {
                dtoInterfaceSymbol = this.MakeAndRegisterDtoInterface(unwrappedTypeSymbol, dtoSymbolMap);
            }

            if (rank > 0)
            {
                return(TsTypeSymbol.CreateArraySymbol(dtoInterfaceSymbol, rank));
            }

            return(dtoInterfaceSymbol);
        }
Пример #12
0
        private void WriteClassDtoTransformMethod(SourceWriter writer, TsTypeSymbol symbol)
        {
            //InterfaceTransformLookup interfaceTransformLookup = this.BuildInterfaceTransformLookup(symbol);
            DtoInterfaceTransformLookup interfaceTransformLookup = DtoInterfaceTransformLookup.BuildLookup(symbol);

            writer.WriteLine();

            TsTypeSymbol dtoInterface = symbol.DtoInterface;

            writer.Write($"public static fromDto(dto: {dtoInterface.Name}): {symbol.Name} ");

            using (writer.Block())
            {
                var rawClassParameters = GetConstructorParameters(GetAllDistinctProperties(symbol))
                                         .Select(cp => new { cp.property, cp.parameterName, requiresTransform = cp.property.Type.RequiresDtoTransform() })
                                         .Apply();

                if (rawClassParameters.Any(x => x.requiresTransform))
                {
                    foreach (var parameter in rawClassParameters.Where(x => x.requiresTransform))
                    {
                        TsDtoTypeSymbolHelper.WriteSymbolDtoTransformation(
                            propertySymbol: parameter.property,
                            valueAccessor: $"dto.{parameter.property.Name}",
                            variableName: parameter.parameterName,
                            interfaceTransformLookup: interfaceTransformLookup,
                            writer);
                    }

                    writer.WriteLine();
                }

                // Write out the class constructor.
                var classParameters = rawClassParameters.Select(p => p.requiresTransform ? p.parameterName : "dto." + p.parameterName)
                                      .Apply();
                writer.Write($"return new {symbol.Name}(");
                this.WriteCommaSeparatedItems(writer, classParameters);
                writer.WriteLine(");");
            }

            // Write interface transform methods, if any.
            if (interfaceTransformLookup.Count > 0)
            {
                this.WriteClassDtoInterfaceTransfomMethods(writer, interfaceTransformLookup);
            }
        }
Пример #13
0
        private static IEnumerable <TsPropertySymbol> GetAllDistinctProperties(TsTypeSymbol symbol)
        {
            HashSet <TsPropertySymbol> propertySymbols = new HashSet <TsPropertySymbol>(
                symbol.Properties, TsPropertySymbolNameComparer.Instance);

            if (symbol.Base != null)
            {
                propertySymbols.AddRange(GetAllDistinctProperties(symbol.Base));
            }

            if (symbol.Interfaces.Count > 0)
            {
                propertySymbols.AddRange(symbol.Interfaces.SelectMany(i => GetAllDistinctProperties(i)));
            }

            return(propertySymbols);
        }
        /// <summary>
        /// Unwraps an array symbol.
        /// </summary>
        /// <param name="symbol">The symbol.</param>
        /// <param name="rank">The rank.</param>
        /// <returns><see cref="TsTypeSymbol"/>.</returns>
        public static TsTypeSymbol UnwrapArray(this TsTypeSymbol symbol, out int rank)
        {
            if (symbol == null)
            {
                throw new ArgumentNullException(nameof(symbol));
            }

            if (symbol.IsArray)
            {
                TsTypeSymbol result = UnwrapArray(symbol.ElementType, out int elementRank);
                rank = elementRank + 1;
                return(result);
            }

            rank = 0;
            return(symbol);
        }
Пример #15
0
        /// <summary>
        /// Tries to resolve the symbol.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="symbol">The symbol.</param>
        /// <returns><c>true</c> if the symbol was resolved, <c>false</c> otherwise.</returns>
        public bool TryResolveSymbol(Type type, out TsTypeSymbol symbol)
        {
            symbol = null;

            if (type.IsArray)
            {
                if (type.GetArrayRank() != 1)
                {
                    return(false);
                }

                if (!this.TryResolveSymbol(type.GetElementType(), out TsTypeSymbol elementSymbol))
                {
                    return(false);
                }

                symbol = TsTypeSymbol.CreateArraySymbol(elementSymbol);
                return(true);
            }

            if (type.IsConstructedGenericType)
            {
                Type typeDefinition = type.GetGenericTypeDefinition();
                if (typeDefinition == typeof(IEnumerable <>) ||
                    typeDefinition == typeof(ICollection <>) ||
                    typeDefinition == typeof(IList <>) ||
                    typeDefinition == typeof(IReadOnlyCollection <>) ||
                    typeDefinition == typeof(IReadOnlyList <>) ||
                    typeDefinition == typeof(List <>))
                {
                    Type genericType = type.GetGenericArguments().Single();

                    if (!this.TryResolveSymbol(genericType, out TsTypeSymbol elementSymbol))
                    {
                        return(false);
                    }

                    symbol = TsTypeSymbol.CreateArraySymbol(elementSymbol);
                    return(true);
                }
            }

            return(this.lookup.TryGetValue(type.UnwrapNullable(), out symbol));
        }
Пример #16
0
        /// <summary>
        /// Creates an array symbol.
        /// </summary>
        /// <param name="elementType">The type of the element.</param>
        /// <param name="rank">The rank.</param>
        /// <returns>A <see cref="TsTypeSymbol" />.</returns>
        /// <exception cref="ArgumentException">The rank must be greater than 0.</exception>
        internal static TsTypeSymbol CreateArraySymbol(TsTypeSymbol elementType, int rank)
        {
            if (rank <= 0)
            {
                throw new ArgumentException("The rank must be greater than 0.");
            }

            if (rank == 1)
            {
                return(CreateArraySymbol(elementType));
            }

            TsTypeSymbol result = elementType;

            for (int i = 0; i < rank; i++)
            {
                result = CreateArraySymbol(result);
            }

            return(result);
        }
Пример #17
0
        /// <summary>
        /// Resolves the type symbols for the specified <see cref="Type"/> objects.
        /// </summary>
        /// <param name="types">The types.</param>
        /// <returns>An <see cref="IReadOnlyList{T}"/> of <see cref="TsTypeSymbol"/>.</returns>
        public IReadOnlyList <TsTypeSymbol> ResolveTypeSymbols(IEnumerable <Type> types)
        {
            List <TsTypeSymbol>   results      = new List <TsTypeSymbol>();
            TsSymbolLookup        symbolLookup = new TsSymbolLookup();
            TsDtoTypeSymbolHelper dtoHelper    = new TsDtoTypeSymbolHelper(symbolLookup, this.options);

            TsDependencySortVisitor sortVisitor = new TsDependencySortVisitor();
            IReadOnlyList <Type>    sortedTypes = sortVisitor.Sort(types);

            foreach (Type type in sortedTypes)
            {
                TsTypeSymbol symbol = TsTypeSymbol.LoadFrom(type, symbolLookup, this.options);

                symbolLookup.Add(type, symbol);
                results.Add(symbol);
            }

            results.AddRange(dtoHelper.CreateAndConfigureDtoSymbols(results));

            return(results);
        }
Пример #18
0
 /// <summary>
 /// Adds an item to the lookup with the provided key and value.
 /// </summary>
 /// <param name="type">The <see cref="Type"/>.</param>
 /// <param name="symbol">The <see cref="TsTypeSymbol"/>.</param>
 public void Add(Type type, TsTypeSymbol symbol) => this.lookup.Add(type, symbol);
Пример #19
0
        private void WriteClass(SourceWriter writer, TsTypeSymbol symbol)
        {
            writer.Write("export ");

            if (symbol.IsAbstractClass)
            {
                writer.Write("abstract ");
            }

            writer.Write("class ").Write(symbol.Name);

            if (symbol.Base != null)
            {
                writer.Write(" extends ").Write(symbol.Base.Name);
            }

            if (symbol.Interfaces?.Any() ?? false)
            {
                writer
                .Write(" implements ")
                .Write(string.Join(", ", symbol.Interfaces.Select(i => i.Name)));
            }

            writer.Write(" ");

            using (writer.Block())
            {
                // Write the properties.
                foreach (TsPropertySymbol property in symbol.Properties)
                {
                    writer.Write("public ");

                    if (property.IsReadOnly)
                    {
                        writer.Write("readonly ");
                    }

                    writer.Write(property.Name);

                    if (property.IsOptional)
                    {
                        writer.Write("?");
                    }

                    writer.Write(": ").Write(GetPropertyTypeIdentifier(property));

                    writer.WriteLine(";");
                }

                if (symbol.Properties.Count > 0)
                {
                    writer.WriteLine();
                }

                // Write the constructor.
                var baseProperties    = symbol.Base?.Properties ?? Enumerable.Empty <TsPropertySymbol>();
                var basePropertyInfos = GetConstructorParameters(baseProperties).Apply();
                var propertyInfos     = GetConstructorParameters(symbol.Properties).Apply();
                var parameterInfos    = GetConstructorParameters(baseProperties.Concat(symbol.Properties)).Apply();

                IReadOnlyList <string> parameters = parameterInfos
                                                    .Select(p => $"{p.parameterName}{(p.property.IsOptional ? "?" : string.Empty)}: {GetPropertyTypeIdentifier(p.property)}")
                                                    .Apply();

                if (parameters.Count > 0)
                {
                    writer.Write("constructor(");

                    this.WriteCommaSeparatedItems(writer, parameters);

                    writer.Write(") ");
                    using (writer.Block())
                    {
                        if (symbol.Base != null)
                        {
                            // Make call to super(...).
                            writer.Write("super(");
                            this.WriteCommaSeparatedItems(writer, basePropertyInfos.Select(p => p.parameterName).Apply());
                            writer.WriteLine(");");

                            if (propertyInfos.Any())
                            {
                                writer.WriteLine();
                            }
                        }

                        foreach (var(property, parameterName) in propertyInfos)
                        {
                            writer.WriteLine($"this.{property.Name} = {parameterName};");
                        }
                    }
                }

                // Write the Dto creater if necessary
                bool writeDtoMethod = symbol.IsClass && !symbol.IsAbstractClass && symbol.HasDtoInterface;
                if (writeDtoMethod)
                {
                    this.WriteClassDtoTransformMethod(writer, symbol);
                }
            }
        }
Пример #20
0
 /// <summary>
 /// Sets the data transfer object interface symbol.
 /// </summary>
 /// <param name="dtoInterfaceSymbol">The dto interface symbol.</param>
 public void SetDtoInterface(TsTypeSymbol dtoInterfaceSymbol)
 {
     this.DtoInterface = dtoInterfaceSymbol;
 }
Пример #21
0
 private static string GetFileName(TsTypeSymbol symbol)
 {
     return(symbol.Name + ".ts");
 }
Пример #22
0
 /// <summary>
 /// Initializes a new instance of the <see cref="TsPropertySymbol" /> class.
 /// </summary>
 /// <param name="name">The name.</param>
 /// <param name="type">The type.</param>
 /// <param name="propertyMetadata">The property metadata.</param>
 /// <exception cref="ArgumentNullException"></exception>
 internal TsPropertySymbol(string name, TsTypeSymbol type, TsPropertyMetadata propertyMetadata)
 {
     this.Name             = name;
     this.Type             = type ?? throw new ArgumentNullException(nameof(type));
     this.PropertyMetadata = propertyMetadata ?? throw new ArgumentNullException(nameof(propertyMetadata));
 }
 /// <summary>
 /// Unwraps an array symbol.
 /// </summary>
 /// <param name="symbol">The symbol.</param>
 /// <returns><see cref="TsTypeSymbol"/>.</returns>
 public static TsTypeSymbol UnwrapArray(this TsTypeSymbol symbol)
 {
     return(UnwrapArray(symbol, out _));
 }