Skip to content

Commit cc0ac8d

Browse files
committed
Support for TS constructor generation
1 parent 6f2f126 commit cc0ac8d

19 files changed

+291
-15
lines changed

src/TypeGen/TypeGen.Core/Generator/Generator.cs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
using System.IO;
55
using System.Linq;
66
using System.Reflection;
7+
using System.Runtime.CompilerServices;
8+
using System.Text;
79
using System.Threading.Tasks;
810
using TypeGen.Core.Conversion;
11+
using TypeGen.Core.Converters;
912
using TypeGen.Core.Extensions;
1013
using TypeGen.Core.Generator.Context;
1114
using TypeGen.Core.Generator.Services;
@@ -428,6 +431,9 @@ private IEnumerable<string> GenerateClass(Type type, ExportTsClassAttribute clas
428431
}
429432

430433
string importsText = _tsContentGenerator.GetImportsText(type, outputDir);
434+
435+
string constructorsText = GetClassConstructorsText(type);
436+
431437
string propertiesText = GetClassPropertiesText(type);
432438

433439
// generate the file content
@@ -445,8 +451,8 @@ private IEnumerable<string> GenerateClass(Type type, ExportTsClassAttribute clas
445451
var tsDoc = GetTsDocForType(type);
446452

447453
var content = _typeService.UseDefaultExport(type) ?
448-
_templateService.FillClassDefaultExportTemplate(importsText, tsTypeName, tsTypeNameFirstPart, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, Options.FileHeading) :
449-
_templateService.FillClassTemplate(importsText, tsTypeName, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, Options.FileHeading);
454+
_templateService.FillClassDefaultExportTemplate(importsText, tsTypeName, tsTypeNameFirstPart, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, constructorsText, Options.FileHeading) :
455+
_templateService.FillClassTemplate(importsText, tsTypeName, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, constructorsText, Options.FileHeading);
450456

451457
// write TypeScript file
452458
FileContentGenerated?.Invoke(this, new FileContentGeneratedArgs(type, filePath, content));
@@ -644,6 +650,74 @@ private void LogClassPropertyWarnings(MemberInfo memberInfo)
644650
Logger.Log($"TsOptionalAttribute used for a class property ({memberInfo.DeclaringType?.FullName}.{memberInfo.Name}). The attribute will be ignored.", LogLevel.Warning);
645651
}
646652

653+
private string GetClassConstructorsText(Type type)
654+
{
655+
var constructorsText = "";
656+
var constructors = type.GetConstructors();
657+
658+
//var isRecord = ((TypeInfo)type).DeclaredProperties.FirstOrDefault(x => x.Name == "EqualityContract")?.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is object;
659+
660+
var classHasTsConstructorAttribute = type.GetCustomAttributes(typeof(TsConstructorAttribute)).Any();
661+
662+
var members = type.GetTsExportableMembers(_metadataReaderFactory.GetInstance()).Select(memberInfo =>
663+
{
664+
var nameAttribute = _metadataReaderFactory.GetInstance().GetAttribute<TsMemberNameAttribute>(memberInfo);
665+
var memberName = nameAttribute?.Name ?? Options.PropertyNameConverters.Convert(memberInfo.Name, memberInfo);
666+
var typeName = _typeService.GetTsTypeName(memberInfo);
667+
Type memberType = null;
668+
669+
switch (memberInfo.MemberType)
670+
{
671+
case MemberTypes.Field:
672+
memberType = ((FieldInfo)memberInfo).FieldType;
673+
break;
674+
case MemberTypes.Property:
675+
memberType = ((PropertyInfo)memberInfo).PropertyType;
676+
break;
677+
}
678+
679+
return new { memberName = memberInfo.Name, memberType, tsName = memberName, tsType = typeName };
680+
});
681+
682+
var tsMemberTypes = members.ToDictionary(x => x.tsName, x => x.tsType);
683+
684+
//if ctor params all match member names and types or is a Record
685+
var matchedConstructors = constructors.Where(ctor =>
686+
(classHasTsConstructorAttribute || ctor.GetCustomAttributes(typeof(TsConstructorAttribute)).Any()) &&
687+
ctor.GetParameters().All(p => members.Any(m => m.memberName.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase) && m.memberType == p.ParameterType))).ToArray();
688+
689+
//if single empty constructor, skip
690+
if (matchedConstructors.Length == 1 && matchedConstructors[0].GetParameters().Length == 0)
691+
{
692+
return constructorsText;
693+
}
694+
695+
constructorsText += matchedConstructors.Aggregate(constructorsText, (current, ctorInfo) => current + GetClassConstructorText(ctorInfo, tsMemberTypes));
696+
697+
return constructorsText;
698+
}
699+
700+
private string GetClassConstructorText(ConstructorInfo ctor, Dictionary<string, string> tsMemberTypes)
701+
{
702+
string argumentsText = "";
703+
string assignmentsText = "";
704+
705+
var parameters = ctor.GetParameters();
706+
707+
foreach (var param in parameters)
708+
{
709+
var matchingKey = tsMemberTypes.Keys.FirstOrDefault(k => k.Equals(param.Name, StringComparison.InvariantCultureIgnoreCase));
710+
711+
argumentsText += _templateService.FillClassConstructorArgumentTemplate(matchingKey, tsMemberTypes[matchingKey]);
712+
assignmentsText += _templateService.FillClassConstructorAssignmentTemplate(matchingKey, matchingKey);
713+
}
714+
715+
argumentsText = argumentsText.TrimEnd(',', ' ');
716+
assignmentsText = RemoveLastLineEnding(assignmentsText);
717+
718+
return _templateService.FillClassConstructorTemplate(argumentsText, assignmentsText);
719+
}
720+
647721
/// <summary>
648722
/// Gets TypeScript class properties definition source code
649723
/// </summary>

src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Runtime.Serialization;
44
using TypeGen.Core.Converters;

src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ namespace TypeGen.Core.Generator.Services
44
{
55
internal interface ITemplateService
66
{
7-
string FillClassTemplate(string imports, string name, string extends, string implements, string properties, string tsDoc, string customHead, string customBody, string fileHeading = null);
8-
string FillClassDefaultExportTemplate(string imports, string name, string exportName, string extends, string implements, string properties, string tsDoc, string customHead, string customBody, string fileHeading = null);
7+
string FillClassTemplate(string imports, string name, string extends, string implements, string properties, string tsDoc, string customHead, string customBody, string constructorsText, string fileHeading = null);
8+
string FillClassDefaultExportTemplate(string imports, string name, string exportName, string extends, string implements, string properties, string tsDoc, string customHead, string customBody, string constructorsText, string fileHeading = null);
99
string FillClassPropertyTemplate(string modifiers, string name, string type, IEnumerable<string> typeUnions, bool isOptional, string tsDoc, string defaultValue = null);
1010
string FillInterfaceTemplate(string imports, string name, string extends, string properties, string tsDoc, string customHead, string customBody, string fileHeading = null);
1111
string FillInterfaceDefaultExportTemplate(string imports, string name, string exportName, string extends, string properties, string tsDoc, string customHead, string customBody, string fileHeading = null);
@@ -21,5 +21,8 @@ internal interface ITemplateService
2121
string GetExtendsText(string name);
2222
string GetExtendsText(IEnumerable<string> names);
2323
string GetImplementsText(IEnumerable<string> names);
24+
string FillClassConstructorTemplate(string arguments, string assignments);
25+
string FillClassConstructorArgumentTemplate(string argumentName, string argumentType);
26+
string FillClassConstructorAssignmentTemplate(string memberName, string argumentName);
2427
}
2528
}

src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ internal class TemplateService : ITemplateService
3333
private readonly string _indexTemplate;
3434
private readonly string _indexExportTemplate;
3535
private readonly string _headingTemplate;
36+
private readonly string _classConstructorTemplate;
37+
private readonly string _classConstructorArgumentTemplate;
38+
private readonly string _classConstructorAssignmentTemplate;
3639

3740
private GeneratorOptions GeneratorOptions => _generatorOptionsProvider.GeneratorOptions;
3841

@@ -58,10 +61,13 @@ public TemplateService(IInternalStorage internalStorage, IGeneratorOptionsProvid
5861
_indexTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.Index.tpl");
5962
_indexExportTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.IndexExport.tpl");
6063
_headingTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.Heading.tpl");
64+
_classConstructorTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassConstructor.tpl");
65+
_classConstructorArgumentTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassConstructorArgument.tpl");
66+
_classConstructorAssignmentTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassConstructorAssignment.tpl");
6167
}
6268

6369
public string FillClassTemplate(string imports, string name, string extends, string implements, string properties,
64-
string tsDoc, string customHead, string customBody, string fileHeading = null)
70+
string tsDoc, string customHead, string customBody, string constructorsText, string fileHeading = null)
6571
{
6672
if (fileHeading == null) fileHeading = _headingTemplate;
6773

@@ -74,11 +80,12 @@ public string FillClassTemplate(string imports, string name, string extends, str
7480
.Replace(GetTag("tsDoc"), tsDoc)
7581
.Replace(GetTag("customHead"), customHead)
7682
.Replace(GetTag("customBody"), customBody)
77-
.Replace(GetTag("fileHeading"), fileHeading);
83+
.Replace(GetTag("fileHeading"), fileHeading)
84+
.Replace(GetTag("constructors"), constructorsText);
7885
}
7986

8087
public string FillClassDefaultExportTemplate(string imports, string name, string exportName, string extends, string implements,
81-
string properties, string tsDoc, string customHead, string customBody, string fileHeading = null)
88+
string properties, string tsDoc, string customHead, string customBody, string constructorsText, string fileHeading = null)
8289
{
8390
if (fileHeading == null) fileHeading = _headingTemplate;
8491

@@ -92,7 +99,8 @@ public string FillClassDefaultExportTemplate(string imports, string name, string
9299
.Replace(GetTag("tsDoc"), tsDoc)
93100
.Replace(GetTag("customHead"), customHead)
94101
.Replace(GetTag("customBody"), customBody)
95-
.Replace(GetTag("fileHeading"), fileHeading);
102+
.Replace(GetTag("fileHeading"), fileHeading)
103+
.Replace(GetTag("constructors"), constructorsText);
96104
}
97105

98106
public string FillClassPropertyTemplate(string modifiers, string name, string type, IEnumerable<string> typeUnions,
@@ -236,6 +244,27 @@ public string FillIndexExportTemplate(string filename)
236244
return ReplaceSpecialChars(_indexExportTemplate)
237245
.Replace(GetTag("filename"), filename);
238246
}
247+
248+
public string FillClassConstructorTemplate(string arguments, string assignments)
249+
{
250+
return ReplaceSpecialChars(_classConstructorTemplate)
251+
.Replace(GetTag("arguments"), arguments)
252+
.Replace(GetTag("assignments"), assignments);
253+
}
254+
255+
public string FillClassConstructorArgumentTemplate(string argumentName, string argumentType)
256+
{
257+
return ReplaceSpecialChars(_classConstructorArgumentTemplate)
258+
.Replace(GetTag("argName"), argumentName)
259+
.Replace(GetTag("argType"), argumentType);
260+
}
261+
262+
public string FillClassConstructorAssignmentTemplate(string memberName, string argumentName)
263+
{
264+
return ReplaceSpecialChars(_classConstructorAssignmentTemplate)
265+
.Replace(GetTag("memberName"), memberName)
266+
.Replace(GetTag("argumentName"), argumentName);
267+
}
239268

240269
public string GetExtendsText(string name) => $" extends {name}";
241270
public string GetExtendsText(IEnumerable<string> names) => $" extends {string.Join(", ", names)}";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
$tg{fileHeading}$tg{imports}$tg{customHead}$tg{tsDoc}export class $tg{name}$tg{extends}$tg{implements} {
2-
$tg{properties}$tg{customBody}
2+
$tg{constructors}$tg{properties}$tg{customBody}
33
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
$tg{tab}constructor($tg{arguments}){
2+
$tg{assignments}
3+
$tg{tab}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$tg{argName}: $tg{argType},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$tg{tab}$tg{tab}this.$tg{memberName}: $tg{argumentName};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
$tg{fileHeading}$tg{imports}$tg{customHead}$tg{tsDoc}class $tg{name}$tg{extends}$tg{implements} {
2-
$tg{properties}$tg{customBody}
2+
$tg{constructors}$tg{properties}$tg{customBody}
33
}
44
export default $tg{exportName};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
3+
namespace TypeGen.Core.TypeAnnotations;
4+
5+
/// <summary>
6+
/// Identifies a TypeScript class constructor.
7+
/// Constructor implementation will be generated if empty or if the arguments match members of the class.
8+
/// ie: ctor(string name) => public string Name { get; set; }
9+
/// generated as :
10+
/// constructor(name: string)
11+
/// {
12+
/// this.Name: name;
13+
/// }
14+
/// </summary>
15+
[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Class)]
16+
public class TsConstructorAttribute: Attribute
17+
{
18+
19+
}

0 commit comments

Comments
 (0)