From cc0ac8d8ee355a9c24e307131641c7b587b51b95 Mon Sep 17 00:00:00 2001 From: Mathieu Lafontaine Date: Tue, 31 Oct 2023 09:28:50 -0400 Subject: [PATCH 1/4] Support for TS constructor generation --- .../TypeGen.Core/Generator/Generator.cs | 78 ++++++++++++++++++- .../Generator/GeneratorOptions.cs | 2 +- .../Generator/Services/ITemplateService.cs | 7 +- .../Generator/Services/TemplateService.cs | 37 ++++++++- src/TypeGen/TypeGen.Core/Templates/Class.tpl | 2 +- .../Templates/ClassConstructor.tpl | 3 + .../Templates/ClassConstructorArgument.tpl | 1 + .../Templates/ClassConstructorAssignment.tpl | 1 + .../Templates/ClassDefaultExport.tpl | 2 +- .../TypeAnnotations/TsConstructorAttribute.cs | 19 +++++ src/TypeGen/TypeGen.Core/TypeGen.Core.csproj | 10 ++- .../Constructor/ConstructorGenerationTests.cs | 19 +++++ .../ClassWithConstructorMatchingMembers.cs | 57 ++++++++++++++ .../Expected/test-constructors-mismatch.ts | 12 +++ .../Constructor/Expected/test-constructors.ts | 17 ++++ .../Expected/test-empty-constructors.ts | 19 +++++ .../Expected/test-record-constructors.ts | 10 +++ .../TestingUtils/GenerationTestBase.cs | 7 +- .../TypeGen.FileContentTest.csproj | 3 + 19 files changed, 291 insertions(+), 15 deletions(-) create mode 100644 src/TypeGen/TypeGen.Core/Templates/ClassConstructor.tpl create mode 100644 src/TypeGen/TypeGen.Core/Templates/ClassConstructorArgument.tpl create mode 100644 src/TypeGen/TypeGen.Core/Templates/ClassConstructorAssignment.tpl create mode 100644 src/TypeGen/TypeGen.Core/TypeAnnotations/TsConstructorAttribute.cs create mode 100644 src/TypeGen/TypeGen.FileContentTest/Constructor/ConstructorGenerationTests.cs create mode 100644 src/TypeGen/TypeGen.FileContentTest/Constructor/Entities/ClassWithConstructorMatchingMembers.cs create mode 100644 src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors-mismatch.ts create mode 100644 src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors.ts create mode 100644 src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-empty-constructors.ts create mode 100644 src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-record-constructors.ts diff --git a/src/TypeGen/TypeGen.Core/Generator/Generator.cs b/src/TypeGen/TypeGen.Core/Generator/Generator.cs index 35b56a69..a452dea0 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Generator.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Generator.cs @@ -4,8 +4,11 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; using System.Threading.Tasks; using TypeGen.Core.Conversion; +using TypeGen.Core.Converters; using TypeGen.Core.Extensions; using TypeGen.Core.Generator.Context; using TypeGen.Core.Generator.Services; @@ -428,6 +431,9 @@ private IEnumerable GenerateClass(Type type, ExportTsClassAttribute clas } string importsText = _tsContentGenerator.GetImportsText(type, outputDir); + + string constructorsText = GetClassConstructorsText(type); + string propertiesText = GetClassPropertiesText(type); // generate the file content @@ -445,8 +451,8 @@ private IEnumerable GenerateClass(Type type, ExportTsClassAttribute clas var tsDoc = GetTsDocForType(type); var content = _typeService.UseDefaultExport(type) ? - _templateService.FillClassDefaultExportTemplate(importsText, tsTypeName, tsTypeNameFirstPart, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, Options.FileHeading) : - _templateService.FillClassTemplate(importsText, tsTypeName, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, Options.FileHeading); + _templateService.FillClassDefaultExportTemplate(importsText, tsTypeName, tsTypeNameFirstPart, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, constructorsText, Options.FileHeading) : + _templateService.FillClassTemplate(importsText, tsTypeName, extendsText, implementsText, propertiesText, tsDoc, customHead, customBody, constructorsText, Options.FileHeading); // write TypeScript file FileContentGenerated?.Invoke(this, new FileContentGeneratedArgs(type, filePath, content)); @@ -644,6 +650,74 @@ private void LogClassPropertyWarnings(MemberInfo memberInfo) Logger.Log($"TsOptionalAttribute used for a class property ({memberInfo.DeclaringType?.FullName}.{memberInfo.Name}). The attribute will be ignored.", LogLevel.Warning); } + private string GetClassConstructorsText(Type type) + { + var constructorsText = ""; + var constructors = type.GetConstructors(); + + //var isRecord = ((TypeInfo)type).DeclaredProperties.FirstOrDefault(x => x.Name == "EqualityContract")?.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is object; + + var classHasTsConstructorAttribute = type.GetCustomAttributes(typeof(TsConstructorAttribute)).Any(); + + var members = type.GetTsExportableMembers(_metadataReaderFactory.GetInstance()).Select(memberInfo => + { + var nameAttribute = _metadataReaderFactory.GetInstance().GetAttribute(memberInfo); + var memberName = nameAttribute?.Name ?? Options.PropertyNameConverters.Convert(memberInfo.Name, memberInfo); + var typeName = _typeService.GetTsTypeName(memberInfo); + Type memberType = null; + + switch (memberInfo.MemberType) + { + case MemberTypes.Field: + memberType = ((FieldInfo)memberInfo).FieldType; + break; + case MemberTypes.Property: + memberType = ((PropertyInfo)memberInfo).PropertyType; + break; + } + + return new { memberName = memberInfo.Name, memberType, tsName = memberName, tsType = typeName }; + }); + + var tsMemberTypes = members.ToDictionary(x => x.tsName, x => x.tsType); + + //if ctor params all match member names and types or is a Record + var matchedConstructors = constructors.Where(ctor => + (classHasTsConstructorAttribute || ctor.GetCustomAttributes(typeof(TsConstructorAttribute)).Any()) && + ctor.GetParameters().All(p => members.Any(m => m.memberName.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase) && m.memberType == p.ParameterType))).ToArray(); + + //if single empty constructor, skip + if (matchedConstructors.Length == 1 && matchedConstructors[0].GetParameters().Length == 0) + { + return constructorsText; + } + + constructorsText += matchedConstructors.Aggregate(constructorsText, (current, ctorInfo) => current + GetClassConstructorText(ctorInfo, tsMemberTypes)); + + return constructorsText; + } + + private string GetClassConstructorText(ConstructorInfo ctor, Dictionary tsMemberTypes) + { + string argumentsText = ""; + string assignmentsText = ""; + + var parameters = ctor.GetParameters(); + + foreach (var param in parameters) + { + var matchingKey = tsMemberTypes.Keys.FirstOrDefault(k => k.Equals(param.Name, StringComparison.InvariantCultureIgnoreCase)); + + argumentsText += _templateService.FillClassConstructorArgumentTemplate(matchingKey, tsMemberTypes[matchingKey]); + assignmentsText += _templateService.FillClassConstructorAssignmentTemplate(matchingKey, matchingKey); + } + + argumentsText = argumentsText.TrimEnd(',', ' '); + assignmentsText = RemoveLastLineEnding(assignmentsText); + + return _templateService.FillClassConstructorTemplate(argumentsText, assignmentsText); + } + /// /// Gets TypeScript class properties definition source code /// diff --git a/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs b/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs index 1aaacd4f..5d1c79eb 100644 --- a/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs +++ b/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; using TypeGen.Core.Converters; diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs b/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs index d196ffd1..cb790e18 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs @@ -4,8 +4,8 @@ namespace TypeGen.Core.Generator.Services { internal interface ITemplateService { - string FillClassTemplate(string imports, string name, string extends, string implements, string properties, string tsDoc, string customHead, string customBody, string fileHeading = null); - string FillClassDefaultExportTemplate(string imports, string name, string exportName, string extends, string implements, string properties, string tsDoc, string customHead, string customBody, string fileHeading = null); + string FillClassTemplate(string imports, string name, string extends, string implements, string properties, string tsDoc, string customHead, string customBody, string constructorsText, string fileHeading = null); + 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); string FillClassPropertyTemplate(string modifiers, string name, string type, IEnumerable typeUnions, bool isOptional, string tsDoc, string defaultValue = null); string FillInterfaceTemplate(string imports, string name, string extends, string properties, string tsDoc, string customHead, string customBody, string fileHeading = null); 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 string GetExtendsText(string name); string GetExtendsText(IEnumerable names); string GetImplementsText(IEnumerable names); + string FillClassConstructorTemplate(string arguments, string assignments); + string FillClassConstructorArgumentTemplate(string argumentName, string argumentType); + string FillClassConstructorAssignmentTemplate(string memberName, string argumentName); } } \ No newline at end of file diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs b/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs index 3ca1759e..646498b3 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs @@ -33,6 +33,9 @@ internal class TemplateService : ITemplateService private readonly string _indexTemplate; private readonly string _indexExportTemplate; private readonly string _headingTemplate; + private readonly string _classConstructorTemplate; + private readonly string _classConstructorArgumentTemplate; + private readonly string _classConstructorAssignmentTemplate; private GeneratorOptions GeneratorOptions => _generatorOptionsProvider.GeneratorOptions; @@ -58,10 +61,13 @@ public TemplateService(IInternalStorage internalStorage, IGeneratorOptionsProvid _indexTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.Index.tpl"); _indexExportTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.IndexExport.tpl"); _headingTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.Heading.tpl"); + _classConstructorTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassConstructor.tpl"); + _classConstructorArgumentTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassConstructorArgument.tpl"); + _classConstructorAssignmentTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassConstructorAssignment.tpl"); } public string FillClassTemplate(string imports, string name, string extends, string implements, string properties, - string tsDoc, string customHead, string customBody, string fileHeading = null) + string tsDoc, string customHead, string customBody, string constructorsText, string fileHeading = null) { if (fileHeading == null) fileHeading = _headingTemplate; @@ -74,11 +80,12 @@ public string FillClassTemplate(string imports, string name, string extends, str .Replace(GetTag("tsDoc"), tsDoc) .Replace(GetTag("customHead"), customHead) .Replace(GetTag("customBody"), customBody) - .Replace(GetTag("fileHeading"), fileHeading); + .Replace(GetTag("fileHeading"), fileHeading) + .Replace(GetTag("constructors"), constructorsText); } public string FillClassDefaultExportTemplate(string imports, string name, string exportName, string extends, string implements, - string properties, string tsDoc, string customHead, string customBody, string fileHeading = null) + string properties, string tsDoc, string customHead, string customBody, string constructorsText, string fileHeading = null) { if (fileHeading == null) fileHeading = _headingTemplate; @@ -92,7 +99,8 @@ public string FillClassDefaultExportTemplate(string imports, string name, string .Replace(GetTag("tsDoc"), tsDoc) .Replace(GetTag("customHead"), customHead) .Replace(GetTag("customBody"), customBody) - .Replace(GetTag("fileHeading"), fileHeading); + .Replace(GetTag("fileHeading"), fileHeading) + .Replace(GetTag("constructors"), constructorsText); } public string FillClassPropertyTemplate(string modifiers, string name, string type, IEnumerable typeUnions, @@ -236,6 +244,27 @@ public string FillIndexExportTemplate(string filename) return ReplaceSpecialChars(_indexExportTemplate) .Replace(GetTag("filename"), filename); } + + public string FillClassConstructorTemplate(string arguments, string assignments) + { + return ReplaceSpecialChars(_classConstructorTemplate) + .Replace(GetTag("arguments"), arguments) + .Replace(GetTag("assignments"), assignments); + } + + public string FillClassConstructorArgumentTemplate(string argumentName, string argumentType) + { + return ReplaceSpecialChars(_classConstructorArgumentTemplate) + .Replace(GetTag("argName"), argumentName) + .Replace(GetTag("argType"), argumentType); + } + + public string FillClassConstructorAssignmentTemplate(string memberName, string argumentName) + { + return ReplaceSpecialChars(_classConstructorAssignmentTemplate) + .Replace(GetTag("memberName"), memberName) + .Replace(GetTag("argumentName"), argumentName); + } public string GetExtendsText(string name) => $" extends {name}"; public string GetExtendsText(IEnumerable names) => $" extends {string.Join(", ", names)}"; diff --git a/src/TypeGen/TypeGen.Core/Templates/Class.tpl b/src/TypeGen/TypeGen.Core/Templates/Class.tpl index f49dbd0f..c61127b8 100644 --- a/src/TypeGen/TypeGen.Core/Templates/Class.tpl +++ b/src/TypeGen/TypeGen.Core/Templates/Class.tpl @@ -1,3 +1,3 @@ $tg{fileHeading}$tg{imports}$tg{customHead}$tg{tsDoc}export class $tg{name}$tg{extends}$tg{implements} { -$tg{properties}$tg{customBody} +$tg{constructors}$tg{properties}$tg{customBody} } diff --git a/src/TypeGen/TypeGen.Core/Templates/ClassConstructor.tpl b/src/TypeGen/TypeGen.Core/Templates/ClassConstructor.tpl new file mode 100644 index 00000000..d6afcc41 --- /dev/null +++ b/src/TypeGen/TypeGen.Core/Templates/ClassConstructor.tpl @@ -0,0 +1,3 @@ +$tg{tab}constructor($tg{arguments}){ +$tg{assignments} +$tg{tab}} diff --git a/src/TypeGen/TypeGen.Core/Templates/ClassConstructorArgument.tpl b/src/TypeGen/TypeGen.Core/Templates/ClassConstructorArgument.tpl new file mode 100644 index 00000000..2a464a10 --- /dev/null +++ b/src/TypeGen/TypeGen.Core/Templates/ClassConstructorArgument.tpl @@ -0,0 +1 @@ +$tg{argName}: $tg{argType}, \ No newline at end of file diff --git a/src/TypeGen/TypeGen.Core/Templates/ClassConstructorAssignment.tpl b/src/TypeGen/TypeGen.Core/Templates/ClassConstructorAssignment.tpl new file mode 100644 index 00000000..c2ff5953 --- /dev/null +++ b/src/TypeGen/TypeGen.Core/Templates/ClassConstructorAssignment.tpl @@ -0,0 +1 @@ +$tg{tab}$tg{tab}this.$tg{memberName}: $tg{argumentName}; diff --git a/src/TypeGen/TypeGen.Core/Templates/ClassDefaultExport.tpl b/src/TypeGen/TypeGen.Core/Templates/ClassDefaultExport.tpl index 45a56f6b..839fae06 100644 --- a/src/TypeGen/TypeGen.Core/Templates/ClassDefaultExport.tpl +++ b/src/TypeGen/TypeGen.Core/Templates/ClassDefaultExport.tpl @@ -1,4 +1,4 @@ $tg{fileHeading}$tg{imports}$tg{customHead}$tg{tsDoc}class $tg{name}$tg{extends}$tg{implements} { -$tg{properties}$tg{customBody} +$tg{constructors}$tg{properties}$tg{customBody} } export default $tg{exportName}; diff --git a/src/TypeGen/TypeGen.Core/TypeAnnotations/TsConstructorAttribute.cs b/src/TypeGen/TypeGen.Core/TypeAnnotations/TsConstructorAttribute.cs new file mode 100644 index 00000000..9fa70c48 --- /dev/null +++ b/src/TypeGen/TypeGen.Core/TypeAnnotations/TsConstructorAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace TypeGen.Core.TypeAnnotations; + +/// +/// Identifies a TypeScript class constructor. +/// Constructor implementation will be generated if empty or if the arguments match members of the class. +/// ie: ctor(string name) => public string Name { get; set; } +/// generated as : +/// constructor(name: string) +/// { +/// this.Name: name; +/// } +/// +[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Class)] +public class TsConstructorAttribute: Attribute +{ + +} \ No newline at end of file diff --git a/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj b/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj index 57345189..51299660 100644 --- a/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj +++ b/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net7.0 TypeGen.Core.xml @@ -21,6 +21,9 @@ + + + @@ -39,7 +42,10 @@ - + + + + diff --git a/src/TypeGen/TypeGen.FileContentTest/Constructor/ConstructorGenerationTests.cs b/src/TypeGen/TypeGen.FileContentTest/Constructor/ConstructorGenerationTests.cs new file mode 100644 index 00000000..225bfd2c --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/Constructor/ConstructorGenerationTests.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using TypeGen.FileContentTest.TestingUtils; +using Xunit; + +namespace TypeGen.FileContentTest.Constructor; + +public class ConstructorGenerationTests: GenerationTestBase +{ + [Theory] + [InlineData(typeof(Entities.ClassWithConstructorMatchingMembers), "TypeGen.FileContentTest.Constructor.Expected.test-constructors.ts")] + [InlineData(typeof(Entities.ClassWithConstructorMatchingMembersMismatch), "TypeGen.FileContentTest.Constructor.Expected.test-constructors-mismatch.ts")] + [InlineData(typeof(Entities.ClassWithConstructorMatchingMembersAndEmpty), "TypeGen.FileContentTest.Constructor.Expected.test-empty-constructors.ts")] + [InlineData(typeof(Entities.MyRecord), "TypeGen.FileContentTest.Constructor.Expected.test-record-constructors.ts")] + public async Task TestConstructor(Type type, string expectedLocation) + { + await TestFromAssembly(type, expectedLocation); + } +} \ No newline at end of file diff --git a/src/TypeGen/TypeGen.FileContentTest/Constructor/Entities/ClassWithConstructorMatchingMembers.cs b/src/TypeGen/TypeGen.FileContentTest/Constructor/Entities/ClassWithConstructorMatchingMembers.cs new file mode 100644 index 00000000..45c0f4d9 --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/Constructor/Entities/ClassWithConstructorMatchingMembers.cs @@ -0,0 +1,57 @@ +using TypeGen.Core.TypeAnnotations; +using Xunit; + +namespace TypeGen.FileContentTest.Constructor.Entities; + +[ExportTsClass] +public class ClassWithConstructorMatchingMembers +{ + public string MyProp { get; set; } + public int myField; + public SubClass SubClass { get; set; } + + [TsConstructor] + public ClassWithConstructorMatchingMembers(string myProp, int myField, SubClass subClass) + { } +} + +[ExportTsClass] +public class ClassWithConstructorMatchingMembersMismatch +{ + public string MyProp { get; set; } + public int myField; + public SubClass SubClass { get; set; } + + [TsConstructor] + public ClassWithConstructorMatchingMembersMismatch(string myProp1, int myField, SubClass subClass) + { } +} + +[ExportTsClass] +[TsConstructor] +public class ClassWithConstructorMatchingMembersAndEmpty +{ + public string MyProp { get; set; } + public int myField; + public SubClass SubClass { get; set; } + + public ClassWithConstructorMatchingMembersAndEmpty(string myProp, int myField, SubClass subClass) + { } + + public ClassWithConstructorMatchingMembersAndEmpty() + { } +} + +[ExportTsClass] +[TsConstructor] +public record MyRecord(string Name); + +public class SubClass +{ + public string Name { get; set; } + + public SubClass(string name) + { + Name = name; + } +} \ No newline at end of file diff --git a/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors-mismatch.ts b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors-mismatch.ts new file mode 100644 index 00000000..82c4a6b4 --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors-mismatch.ts @@ -0,0 +1,12 @@ +/** + * This is a TypeGen auto-generated file. + * Any changes made to this file can be lost when this file is regenerated. + */ + +import { SubClass } from "./sub-class"; + +export class ClassWithConstructorMatchingMembersMismatch { + myField: number; + myProp: string; + subClass: SubClass; +} diff --git a/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors.ts b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors.ts new file mode 100644 index 00000000..f82a91ab --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-constructors.ts @@ -0,0 +1,17 @@ +/** + * This is a TypeGen auto-generated file. + * Any changes made to this file can be lost when this file is regenerated. + */ + +import { SubClass } from "./sub-class"; + +export class ClassWithConstructorMatchingMembers { + constructor(myProp: string, myField: number, subClass: SubClass){ + this.myProp: myProp; + this.myField: myField; + this.subClass: subClass; + } + myField: number; + myProp: string; + subClass: SubClass; +} diff --git a/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-empty-constructors.ts b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-empty-constructors.ts new file mode 100644 index 00000000..ae2a97c6 --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-empty-constructors.ts @@ -0,0 +1,19 @@ +/** + * This is a TypeGen auto-generated file. + * Any changes made to this file can be lost when this file is regenerated. + */ + +import { SubClass } from "./sub-class"; + +export class ClassWithConstructorMatchingMembersAndEmpty { + constructor(myProp: string, myField: number, subClass: SubClass){ + this.myProp: myProp; + this.myField: myField; + this.subClass: subClass; + } + constructor(){ + } + myField: number; + myProp: string; + subClass: SubClass; +} diff --git a/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-record-constructors.ts b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-record-constructors.ts new file mode 100644 index 00000000..1616b6b8 --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/Constructor/Expected/test-record-constructors.ts @@ -0,0 +1,10 @@ +/** + * This is a TypeGen auto-generated file. + * Any changes made to this file can be lost when this file is regenerated. + */ +export class MyRecord { + constructor(name: string){ + this.name: name; + } + name: string; +} diff --git a/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs b/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs index 27dee9eb..17eb9a26 100644 --- a/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs +++ b/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs @@ -9,11 +9,14 @@ namespace TypeGen.FileContentTest.TestingUtils; public class GenerationTestBase { - protected async Task TestFromAssembly(Type type, string expectedLocation) + protected async Task TestFromAssembly(Type type, string expectedLocation, GeneratorOptions generatorOptions = null) { var readExpectedTask = EmbededResourceReader.GetEmbeddedResourceAsync(expectedLocation); - var generator = new Generator(); + var generator = generatorOptions != null ? + new Generator(generatorOptions) : + new Generator(); + var interceptor = GeneratorOutputInterceptor.CreateInterceptor(generator); await generator.GenerateAsync(type.Assembly); diff --git a/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj b/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj index 93719d7e..f3d1ae06 100644 --- a/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj +++ b/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj @@ -98,6 +98,9 @@ + + + From ac6f2cab4c202d75022f8274a8baa1b9274b6104 Mon Sep 17 00:00:00 2001 From: Mathieu Lafontaine Date: Tue, 31 Oct 2023 09:33:12 -0400 Subject: [PATCH 2/4] fixed embedded file for unit test --- .../TypeGen.FileContentTest/TypeGen.FileContentTest.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj b/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj index f3d1ae06..c64b8d1e 100644 --- a/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj +++ b/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj @@ -118,6 +118,8 @@ + + From 69254c3f2acbde1424f1ebfe31561c696645b084 Mon Sep 17 00:00:00 2001 From: Mathieu Lafontaine Date: Wed, 1 Nov 2023 08:54:49 -0400 Subject: [PATCH 3/4] use TsMember name when matching constructor arguments --- .../TypeGen.Core/Generator/Generator.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/TypeGen/TypeGen.Core/Generator/Generator.cs b/src/TypeGen/TypeGen.Core/Generator/Generator.cs index a452dea0..df8279f5 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Generator.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Generator.cs @@ -676,15 +676,13 @@ private string GetClassConstructorsText(Type type) break; } - return new { memberName = memberInfo.Name, memberType, tsName = memberName, tsType = typeName }; - }); - - var tsMemberTypes = members.ToDictionary(x => x.tsName, x => x.tsType); + return new TsMemberInfo(memberInfo.Name, memberType, memberName, typeName); + }).ToArray(); //if ctor params all match member names and types or is a Record var matchedConstructors = constructors.Where(ctor => (classHasTsConstructorAttribute || ctor.GetCustomAttributes(typeof(TsConstructorAttribute)).Any()) && - ctor.GetParameters().All(p => members.Any(m => m.memberName.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase) && m.memberType == p.ParameterType))).ToArray(); + ctor.GetParameters().All(p => members.Any(m => m.DotNetMemberName.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase) && m.DotNetMemberType == p.ParameterType))).ToArray(); //if single empty constructor, skip if (matchedConstructors.Length == 1 && matchedConstructors[0].GetParameters().Length == 0) @@ -692,12 +690,12 @@ private string GetClassConstructorsText(Type type) return constructorsText; } - constructorsText += matchedConstructors.Aggregate(constructorsText, (current, ctorInfo) => current + GetClassConstructorText(ctorInfo, tsMemberTypes)); + constructorsText += matchedConstructors.Aggregate(constructorsText, (current, ctorInfo) => current + GetClassConstructorText(ctorInfo, members)); return constructorsText; } - private string GetClassConstructorText(ConstructorInfo ctor, Dictionary tsMemberTypes) + private string GetClassConstructorText(ConstructorInfo ctor, TsMemberInfo[] tsMemberTypes) { string argumentsText = ""; string assignmentsText = ""; @@ -706,10 +704,16 @@ private string GetClassConstructorText(ConstructorInfo ctor, Dictionary k.Equals(param.Name, StringComparison.InvariantCultureIgnoreCase)); + var matchingMemberInfo = tsMemberTypes.FirstOrDefault(k => k.DotNetMemberName.Equals(param.Name, StringComparison.InvariantCultureIgnoreCase)); - argumentsText += _templateService.FillClassConstructorArgumentTemplate(matchingKey, tsMemberTypes[matchingKey]); - assignmentsText += _templateService.FillClassConstructorAssignmentTemplate(matchingKey, matchingKey); + if(matchingMemberInfo == null) + { + //not all parameters have a matching member name, skip constructor building + return string.Empty; + } + + argumentsText += _templateService.FillClassConstructorArgumentTemplate(matchingMemberInfo.TsMemberName, matchingMemberInfo.TsMemberType); + assignmentsText += _templateService.FillClassConstructorAssignmentTemplate(matchingMemberInfo.TsMemberName, matchingMemberInfo.TsMemberName); } argumentsText = argumentsText.TrimEnd(',', ' '); From 63b497acb9c0c81565bb5c36bca6dfd8bcbace03 Mon Sep 17 00:00:00 2001 From: Mathieu Lafontaine Date: Fri, 3 Nov 2023 13:48:42 -0400 Subject: [PATCH 4/4] added missing TsMemberInfo file --- src/TypeGen/TypeGen.Core/Generator/TsMemberInfo.cs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/TypeGen/TypeGen.Core/Generator/TsMemberInfo.cs diff --git a/src/TypeGen/TypeGen.Core/Generator/TsMemberInfo.cs b/src/TypeGen/TypeGen.Core/Generator/TsMemberInfo.cs new file mode 100644 index 00000000..2d27042d --- /dev/null +++ b/src/TypeGen/TypeGen.Core/Generator/TsMemberInfo.cs @@ -0,0 +1,5 @@ +using System; + +namespace TypeGen.Core.Generator; + +public record TsMemberInfo(string DotNetMemberName, Type DotNetMemberType, string TsMemberName, string TsMemberType); \ No newline at end of file