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); }
/// <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); }
private static string GetTypeIdentifier(TsTypeSymbol type) { if (type.IsArray) { return($"{GetTypeIdentifier(type.ElementType)}[]"); } else { return(type.Name); } }
/// <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; }
/// <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); }
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); } }
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); }
/// <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)); }
/// <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); }
/// <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); }
/// <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);
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); } } }
/// <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; }
private static string GetFileName(TsTypeSymbol symbol) { return(symbol.Name + ".ts"); }
/// <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 _)); }