diff --git a/nuget-dotnetcli/dotnet-typegen.nuspec b/nuget-dotnetcli/dotnet-typegen.nuspec index 9a054666..14f91e8b 100644 --- a/nuget-dotnetcli/dotnet-typegen.nuspec +++ b/nuget-dotnetcli/dotnet-typegen.nuspec @@ -1,7 +1,7 @@ - dotnet-typegen + CIERA.dotnet-typegen 5.0.0 Jacek Burzynski Jacek Burzynski diff --git a/nuget/TypeGen.nuspec b/nuget/TypeGen.nuspec index 13d49ad9..f73074ce 100644 --- a/nuget/TypeGen.nuspec +++ b/nuget/TypeGen.nuspec @@ -1,10 +1,10 @@ - TypeGen + CIERA.TypeGen 5.0.0 Jacek Burzynski - Jacek Burzynski + NCMEC LICENSE https://github.com/jburzynski/TypeGen https://raw.githubusercontent.com/jburzynski/type-gen/master/docs/icon.png diff --git a/src/TypeGen/TypeGen.Cli.Test/CliSmokeTest.cs b/src/TypeGen/TypeGen.Cli.Test/CliSmokeTest.cs index 27fb6268..f4a63116 100644 --- a/src/TypeGen/TypeGen.Cli.Test/CliSmokeTest.cs +++ b/src/TypeGen/TypeGen.Cli.Test/CliSmokeTest.cs @@ -6,11 +6,16 @@ using System.Text; using FluentAssertions; using Xunit; +using Xunit.Abstractions; namespace TypeGen.Cli.Test { public class CliSmokeTest { + private readonly ITestOutputHelper logger; + + public CliSmokeTest(ITestOutputHelper output) => this.logger = output; + [Fact] public void Cli_should_finish_with_success() { @@ -19,8 +24,8 @@ public void Cli_should_finish_with_success() const string projectToGeneratePath = "../../../../TypeGen.FileContentTest"; const string cliFileName = "TypeGen.Cli.exe"; string[] cliPossibleDirectories = { - "../../../../TypeGen.Cli/bin/Debug/net7.0", - "../../../../TypeGen.Cli/bin/Release/net7.0", + "../../../../TypeGen.Cli/bin/Debug/net8.0", + "../../../../TypeGen.Cli/bin/Release/net8.0", }; var cliFilePath = GetCliDirectory(cliPossibleDirectories); @@ -47,7 +52,9 @@ public void Cli_should_finish_with_success() var outputBuilder = new StringBuilder(); while (!process.StandardOutput.EndOfStream) { - outputBuilder.AppendLine(process.StandardOutput.ReadLine()); + var line = process.StandardOutput.ReadLine(); + logger.WriteLine(line); + outputBuilder.AppendLine(line); } var output = outputBuilder.ToString().TrimEnd(); diff --git a/src/TypeGen/TypeGen.Cli/GenerationConfig/GeneratorOptionsProvider.cs b/src/TypeGen/TypeGen.Cli/GenerationConfig/GeneratorOptionsProvider.cs index b8d5cb23..52bb0fa6 100644 --- a/src/TypeGen/TypeGen.Cli/GenerationConfig/GeneratorOptionsProvider.cs +++ b/src/TypeGen/TypeGen.Cli/GenerationConfig/GeneratorOptionsProvider.cs @@ -58,7 +58,8 @@ public GeneratorOptions GetGeneratorOptions(TgConfig config, IEnumerable public event EventHandler FileContentGenerated; - + /// /// A logger instance used to log messages raised by a Generator instance /// public ILogger Logger { get; } - + /// /// Generator options. Cannot be null. /// public GeneratorOptions Options { get; } - + private readonly MetadataReaderFactory _metadataReaderFactory; private readonly ITypeService _typeService; private readonly ITypeDependencyService _typeDependencyService; @@ -53,10 +53,10 @@ public Generator(GeneratorOptions options, ILogger logger = null) Requires.NotNull(options, nameof(options)); FileContentGenerated += OnFileContentGenerated; - + Options = options; Logger = logger; - + var generatorOptionsProvider = new GeneratorOptionsProvider { GeneratorOptions = options }; var internalStorage = new InternalStorage(); @@ -74,7 +74,7 @@ public Generator(GeneratorOptions options, ILogger logger = null) generatorOptionsProvider, logger); } - + public Generator(ILogger logger) : this(new GeneratorOptions(), logger) { } @@ -91,7 +91,7 @@ public void SubscribeDefaultFileContentGeneratedHandler() FileContentGenerated -= OnFileContentGenerated; FileContentGenerated += OnFileContentGenerated; } - + /// /// Unsubscribes the default FileContentGenerated event handler, which saves generated sources to the file system /// @@ -270,20 +270,20 @@ protected virtual void OnFileContentGenerated(object sender, FileContentGenerate private IEnumerable GenerateBarrel(BarrelSpec barrelSpec) { string directory = Path.Combine(Options.BaseOutputDirectory?.EnsurePostfix("/") ?? "", barrelSpec.Directory); - + var fileName = "index"; if (!string.IsNullOrWhiteSpace(Options.TypeScriptFileExtension)) fileName += $".{Options.TypeScriptFileExtension}"; string filePath = Path.Combine(directory.EnsurePostfix("/"), fileName); var entries = new List(); - + if (barrelSpec.BarrelScope.HasFlag(BarrelScope.Files)) { entries.AddRange(_fileSystem.GetDirectoryFiles(directory) .Where(x => Path.GetFileName(x) != fileName && x.EndsWith($".{Options.TypeScriptFileExtension}")) .Select(Path.GetFileNameWithoutExtension)); } - + if (barrelSpec.BarrelScope.HasFlag(BarrelScope.Directories)) { entries.AddRange( @@ -294,11 +294,11 @@ private IEnumerable GenerateBarrel(BarrelSpec barrelSpec) string indexExportsContent = entries.Aggregate("", (acc, entry) => acc += _templateService.FillIndexExportTemplate(entry)); string content = _templateService.FillIndexTemplate(indexExportsContent); - + FileContentGenerated?.Invoke(this, new FileContentGeneratedArgs(null, filePath, content)); return new[] { Path.Combine(barrelSpec.Directory.EnsurePostfix("/"), fileName) }; } - + /// /// DEPRECATED, can be removed in the future. /// Generates an `index.ts` file which exports all types within the generated files @@ -339,7 +339,7 @@ private IEnumerable GenerateMarkedType(Type type) return files.Distinct(); } - + /// /// Contains the actual logic of generating TypeScript files for a given type /// Should only be used inside GenerateTypeInit(), otherwise use GenerateTypeInit() @@ -387,7 +387,7 @@ private IEnumerable GenerateNotMarkedType(Type type, string outputDirect ? GenerateInterface(type, new ExportTsInterfaceAttribute { OutputDir = outputDirectory }) : GenerateClass(type, new ExportTsClassAttribute { OutputDir = outputDirectory }); } - + if (typeInfo.IsInterface) return GenerateInterface(type, new ExportTsInterfaceAttribute { OutputDir = outputDirectory }); @@ -429,6 +429,7 @@ private IEnumerable GenerateClass(Type type, ExportTsClassAttribute clas string importsText = _tsContentGenerator.GetImportsText(type, outputDir); string propertiesText = GetClassPropertiesText(type); + string constructorText = _tsContentGenerator.GetConstructorText(type); // generate the file content @@ -445,8 +446,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, constructorText, tsDoc, customHead, customBody, Options.FileHeading) : + _templateService.FillClassTemplate(importsText, tsTypeName, extendsText, implementsText, propertiesText, constructorText, tsDoc, customHead, customBody, Options.FileHeading); // write TypeScript file FileContentGenerated?.Invoke(this, new FileContentGeneratedArgs(type, filePath, content)); @@ -482,7 +483,6 @@ private IEnumerable GenerateInterface(Type type, ExportTsInterfaceAttrib string importsText = _tsContentGenerator.GetImportsText(type, outputDir); string propertiesText = GetInterfacePropertiesText(type); - // generate the file content string tsTypeName = _typeService.GetTsTypeName(type, true); @@ -517,7 +517,7 @@ private static List GetNotNullOrEmptyImplementedInterfaceNames(TsCustomB .Select(x => x.Name) .Where(x => !string.IsNullOrEmpty(x)) .ToList(); - + /// /// Generates a TypeScript enum file from a class type /// @@ -552,7 +552,7 @@ private bool IsStaticTsProperty(MemberInfo memberInfo) if (_metadataReaderFactory.GetInstance().GetAttribute(memberInfo) != null) return false; return _metadataReaderFactory.GetInstance().GetAttribute(memberInfo) != null || memberInfo.IsStatic(); } - + private bool IsReadonlyTsProperty(MemberInfo memberInfo) { if (_metadataReaderFactory.GetInstance().GetAttribute(memberInfo) != null) return false; @@ -588,20 +588,28 @@ private string GetClassPropertyText(Type type, MemberInfo memberInfo) isOptional = true; } + var ctorAttribute = _metadataReaderFactory.GetInstance().GetAttribute(memberInfo); + if (ctorAttribute != null) + return _templateService.FillClassPropertyTemplate(modifiers, name, typeName, typeUnions, isOptional, tsDoc); + // try to get default value from TsDefaultValueAttribute var defaultValueAttribute = _metadataReaderFactory.GetInstance().GetAttribute(memberInfo); if (defaultValueAttribute != null) return _templateService.FillClassPropertyTemplate(modifiers, name, typeName, typeUnions, isOptional, tsDoc, defaultValueAttribute.DefaultValue); + string fallback = null; + // try to get default value from Options.DefaultValuesForTypes + if (Options.DefaultValuesForTypes.Any() && Options.DefaultValuesForTypes.ContainsKey(typeName)) + fallback = Options.DefaultValuesForTypes[typeName]; + // try to get default value from the member's default value - string valueText = _tsContentGenerator.GetMemberValueText(memberInfo); + string valueText = _tsContentGenerator.GetMemberValueText(memberInfo, isOptional, fallback); + if (((string.IsNullOrWhiteSpace(valueText) || valueText == "null") && Options.StrictMode) && !typeUnions.Contains("null")) + typeUnions = typeUnions.Append("null"); + if (!string.IsNullOrWhiteSpace(valueText)) return _templateService.FillClassPropertyTemplate(modifiers, name, typeName, typeUnions, isOptional, tsDoc, valueText); - // try to get default value from Options.DefaultValuesForTypes - if (Options.DefaultValuesForTypes.Any() && Options.DefaultValuesForTypes.ContainsKey(typeName)) - return _templateService.FillClassPropertyTemplate(modifiers, name, typeName, typeUnions, isOptional, tsDoc, Options.DefaultValuesForTypes[typeName]); - return _templateService.FillClassPropertyTemplate(modifiers, name, typeName, typeUnions, isOptional, tsDoc); } diff --git a/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs b/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs index 1aaacd4f..9ea3c660 100644 --- a/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs +++ b/src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs @@ -37,6 +37,7 @@ public class GeneratorOptions public static string DefaultIndexFileExtension => DefaultTypeScriptFileExtension; public static bool DefaultExportTypesAsInterfacesByDefault => false; public static bool DefaultUseImportType => false; + public static bool DefaultStrictMode => false; public static HashSet DefaultTypeBlacklist => new(new [] { @@ -178,6 +179,11 @@ public string BaseOutputDirectory /// public bool ExportTypesAsInterfacesByDefault { get; set; } = DefaultExportTypesAsInterfacesByDefault; + /// + /// Whether to append null to type unions where null is the default value. + /// + public bool StrictMode { get; set; } = DefaultStrictMode; + /// /// Whether to use "import type" instead of "import" for imports in TS sources. /// diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs b/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs index d196ffd1..9fc3e560 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/ITemplateService.cs @@ -4,8 +4,10 @@ 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 constructor, string tsDoc, string customHead, string customBody, string fileHeading = null); + string FillClassDefaultExportTemplate(string imports, string name, string exportName, string extends, string implements, string properties, string constructor, string tsDoc, string customHead, string customBody, string fileHeading = null); + string FillConstructorTemplate(string type, string parameters, string superCall, string body); + string FillConstructorAssignmentTemplate(string name); 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); diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/ITsContentGenerator.cs b/src/TypeGen/TypeGen.Core/Generator/Services/ITsContentGenerator.cs index 8be941ba..16225ffe 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/ITsContentGenerator.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/ITsContentGenerator.cs @@ -42,9 +42,11 @@ internal interface ITsContentGenerator /// Gets text to be used as a member value /// /// + /// /// The text to be used as a member value. Null if the member has no value or value cannot be determined. - string GetMemberValueText(MemberInfo memberInfo); + string GetMemberValueText(MemberInfo memberInfo, bool isOptional, string? fallback = null); string GetImplementsText(Type type); string GetExtendsForInterfacesText(Type type); + string GetConstructorText(Type type); } } \ No newline at end of file diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/ITypeService.cs b/src/TypeGen/TypeGen.Core/Generator/Services/ITypeService.cs index f39856e7..f88ab042 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/ITypeService.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/ITypeService.cs @@ -21,6 +21,14 @@ internal interface ITypeService /// TypeScript type name. Null if the passed type cannot be represented as a TypeScript simple type. string GetTsBuiltInTypeName(Type type); + /// + /// Determines whether the type represents a TypeScript class + /// + /// + /// True if the type represents a TypeScript class; false otherwise + /// Thrown if the type is null + bool IsTsClass(Type type); + /// /// Determines whether the type represents a TypeScript class /// diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs b/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs index 3ca1759e..7274f0bd 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/TemplateService.cs @@ -24,6 +24,8 @@ internal class TemplateService : ITemplateService private readonly string _enumUnionTypeValueTemplate; private readonly string _classTemplate; private readonly string _classDefaultExportTemplate; + private readonly string _constructorTemplate; + private readonly string _constructorAssignmentTemplate; private readonly string _classPropertyTemplate; private readonly string _interfaceTemplate; private readonly string _interfaceDefaultExportTemplate; @@ -49,6 +51,8 @@ public TemplateService(IInternalStorage internalStorage, IGeneratorOptionsProvid _enumUnionTypeValueTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.EnumUnionTypeValue.tpl"); _classTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.Class.tpl"); _classDefaultExportTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassDefaultExport.tpl"); + _constructorTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.Constructor.tpl"); + _constructorAssignmentTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ConstructorAssignment.tpl"); _classPropertyTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.ClassProperty.tpl"); _interfaceTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.Interface.tpl"); _interfaceDefaultExportTemplate = _internalStorage.GetEmbeddedResource("TypeGen.Core.Templates.InterfaceDefaultExport.tpl"); @@ -61,7 +65,7 @@ public TemplateService(IInternalStorage internalStorage, IGeneratorOptionsProvid } public string FillClassTemplate(string imports, string name, string extends, string implements, string properties, - string tsDoc, string customHead, string customBody, string fileHeading = null) + string constructor, string tsDoc, string customHead, string customBody, string fileHeading = null) { if (fileHeading == null) fileHeading = _headingTemplate; @@ -71,6 +75,7 @@ public string FillClassTemplate(string imports, string name, string extends, str .Replace(GetTag("extends"), extends) .Replace(GetTag("implements"), implements) .Replace(GetTag("properties"), properties) + .Replace(GetTag("constructor"), constructor) .Replace(GetTag("tsDoc"), tsDoc) .Replace(GetTag("customHead"), customHead) .Replace(GetTag("customBody"), customBody) @@ -78,7 +83,7 @@ public string FillClassTemplate(string imports, string name, string extends, str } 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 constructor, string tsDoc, string customHead, string customBody, string fileHeading = null) { if (fileHeading == null) fileHeading = _headingTemplate; @@ -89,12 +94,28 @@ public string FillClassDefaultExportTemplate(string imports, string name, string .Replace(GetTag("extends"), extends) .Replace(GetTag("implements"), implements) .Replace(GetTag("properties"), properties) + .Replace(GetTag("constructor"), constructor) .Replace(GetTag("tsDoc"), tsDoc) .Replace(GetTag("customHead"), customHead) .Replace(GetTag("customBody"), customBody) .Replace(GetTag("fileHeading"), fileHeading); } + public string FillConstructorTemplate(string type, string parameters, string superCall, string body) + { + return ReplaceSpecialChars(_constructorTemplate) + .Replace(GetTag("type"), type) + .Replace(GetTag("parameters"), parameters) + .Replace(GetTag("super"), superCall) + .Replace(GetTag("body"), body); + } + + public string FillConstructorAssignmentTemplate(string name) + { + return ReplaceSpecialChars(_constructorAssignmentTemplate) + .Replace(GetTag("name"), name); + } + public string FillClassPropertyTemplate(string modifiers, string name, string type, IEnumerable typeUnions, bool isOptional, string tsDoc, string defaultValue = null) { diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/TsContentGenerator.cs b/src/TypeGen/TypeGen.Core/Generator/Services/TsContentGenerator.cs index 490ee4fe..ec9877bf 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/TsContentGenerator.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/TsContentGenerator.cs @@ -1,9 +1,14 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Xml.Linq; using Newtonsoft.Json; using TypeGen.Core.Extensions; using TypeGen.Core.Logging; @@ -67,10 +72,10 @@ public TsContentGenerator(ITypeDependencyService typeDependencyService, public string GetImportsText(Type type, string outputDir) { Requires.NotNull(type, nameof(type)); - + if (GeneratorOptions.FileNameConverters == null) throw new InvalidOperationException($"{nameof(GeneratorOptions.FileNameConverters)} should not be null."); - + if (GeneratorOptions.TypeNameConverters == null) throw new InvalidOperationException($"{nameof(GeneratorOptions.TypeNameConverters)} should not be null."); @@ -94,7 +99,7 @@ public string GetExtendsForClassesText(Type type) { Requires.NotNull(type, nameof(type)); Requires.NotNull(GeneratorOptions.TypeNameConverters, nameof(GeneratorOptions.TypeNameConverters)); - + Type baseType = _typeService.GetBaseType(type); if (baseType == null) return ""; @@ -135,7 +140,7 @@ public string GetImplementsText(Type type) IEnumerable baseTypeNames = implementedInterfaces.Select(baseType => _typeService.GetTsTypeName(baseType, true)); return _templateService.GetImplementsText(baseTypeNames); } - + /// /// Returns TypeScript imports source code related to type dependencies /// @@ -144,7 +149,7 @@ public string GetImplementsText(Type type) /// private string GetTypeDependencyImportsText(Type type, string outputDir) { - if (!string.IsNullOrEmpty(outputDir) && !outputDir.EndsWith("/") && !outputDir.EndsWith("\\")) outputDir += "\\"; + if (!string.IsNullOrEmpty(outputDir) && !outputDir.EndsWith("/") && !outputDir.EndsWith("\\")) outputDir += "/"; var result = ""; IEnumerable typeDependencies = _typeDependencyService.GetTypeDependencies(type); @@ -154,26 +159,51 @@ private string GetTypeDependencyImportsText(Type type, string outputDir) typeDependencies = typeDependencies.Where(td => !td.IsBase); } + var startFilePath = GeneratorOptions.FileNameConverters.Convert(type.Name.RemoveTypeArity(), type)?.Replace("\\", "/"); + var startFileName = startFilePath; + + var startOutputDir = outputDir == null ? startFilePath : outputDir.EnsurePostfix("/") + startFilePath; + if (startOutputDir.IndexOf('/') != -1) + { + startFileName = startOutputDir.Substring(startOutputDir.LastIndexOf('/') + 1); + startOutputDir = startOutputDir.Remove(startOutputDir.LastIndexOf('/') + 1); + } + else + { + startOutputDir = outputDir; + } + foreach (TypeDependencyInfo typeDependencyInfo in typeDependencies) { Type typeDependency = typeDependencyInfo.Type; - string dependencyOutputDir = GetTypeDependencyOutputDir(typeDependencyInfo, outputDir); - - // get path diff - string pathDiff = FileSystemUtils.GetPathDiff(outputDir, dependencyOutputDir); - pathDiff = pathDiff.StartsWith("..\\") || pathDiff.StartsWith("../") ? pathDiff : $"./{pathDiff}"; - // get type & file name string typeDependencyName = typeDependency.Name.RemoveTypeArity(); - string fileName = GeneratorOptions.FileNameConverters.Convert(typeDependencyName, typeDependency); + string endFilePath = GeneratorOptions.FileNameConverters.Convert(typeDependencyName, typeDependency); + string endFileName = endFilePath; + + var endOutputDir = GetTypeDependencyOutputDir(typeDependencyInfo, outputDir)?.Replace("\\", "/"); + endOutputDir = endOutputDir == null ? endFilePath : endOutputDir.EnsurePostfix("/") + endFilePath; + if (endOutputDir.IndexOf('/') != -1) + { + endFileName = endOutputDir.Substring(endOutputDir.LastIndexOf('/') + 1); + endOutputDir = endOutputDir.Remove(endOutputDir.LastIndexOf('/') + 1); + } + else + { + endOutputDir = "./"; + } + // get path diff + string pathDiff = FileSystemUtils.GetPathDiff(startOutputDir, endOutputDir); + pathDiff = pathDiff.StartsWith("../") ? pathDiff : $"./{pathDiff}"; + _logger?.Log($"{startOutputDir} -> {endOutputDir} = {pathDiff}", LogLevel.Info); // get file path - string dependencyPath = Path.Combine(pathDiff.EnsurePostfix("/"), fileName); + string dependencyPath = pathDiff.EnsurePostfix("/") + endFileName; dependencyPath = dependencyPath.Replace('\\', '/'); string typeName = GeneratorOptions.TypeNameConverters.Convert(typeDependencyName, typeDependency); - + result += _typeService.UseDefaultExport(typeDependency) ? _templateService.FillImportDefaultExportTemplate(typeName, dependencyPath, GeneratorOptions.UseImportType) : _templateService.FillImportTemplate(typeName, "", dependencyPath, GeneratorOptions.UseImportType); @@ -239,8 +269,8 @@ private string FillCustomImportTemplate(string typeName, string importPath, stri string name = withOriginalTypeName ? originalTypeName : typeName; string typeAlias = withOriginalTypeName ? typeName : null; - - return isDefaultExport ? _templateService.FillImportDefaultExportTemplate(name, importPath, GeneratorOptions.UseImportType) : + + return isDefaultExport ? _templateService.FillImportDefaultExportTemplate(name, importPath, GeneratorOptions.UseImportType) : _templateService.FillImportTemplate(name, typeAlias, importPath, GeneratorOptions.UseImportType); } @@ -280,7 +310,7 @@ private string GetTypeDependencyOutputDir(TypeDependencyInfo typeDependencyInfo, public string GetCustomBody(string filePath, int indentSize) { Requires.NotNull(filePath, nameof(filePath)); - + string content = _tsContentParser.GetTagContent(filePath, indentSize, KeepTsTagName, CustomBodyTagName); string tab = StringUtils.GetTabText(indentSize); @@ -298,7 +328,7 @@ public string GetCustomBody(string filePath, int indentSize) public string GetCustomHead(string filePath) { Requires.NotNull(filePath, nameof(filePath)); - + string content = _tsContentParser.GetTagContent(filePath, 0, CustomHeadTagName); return string.IsNullOrEmpty(content) ? "" @@ -309,63 +339,53 @@ public string GetCustomHead(string filePath) /// Gets text to be used as a member value /// /// + /// /// The text to be used as a member value. Null if the member has no value or value cannot be determined. - public string GetMemberValueText(MemberInfo memberInfo) + public string GetMemberValueText(MemberInfo memberInfo, bool isOptional, string? fallback = null) { - if (memberInfo.DeclaringType == null) return null; + var temp = memberInfo.Name; + if (memberInfo.DeclaringType == null) return fallback; try { object instance = memberInfo.IsStatic() ? null : ActivatorUtils.CreateInstanceAutoFillGenericParameters(memberInfo.DeclaringType); + if (instance != null) + { + memberInfo = instance.GetType().GetMember(memberInfo.Name).First(); // Properties and fields can't overload, right? + } var valueObj = new object(); object valueObjGuard = valueObj; bool isConstant = false; - - switch (memberInfo) - { - case FieldInfo fieldInfo: - valueObj = fieldInfo.GetValue(instance); - isConstant = fieldInfo.IsStatic && fieldInfo.IsLiteral && !fieldInfo.IsInitOnly; - break; - case PropertyInfo propertyInfo: - valueObj = propertyInfo.GetValue(instance); - break; - } + + var valueType = GetMemberValue(memberInfo, instance, out valueObj, out isConstant); // if only default values for constants are allowed - if (GeneratorOptions.CsDefaultValuesForConstantsOnly && !isConstant) return null; + if (GeneratorOptions.CsDefaultValuesForConstantsOnly && !isConstant) return fallback; // if valueObj hasn't been assigned in the switch - if (valueObj == valueObjGuard) return null; - - // if valueObj's value is the default value for its type - if (valueObj == null || valueObj.Equals(TypeUtils.GetDefaultValue(valueObj.GetType()))) return null; - - string memberType = _typeService.GetTsTypeName(memberInfo).GetTsTypeUnion(0); - string quote = GeneratorOptions.SingleQuotes ? "'" : "\""; + if (valueObj == valueObjGuard) return fallback; - - switch (valueObj) - { - case Guid valueGuid when memberType == "string": - return quote + valueGuid + quote; - case DateTime valueDateTime when memberType == "Date": - return $@"new Date({quote}{valueDateTime.ToString("o", CultureInfo.InvariantCulture)}{quote})"; - case DateTime valueDateTime when memberType == "string": - return quote + valueDateTime.ToString("o", CultureInfo.InvariantCulture) + quote; - case DateTimeOffset valueDateTimeOffset when memberType == "Date": - return $@"new Date({quote}{valueDateTimeOffset.ToString("o", CultureInfo.InvariantCulture)}{quote})"; - case DateTimeOffset valueDateTimeOffset when memberType == "string": - return quote + valueDateTimeOffset.ToString("o", CultureInfo.InvariantCulture) + quote; - default: - return JsonConvert.SerializeObject(valueObj, _jsonSerializerSettings).Replace("\"", quote); - } + // if valueObj's value is the default value for its type + var defaultValueForType = TypeUtils.GetDefaultValue(valueType); + if (_typeService.IsCollectionType(valueType)) + valueObj ??= isOptional ? null : new List(); + else if (_typeService.IsDictionaryType(valueType)) + valueObj ??= isOptional ? null : new Dictionary(); + else if (valueObj == null) + return _generatorOptionsProvider.GeneratorOptions.StrictMode ? fallback ?? "null" : fallback; + else if (valueObj.Equals(defaultValueForType)) + if (fallback != null || !_generatorOptionsProvider.GeneratorOptions.StrictMode) + return fallback; + else + valueObj = isOptional ? null : defaultValueForType; + + return SerializeObjectToTs(valueObj, memberInfo); } catch (MissingMethodException e) { _logger?.Log($"Cannot determine the default value for member '{memberInfo.DeclaringType.FullName}.{memberInfo.Name}', because type '{memberInfo.DeclaringType.FullName}' has no default constructor.", LogLevel.Debug); } - catch (ArgumentException e) when(e.InnerException is TypeLoadException) + catch (ArgumentException e) when (e.InnerException is TypeLoadException) { _logger?.Log($"Cannot determine the default value for member '{memberInfo.DeclaringType.FullName}.{memberInfo.Name}', because type '{memberInfo.DeclaringType.FullName}' has generic parameters with base class or interface constraints.", LogLevel.Debug); } @@ -374,7 +394,178 @@ public string GetMemberValueText(MemberInfo memberInfo) _logger?.Log($"Cannot determine the default value for member '{memberInfo.DeclaringType.FullName}.{memberInfo.Name}', because an unknown exception occurred: '{e.Message}'", LogLevel.Debug); } - return null; + return fallback; + } + + private string SerializeObjectToTs(object valueObj) + { + return SerializeObjectToTs(valueObj, null); + } + + private string SerializeObjectToTs(object valueObj, MemberInfo memberInfo) + { + if (valueObj == null) return null; + var valueType = valueObj.GetType(); + string memberType = memberInfo == null ? _typeService.GetTsTypeName(valueType).GetTsTypeUnion(0) : _typeService.GetTsTypeName(memberInfo).GetTsTypeUnion(0); + string quote = GeneratorOptions.SingleQuotes ? "'" : "\""; + + switch (valueObj) + { + case Guid valueGuid when memberType == "string": + return quote + valueGuid + quote; + case DateTime valueDateTime when memberType == "Date": + return $@"new Date({quote}{valueDateTime.ToString("o", CultureInfo.InvariantCulture)}{quote})"; + case DateTime valueDateTime when memberType == "string": + return quote + valueDateTime.ToString("o", CultureInfo.InvariantCulture) + quote; + case DateTimeOffset valueDateTimeOffset when memberType == "Date": + return $@"new Date({quote}{valueDateTimeOffset.ToString("o", CultureInfo.InvariantCulture)}{quote})"; + case DateTimeOffset valueDateTimeOffset when memberType == "string": + return quote + valueDateTimeOffset.ToString("o", CultureInfo.InvariantCulture) + quote; + case IEnumerable valueEnumerable when _typeService.IsCollectionType(valueType) && valueEnumerable.GetEnumerator().MoveNext(): + return $"[ {string.Join(", ", valueEnumerable.Cast().Select(SerializeObjectToTs))} ]"; + case IEnumerable valueDictEnumerable when _typeService.IsDictionaryType(valueType) && valueDictEnumerable.GetEnumerator().MoveNext(): + return $"{{ {string.Join(", ", valueDictEnumerable.Cast().Select(SerializeObjectToTs))} }}"; + case DictionaryEntry valueDictEntry: + return $"{SerializeObjectToTs(valueDictEntry.Key)}: {SerializeObjectToTs(valueDictEntry.Value)}"; + case var _ when valueType.IsGenericType && typeof(KeyValuePair<,>).IsAssignableFrom(valueType.GetGenericTypeDefinition()): + dynamic pair = valueObj; + return $"{SerializeObjectToTs(pair.Key)}: {SerializeObjectToTs(pair.Value)}"; + default: + var serializedValue = JsonConvert.SerializeObject(valueObj, _jsonSerializerSettings).Replace("\"", quote); + if (!_typeService.IsCollectionType(valueType) && + !_typeService.IsDictionaryType(valueType) && + _typeService.IsTsClass(valueType) && // Make sure it's not a list, array, or other special type + !valueType.GetTypeInfo().IsValueType && // Ignore value types + valueType.GetConstructor(Type.EmptyTypes) != null) // Make sure the type has a default constructor to use for this + { + //_logger?.Log($"Checking type {valueType.FullName} for constructor usage", LogLevel.Info); + var defaultCtorValueType = Activator.CreateInstance(valueType); + if (defaultCtorValueType != null && memberwiseEquals(valueObj, defaultCtorValueType)) + { + return $@"new {_typeService.GetTsTypeName(valueType)}()"; + } + } + return serializedValue; + } + } + + private bool memberwiseEquals(object a, object b) + { + if (a == b || a.Equals(b)) return true; + if (a.GetType() != b.GetType()) return false; + var type = a.GetType(); + if (type.GetTypeInfo().IsValueType) + { + return false; + } + while (type != null) + { + foreach (var member in type.GetTsExportableMembers(this._metadataReaderFactory.GetInstance())) + { + if (member is PropertyInfo p && p.GetIndexParameters().Length > 0) continue; + GetMemberValue(member, a, out var aVal, out var _); + GetMemberValue(member, b, out var bVal, out var _); + if (!memberwiseEquals(aVal, bVal)) + { + //_logger?.Log($"Difference found in member {type.FullName}.{member.Name}: {aVal} != {bVal}", LogLevel.Info); + return false; + } + } + type = type.GetTypeInfo().BaseType; + } + return true; + } + + private Type GetMemberValue(MemberInfo memberInfo, object instance, out object valueObj, out bool isConstant) + { + switch (memberInfo) + { + case FieldInfo fieldInfo: + valueObj = fieldInfo.GetValue(instance); + isConstant = fieldInfo.IsStatic && fieldInfo.IsLiteral && !fieldInfo.IsInitOnly; + return fieldInfo.FieldType; + case PropertyInfo propertyInfo: + valueObj = propertyInfo.GetValue(instance); + isConstant = false; + return propertyInfo.PropertyType; + default: + throw new Exception(); + } + + } + + public string GetConstructorText(Type type) + { + var reader = _metadataReaderFactory.GetInstance(); + var ctorParams = GetHierarchyConstructorParams(type); + if (ctorParams.Count == 0 || ctorParams.All(l => l.Count == 0)) + { + return ""; + } + ctorParams.Reverse(); + var paramsText = string.Join(", ", ctorParams.SelectMany(_ => _).Select(m => + { + string defaultValue; + var typeName = _typeService.GetTsTypeName(m); + // try to get default value from TsDefaultValueAttribute + var defaultValueAttribute = _metadataReaderFactory.GetInstance().GetAttribute(m); + if (defaultValueAttribute != null) + { + defaultValue = defaultValueAttribute.DefaultValue; + } + else + { + // try to get default value from Options.DefaultValuesForTypes + string fallback = null; + if (_generatorOptionsProvider.GeneratorOptions.DefaultValuesForTypes.Any() && _generatorOptionsProvider.GeneratorOptions.DefaultValuesForTypes.ContainsKey(typeName)) + fallback = _generatorOptionsProvider.GeneratorOptions.DefaultValuesForTypes[typeName]; + + // try to get default value from the member's default value + var isNullable = m.IsNullable(); + var isOptional = false; + if (isNullable && _generatorOptionsProvider.GeneratorOptions.CsNullableTranslation == StrictNullTypeUnionFlags.Optional) + { + isOptional = true; + } + string valueText = GetMemberValueText(m, isOptional, fallback); + if (!string.IsNullOrWhiteSpace(valueText)) + defaultValue = valueText; + else + defaultValue = null; + } + var ret = $"{GetTsMemberName(m)}: {typeName}"; + if (!string.IsNullOrWhiteSpace(defaultValue)) + { + ret += $" = {defaultValue}"; + } + return ret; + })); + string tab = GeneratorOptions.UseTabCharacter ? "\t" : StringUtils.GetTabText(GeneratorOptions.TabLength); + var newParams = ctorParams.Last(); + var superParams = new List>(ctorParams); + superParams.RemoveAt(superParams.Count - 1); + var superText = superParams.Count == 0 || superParams.All(l => l.Count == 0) ? "" : $"{tab}{tab}super({string.Join(", ", superParams.SelectMany(_ => _).Select(GetTsMemberName))});\r\n"; + var bodyText = newParams.Aggregate("", (acc, m) => acc + _templateService.FillConstructorAssignmentTemplate(GetTsMemberName(m))); + return _templateService.FillConstructorTemplate(_typeService.GetTsTypeName(type), paramsText, superText, bodyText); + } + + private List> GetHierarchyConstructorParams(Type type) + { + var reader = _metadataReaderFactory.GetInstance(); + var ret = new List>(); + while (type != null) + { + var parameters = type.GetTsExportableMembers(reader).Where(m => reader.GetAttribute(m) != null).ToList(); + ret.Add(parameters); + type = type.GetTypeInfo().BaseType; + } + return ret; + } + + private string GetTsMemberName(MemberInfo memberInfo) + { + var nameAttribute = _metadataReaderFactory.GetInstance().GetAttribute(memberInfo); + return nameAttribute?.Name ?? _generatorOptionsProvider.GeneratorOptions.PropertyNameConverters.Convert(memberInfo.Name, memberInfo); } } } diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/TypeDependencyService.cs b/src/TypeGen/TypeGen.Core/Generator/Services/TypeDependencyService.cs index d1932d24..aa2b9bf2 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/TypeDependencyService.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/TypeDependencyService.cs @@ -53,6 +53,7 @@ public IEnumerable GetTypeDependencies(Type type) .Concat(GetBaseTypeDependency(type)) .Concat(GetImplementedInterfaceTypesDependencies(type)) .Concat(GetMemberTypeDependencies(type)) + .Concat(GetSuperConstructorTypeDependencies(type)) .Distinct(new TypeDependencyInfoTypeComparer()) .Where(t => t.Type != type) .Where(t => GeneratorOptions.IsTypeNotBlacklisted(t.Type)) @@ -150,6 +151,31 @@ private IEnumerable GetMemberTypeDependencies(Type type) return result; } + private IEnumerable GetSuperConstructorTypeDependencies(Type type) + { + var baseClass = type.GetTypeInfo().BaseType; + var result = new List(); + while (baseClass != null) + { + IEnumerable memberInfos = baseClass.GetTsExportableMembers(_metadataReaderFactory.GetInstance()).Where(m => _metadataReaderFactory.GetInstance().GetAttribute(m) != null); + + foreach (var memberInfo in memberInfos) + { + Type memberType = _typeService.GetMemberType(memberInfo); + Type memberFlatType = _typeService.GetFlatType(memberType); + + if (memberFlatType == type || (memberFlatType.IsConstructedGenericType && memberFlatType.GetGenericTypeDefinition() == type)) continue; // NOT a dependency if it's the type itself + if (GeneratorOptions.CustomTypeMappings.ContainsKey(memberFlatType.FullName ?? "")) continue; // NOT a dependency if specified in custom type mappings + + IEnumerable memberAttributes = _metadataReaderFactory.GetInstance().GetAttributes(memberInfo); + result.AddRange(GetFlatTypeDependencies(memberFlatType, memberAttributes)); + } + + baseClass = baseClass.BaseType; + } + return result; + } + private IEnumerable GetFlatTypeDependencies(Type flatType, IEnumerable memberAttributes = null, bool isBase = false) { if (_typeService.IsTsBuiltInType(flatType) || flatType.IsGenericParameter) return Enumerable.Empty(); diff --git a/src/TypeGen/TypeGen.Core/Generator/Services/TypeService.cs b/src/TypeGen/TypeGen.Core/Generator/Services/TypeService.cs index 265280e9..6e24b18c 100644 --- a/src/TypeGen/TypeGen.Core/Generator/Services/TypeService.cs +++ b/src/TypeGen/TypeGen.Core/Generator/Services/TypeService.cs @@ -128,6 +128,18 @@ public string GetTsBuiltInTypeName(Type type) } } + /// + public bool IsTsClass(Type type) + { + Requires.NotNull(type, nameof(type)); + TypeInfo typeInfo = type.GetTypeInfo(); + + if (!typeInfo.IsClass) return false; + + var exportAttribute = MetadataReader.GetAttribute(type); + return exportAttribute == null || exportAttribute is ExportTsClassAttribute; + } + /// public bool IsTsInterface(Type type) { diff --git a/src/TypeGen/TypeGen.Core/Templates/Class.tpl b/src/TypeGen/TypeGen.Core/Templates/Class.tpl index f49dbd0f..91696a7d 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{properties}$tg{constructor}$tg{customBody} } diff --git a/src/TypeGen/TypeGen.Core/Templates/ClassDefaultExport.tpl b/src/TypeGen/TypeGen.Core/Templates/ClassDefaultExport.tpl index 45a56f6b..27fd9295 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{properties}$tg{constructor}$tg{customBody} } export default $tg{exportName}; diff --git a/src/TypeGen/TypeGen.Core/Templates/Constructor.tpl b/src/TypeGen/TypeGen.Core/Templates/Constructor.tpl new file mode 100644 index 00000000..f3b55fff --- /dev/null +++ b/src/TypeGen/TypeGen.Core/Templates/Constructor.tpl @@ -0,0 +1,4 @@ + + +$tg{tab}constructor($tg{parameters}) { +$tg{super}$tg{body}$tg{tab}} \ No newline at end of file diff --git a/src/TypeGen/TypeGen.Core/Templates/ConstructorAssignment.tpl b/src/TypeGen/TypeGen.Core/Templates/ConstructorAssignment.tpl new file mode 100644 index 00000000..d4ecb948 --- /dev/null +++ b/src/TypeGen/TypeGen.Core/Templates/ConstructorAssignment.tpl @@ -0,0 +1 @@ +$tg{tab}$tg{tab}this.$tg{name} = $tg{name}; diff --git a/src/TypeGen/TypeGen.Core/TypeAnnotations/TsConstructorAttribute.cs b/src/TypeGen/TypeGen.Core/TypeAnnotations/TsConstructorAttribute.cs new file mode 100644 index 00000000..4199cccb --- /dev/null +++ b/src/TypeGen/TypeGen.Core/TypeAnnotations/TsConstructorAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace TypeGen.Core.TypeAnnotations +{ + /// + /// Identifies a property that should be required as a constructor parameter when generating a TypeScript file + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class TsConstructorAttribute : Attribute + { + } +} diff --git a/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj b/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj index ea861bd7..81ec4d22 100644 --- a/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj +++ b/src/TypeGen/TypeGen.Core/TypeGen.Core.csproj @@ -11,6 +11,8 @@ + + @@ -25,6 +27,8 @@ + + diff --git a/src/TypeGen/TypeGen.Core/Utils/FileSystemUtils.cs b/src/TypeGen/TypeGen.Core/Utils/FileSystemUtils.cs index a5e40c97..4268b196 100644 --- a/src/TypeGen/TypeGen.Core/Utils/FileSystemUtils.cs +++ b/src/TypeGen/TypeGen.Core/Utils/FileSystemUtils.cs @@ -34,8 +34,8 @@ public static string[] SplitPathSeparator(string path) /// public static string GetPathDiff(string pathFrom, string pathTo) { - var pathFromUri = new Uri("file:///" + pathFrom?.Replace('\\', '/')); - var pathToUri = new Uri("file:///" + pathTo?.Replace('\\', '/')); + var pathFromUri = new Uri("file:///root/" + pathFrom?.Replace('\\', '/')); + var pathToUri = new Uri("file:///root/" + pathTo?.Replace('\\', '/')); return pathFromUri.MakeRelativeUri(pathToUri).ToString(); } diff --git a/src/TypeGen/TypeGen.FileContentTest/AddFolderConverter.cs b/src/TypeGen/TypeGen.FileContentTest/AddFolderConverter.cs new file mode 100644 index 00000000..18bd81cd --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/AddFolderConverter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TypeGen.Core.Converters; + +namespace TypeGen.IntegrationTest +{ + internal class AddFolderConverter : ITypeNameConverter + { + public string Convert(string name, Type type) + { + return "foobar" + Path.DirectorySeparatorChar + name; + } + } +} diff --git a/src/TypeGen/TypeGen.FileContentTest/Blacklist/BlacklistTest.cs b/src/TypeGen/TypeGen.FileContentTest/Blacklist/BlacklistTest.cs index ad24dec8..4a9c69c1 100644 --- a/src/TypeGen/TypeGen.FileContentTest/Blacklist/BlacklistTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/Blacklist/BlacklistTest.cs @@ -8,11 +8,14 @@ using TypeGen.FileContentTest.Blacklist.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.Blacklist { public class BlacklistTest : GenerationTestBase { + public BlacklistTest(ITestOutputHelper output) : base(output) { } + [Fact] public async Task ClassWithBlacklistedBase_Test() { diff --git a/src/TypeGen/TypeGen.FileContentTest/CircularGenericConstraint/CircularGenericConstraintTest.cs b/src/TypeGen/TypeGen.FileContentTest/CircularGenericConstraint/CircularGenericConstraintTest.cs index 637fa74e..42363596 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CircularGenericConstraint/CircularGenericConstraintTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/CircularGenericConstraint/CircularGenericConstraintTest.cs @@ -3,11 +3,14 @@ using TypeGen.FileContentTest.CircularGenericConstraint.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.CircularGenericConstraint { public class CircularGenericConstraintTest : GenerationTestBase { + public CircularGenericConstraintTest(ITestOutputHelper output) : base(output) { } + /// /// Looks into generating classes and interfaces with circular type constraints /// diff --git a/src/TypeGen/TypeGen.FileContentTest/Comments/CommentsTest.cs b/src/TypeGen/TypeGen.FileContentTest/Comments/CommentsTest.cs index 88374299..f04eac7f 100644 --- a/src/TypeGen/TypeGen.FileContentTest/Comments/CommentsTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/Comments/CommentsTest.cs @@ -5,11 +5,14 @@ using TypeGen.FileContentTest.Comments.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.Comments; public class CommentsTest : GenerationTestBase { + public CommentsTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(TsClass), "TypeGen.FileContentTest.Comments.Expected.ts-class.ts", false)] [InlineData(typeof(TsClass), "TypeGen.FileContentTest.Comments.Expected.ts-class-default-export.ts", true)] diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/CommonCasesGenerationTest.cs b/src/TypeGen/TypeGen.FileContentTest/CommonCases/CommonCasesGenerationTest.cs index 1e6daaff..0efefbb2 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CommonCases/CommonCasesGenerationTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/CommonCasesGenerationTest.cs @@ -4,7 +4,9 @@ using TypeGen.FileContentTest.CommonCases.Entities.Constants; using TypeGen.FileContentTest.CommonCases.Entities.ErrorCase; using TypeGen.FileContentTest.TestingUtils; +using TypeGen.IntegrationTest.CommonCases.Entities; using Xunit; +using Xunit.Abstractions; using CustomBaseClass = TypeGen.FileContentTest.CommonCases.Entities.CustomBaseClass; using CustomBaseCustomImport = TypeGen.FileContentTest.CommonCases.Entities.CustomBaseCustomImport; using CustomEmptyBaseClass = TypeGen.FileContentTest.CommonCases.Entities.CustomEmptyBaseClass; @@ -24,6 +26,8 @@ namespace TypeGen.FileContentTest.CommonCases { public class CommonCasesGenerationTest : GenerationTestBase { + public CommonCasesGenerationTest(ITestOutputHelper output) : base(output) { } + /// /// Tests if types are correctly translated to TypeScript. /// The tested types contain all major use cases that should be supported. @@ -38,6 +42,8 @@ public class CommonCasesGenerationTest : GenerationTestBase [InlineData(typeof(Entities.BaseClass<>), "TypeGen.FileContentTest.CommonCases.Expected.base-class.ts")] [InlineData(typeof(BaseClass2<>), "TypeGen.FileContentTest.CommonCases.Expected.base-class2.ts")] [InlineData(typeof(C), "TypeGen.FileContentTest.CommonCases.Expected.c.ts")] + [InlineData(typeof(ConstructorClass), "TypeGen.FileContentTest.CommonCases.Expected.constructor-class.ts")] + [InlineData(typeof(ConstructorChildClass), "TypeGen.FileContentTest.CommonCases.Expected.constructor-child-class.ts")] [InlineData(typeof(CustomBaseClass), "TypeGen.FileContentTest.CommonCases.Expected.custom-base-class.ts")] [InlineData(typeof(CustomEmptyBaseClass), "TypeGen.FileContentTest.CommonCases.Expected.custom-empty-base-class.ts")] [InlineData(typeof(D), "TypeGen.FileContentTest.CommonCases.Expected.d.ts")] diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/ConstructorChildClass.cs b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/ConstructorChildClass.cs new file mode 100644 index 00000000..bfe22549 --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/ConstructorChildClass.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TypeGen.Core.TypeAnnotations; + +namespace TypeGen.IntegrationTest.CommonCases.Entities +{ + [ExportTsClass] + public class ConstructorChildClass : ConstructorClass + { + [TsConstructor] + public string ChildConstructorArg { get; set; } = "testchild"; + } +} diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/ConstructorClass.cs b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/ConstructorClass.cs new file mode 100644 index 00000000..8d63f9ab --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/ConstructorClass.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TypeGen.Core.TypeAnnotations; + +namespace TypeGen.IntegrationTest.CommonCases.Entities +{ + [ExportTsClass] + public class ConstructorClass + { + [TsConstructor] + public int ConstructorArgWithDefault { get; set; } = 4; + + [TsConstructor] + [TsDefaultValue("5")] + public int ConstructorArgWithAttributeDefault { get; set; } + + [TsConstructor] + public int ConstructorArgWithNoDefault { get; set; } + + public string NonConstructorArg { get; set; } = "test"; + } +} diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/DefaultMemberValues.cs b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/DefaultMemberValues.cs index 8c0e84aa..a2ad729d 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/DefaultMemberValues.cs +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Entities/DefaultMemberValues.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using TypeGen.Core.TypeAnnotations; namespace TypeGen.FileContentTest.CommonCases.Entities @@ -19,6 +20,12 @@ public class DefaultMemberValues public DateTime fieldDateTimeUnassigned; - public DefaultMemberComplexValues PropertyComplex { get; set; } = new(); + public DefaultMemberComplexValues PropertyComplexDefaultValue { get; set; } = new(); + + public DefaultMemberComplexValues PropertyComplexNotDefaultValue { get; set; } = new() { Number = 4 }; + + public List PropertyListOfComplexDefaultValue { get; set; } = new() { new() }; + + public Dictionary PropertyDictOfComplexDefaultValue { get; set; } = new() { { "key", new() } }; } } \ No newline at end of file diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/array-of-nullable.ts b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/array-of-nullable.ts index 21f7eccc..17ec1244 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/array-of-nullable.ts +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/array-of-nullable.ts @@ -4,5 +4,5 @@ */ export class ArrayOfNullable { - password: number[]; + password: number[] = []; } diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/constructor-child-class.ts b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/constructor-child-class.ts new file mode 100644 index 00000000..2de592d3 --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/constructor-child-class.ts @@ -0,0 +1,14 @@ +/** + * This is a TypeGen auto-generated file. + * Any changes made to this file can be lost when this file is regenerated. + */ +import { ConstructorClass } from "./constructor-class"; + +export class ConstructorChildClass extends ConstructorClass { + childConstructorArg: string; + + constructor(constructorArgWithDefault: number = 4, constructorArgWithAttributeDefault: number = 5, constructorArgWithNoDefault: number, childConstructorArg: string = "testchild") { + super(constructorArgWithDefault, constructorArgWithAttributeDefault, constructorArgWithNoDefault); + this.childConstructorArg = childConstructorArg; + } +} \ No newline at end of file diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/constructor-class.ts b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/constructor-class.ts new file mode 100644 index 00000000..601a6f8f --- /dev/null +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/constructor-class.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. + */ + +export class ConstructorClass { + constructorArgWithDefault: number; + constructorArgWithAttributeDefault: number; + constructorArgWithNoDefault: number; + nonConstructorArg: string = "test"; + + constructor(constructorArgWithDefault: number = 4, constructorArgWithAttributeDefault: number = 5, constructorArgWithNoDefault: number) { + this.constructorArgWithDefault = constructorArgWithDefault; + this.constructorArgWithAttributeDefault = constructorArgWithAttributeDefault; + this.constructorArgWithNoDefault = constructorArgWithNoDefault; + } +} diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/default-member-values.ts b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/default-member-values.ts index 93085dfd..91ae628b 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/default-member-values.ts +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/default-member-values.ts @@ -13,5 +13,8 @@ export class DefaultMemberValues { static staticFieldNumber: number = 2; propertyNumber: number = 3; static staticPropertyString: string = "StaticPropertyString"; - propertyComplex: DefaultMemberComplexValues = {"number":0,"numberNull":null,"string":"default","stringNull":null}; + propertyComplexDefaultValue: DefaultMemberComplexValues = new DefaultMemberComplexValues(); + propertyComplexNotDefaultValue: DefaultMemberComplexValues = {"number":4,"numberNull":null,"string":"default","stringNull":null}; + propertyListOfComplexDefaultValue: DefaultMemberComplexValues[] = [ new DefaultMemberComplexValues() ]; + propertyDictOfComplexDefaultValue: { [key: string]: DefaultMemberComplexValues; } = { "key": new DefaultMemberComplexValues() }; } diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-string-object-error-case.ts b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-string-object-error-case.ts index 814b77cc..494da025 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-string-object-error-case.ts +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-string-object-error-case.ts @@ -4,5 +4,5 @@ */ export class DictionaryStringObjectErrorCase { - foo: { [key: string]: Object; }; + foo: { [key: string]: Object; } = {}; } diff --git a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-with-enum-key.ts b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-with-enum-key.ts index d05d0ebd..31224eb7 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-with-enum-key.ts +++ b/src/TypeGen/TypeGen.FileContentTest/CommonCases/Expected/dictionary-with-enum-key.ts @@ -7,5 +7,5 @@ import { EnumAsUnionType } from "./enum-as-union-type"; import { CustomBaseClass } from "./custom-base-class"; export class DictionaryWithEnumKey { - enumDict: { [key in EnumAsUnionType]?: CustomBaseClass; }; + enumDict: { [key in EnumAsUnionType]?: CustomBaseClass; } = {}; } diff --git a/src/TypeGen/TypeGen.FileContentTest/ConstantsOnly/ConstantsOnlyTest.cs b/src/TypeGen/TypeGen.FileContentTest/ConstantsOnly/ConstantsOnlyTest.cs index ccab3b10..cf67d219 100644 --- a/src/TypeGen/TypeGen.FileContentTest/ConstantsOnly/ConstantsOnlyTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/ConstantsOnly/ConstantsOnlyTest.cs @@ -4,11 +4,14 @@ using TypeGen.Core.SpecGeneration; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.ConstantsOnly; public class ConstantsOnlyTest : GenerationTestBase { + public ConstantsOnlyTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(Entities.ConstantsOnly), "TypeGen.FileContentTest.ConstantsOnly.Expected.constants-only.ts")] public async Task TestConstantsOnlyGenerationSpec(Type type, string expectedLocation) diff --git a/src/TypeGen/TypeGen.FileContentTest/CustomBaseInterfaces/CustomBaseInterfacesTest.cs b/src/TypeGen/TypeGen.FileContentTest/CustomBaseInterfaces/CustomBaseInterfacesTest.cs index 63d81803..860a192f 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CustomBaseInterfaces/CustomBaseInterfacesTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/CustomBaseInterfaces/CustomBaseInterfacesTest.cs @@ -6,11 +6,14 @@ using TypeGen.FileContentTest.CustomBaseInterfaces.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.CustomBaseInterfaces; public class CustomBaseInterfacesTest : GenerationTestBase { + public CustomBaseInterfacesTest(ITestOutputHelper output) : base(output) {} + [Theory] [InlineData(typeof(Foo), "TypeGen.FileContentTest.CustomBaseInterfaces.Expected.foo.ts")] public async Task TestCustomBaseInterfacesGenerationSpec(Type type, string expectedLocation) diff --git a/src/TypeGen/TypeGen.FileContentTest/CustomMappingsClassGeneration/CustomMappingsClassGeneration.cs b/src/TypeGen/TypeGen.FileContentTest/CustomMappingsClassGeneration/CustomMappingsClassGeneration.cs index 667cffd8..c44dae8d 100644 --- a/src/TypeGen/TypeGen.FileContentTest/CustomMappingsClassGeneration/CustomMappingsClassGeneration.cs +++ b/src/TypeGen/TypeGen.FileContentTest/CustomMappingsClassGeneration/CustomMappingsClassGeneration.cs @@ -6,11 +6,14 @@ using TypeGen.FileContentTest.CommonCases.Entities.CustomMappingsClassGenerationIssue; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.CustomMappingsClassGeneration { public class CustomMappingsClassGeneration : GenerationTestBase { + public CustomMappingsClassGeneration(ITestOutputHelper output) : base(output) { } + [Fact] public async Task GeneratesCorrectly() { diff --git a/src/TypeGen/TypeGen.FileContentTest/DefaultExport/DefaultExportTest.cs b/src/TypeGen/TypeGen.FileContentTest/DefaultExport/DefaultExportTest.cs index 8f85979c..921dcfb0 100644 --- a/src/TypeGen/TypeGen.FileContentTest/DefaultExport/DefaultExportTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/DefaultExport/DefaultExportTest.cs @@ -3,11 +3,14 @@ using TypeGen.FileContentTest.DefaultExport.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.DefaultExport { public class DefaultExportTest : GenerationTestBase { + public DefaultExportTest(ITestOutputHelper output) : base(output) { } + /// /// Looks into generating classes and interfaces with circular type constraints /// diff --git a/src/TypeGen/TypeGen.FileContentTest/ExportTypesAsInterfacesByDefault/ExportTypesAsInterfacesByDefaultTest.cs b/src/TypeGen/TypeGen.FileContentTest/ExportTypesAsInterfacesByDefault/ExportTypesAsInterfacesByDefaultTest.cs index 33d3c707..a3710bb0 100644 --- a/src/TypeGen/TypeGen.FileContentTest/ExportTypesAsInterfacesByDefault/ExportTypesAsInterfacesByDefaultTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/ExportTypesAsInterfacesByDefault/ExportTypesAsInterfacesByDefaultTest.cs @@ -4,11 +4,14 @@ using TypeGen.FileContentTest.ExportTypesAsInterfacesByDefault.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.ExportTypesAsInterfacesByDefault; public class ExportTypesAsInterfacesByDefaultTest : GenerationTestBase { + public ExportTypesAsInterfacesByDefaultTest(ITestOutputHelper output) : base(output) { } + [Fact] public async Task if_option_not_set_should_generate_class() { diff --git a/src/TypeGen/TypeGen.FileContentTest/GenerationSpecForStructs/GenerationSpecsForStructsTest.cs b/src/TypeGen/TypeGen.FileContentTest/GenerationSpecForStructs/GenerationSpecsForStructsTest.cs index cd54bbf4..4168ce23 100644 --- a/src/TypeGen/TypeGen.FileContentTest/GenerationSpecForStructs/GenerationSpecsForStructsTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/GenerationSpecForStructs/GenerationSpecsForStructsTest.cs @@ -5,11 +5,14 @@ using TypeGen.FileContentTest.CommonCases.Entities.Structs; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.GenerationSpecForStructs; public class GenerationSpecsForStructsTest : GenerationTestBase { + public GenerationSpecsForStructsTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(CustomBaseClass), "TypeGen.FileContentTest.CommonCases.Expected.custom-base-class.ts")] [InlineData(typeof(CustomBaseCustomImport), "TypeGen.FileContentTest.CommonCases.Expected.custom-base-custom-import.ts")] diff --git a/src/TypeGen/TypeGen.FileContentTest/GenericInheritance/GenericInheritanceTest.cs b/src/TypeGen/TypeGen.FileContentTest/GenericInheritance/GenericInheritanceTest.cs index 6a1d4bdb..a5fbd8f4 100644 --- a/src/TypeGen/TypeGen.FileContentTest/GenericInheritance/GenericInheritanceTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/GenericInheritance/GenericInheritanceTest.cs @@ -4,11 +4,14 @@ using TypeGen.FileContentTest.GenericInheritance.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.GenericInheritance; public class GenericInheritanceTest : GenerationTestBase { + public GenericInheritanceTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(GetCustomersResponseDto), "TypeGen.FileContentTest.GenericInheritance.Expected.get-customers-response-dto.ts")] public async Task TestGenericInheritance(Type type, string expectedLocation) diff --git a/src/TypeGen/TypeGen.FileContentTest/IgnoreBaseInterfaces/IgnoreBaseInterfacesTest.cs b/src/TypeGen/TypeGen.FileContentTest/IgnoreBaseInterfaces/IgnoreBaseInterfacesTest.cs index 998b26e5..c4fa2c64 100644 --- a/src/TypeGen/TypeGen.FileContentTest/IgnoreBaseInterfaces/IgnoreBaseInterfacesTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/IgnoreBaseInterfaces/IgnoreBaseInterfacesTest.cs @@ -3,11 +3,14 @@ using TypeGen.FileContentTest.IgnoreBaseInterfaces.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.IgnoreBaseInterfaces; public class IgnoreBaseInterfacesTest : GenerationTestBase { + public IgnoreBaseInterfacesTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(Test), "TypeGen.FileContentTest.IgnoreBaseInterfaces.Expected.test.ts")] public async Task TestIgnoreBaseInterfaces(Type type, string expectedLocation) diff --git a/src/TypeGen/TypeGen.FileContentTest/ImportType/ImportTypeTest.cs b/src/TypeGen/TypeGen.FileContentTest/ImportType/ImportTypeTest.cs index e21bab14..5a49f5b7 100644 --- a/src/TypeGen/TypeGen.FileContentTest/ImportType/ImportTypeTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/ImportType/ImportTypeTest.cs @@ -5,11 +5,14 @@ using TypeGen.FileContentTest.ImportType.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.ImportType; public class ImportTypeTest : GenerationTestBase { + public ImportTypeTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(TsClass), "TypeGen.FileContentTest.ImportType.Expected.ts-class.ts", false)] [InlineData(typeof(TsClass), "TypeGen.FileContentTest.ImportType.Expected.ts-class-default-export.ts", true)] diff --git a/src/TypeGen/TypeGen.FileContentTest/NullableTranslation/NullableTranslationTest.cs b/src/TypeGen/TypeGen.FileContentTest/NullableTranslation/NullableTranslationTest.cs index 9bb5850b..0824c280 100644 --- a/src/TypeGen/TypeGen.FileContentTest/NullableTranslation/NullableTranslationTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/NullableTranslation/NullableTranslationTest.cs @@ -6,11 +6,14 @@ using TypeGen.FileContentTest.NullableTranslation.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.NullableTranslation; public class NullableTranslationTest : GenerationTestBase { + public NullableTranslationTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(NullableClass), "TypeGen.FileContentTest.NullableTranslation.Expected.nullable-class.ts")] public async Task TestNullableTranslationGenerationSpec(Type type, string expectedLocation) diff --git a/src/TypeGen/TypeGen.FileContentTest/StructImplementsInterfaces/StructImplementsInterfacesTest.cs b/src/TypeGen/TypeGen.FileContentTest/StructImplementsInterfaces/StructImplementsInterfacesTest.cs index b63da5d7..4760e45f 100644 --- a/src/TypeGen/TypeGen.FileContentTest/StructImplementsInterfaces/StructImplementsInterfacesTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/StructImplementsInterfaces/StructImplementsInterfacesTest.cs @@ -5,11 +5,14 @@ using TypeGen.FileContentTest.StructImplementsInterfaces.Entities; using TypeGen.FileContentTest.TestingUtils; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.StructImplementsInterfaces; public class StructImplementsInterfacesTest : GenerationTestBase { + public StructImplementsInterfacesTest(ITestOutputHelper output) : base(output) {} + [Theory] [InlineData(typeof(ImplementsInterfaces), "TypeGen.FileContentTest.StructImplementsInterfaces.Expected.implements-interfaces.ts")] public async Task TestStructImplementsInterfacesFromGenerationSpec(Type type, string expectedLocation) diff --git a/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs b/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs index 27dee9eb..70237141 100644 --- a/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs +++ b/src/TypeGen/TypeGen.FileContentTest/TestingUtils/GenerationTestBase.cs @@ -2,18 +2,49 @@ using System.Threading.Tasks; using FluentAssertions; using TypeGen.Core.Generator; +using TypeGen.Core.Logging; using TypeGen.Core.SpecGeneration; using TypeGen.FileContentTest.Extensions; +using TypeGen.IntegrationTest; +using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.TestingUtils; public class GenerationTestBase { + private class TestLogger : ILogger + { + private readonly ITestOutputHelper output; + + public TestLogger(ITestOutputHelper output) + { + this.output = output; + } + + public LogLevel MinLevel { get; set; } = LogLevel.Debug; + + void ILogger.Log(string message, LogLevel level) + { + output.WriteLine(message); + } + } + + private readonly ITestOutputHelper output; + private readonly ILogger logger; + protected GenerationTestBase(ITestOutputHelper output) + { + this.output = output; + this.logger = new TestLogger(output); + } + protected async Task TestFromAssembly(Type type, string expectedLocation) { var readExpectedTask = EmbededResourceReader.GetEmbeddedResourceAsync(expectedLocation); - var generator = new Generator(); + GeneratorOptions options = new GeneratorOptions(); + + var generator = new Generator(options, logger); var interceptor = GeneratorOutputInterceptor.CreateInterceptor(generator); await generator.GenerateAsync(type.Assembly); @@ -23,11 +54,11 @@ protected async Task TestFromAssembly(Type type, string expectedLocation) interceptor.GeneratedOutputs[type].Content.NormalizeFileContent().Should().Be(expected); } - protected static async Task TestGenerationSpec(Type type, string expectedLocation, + protected async Task TestGenerationSpec(Type type, string expectedLocation, GenerationSpec generationSpec, GeneratorOptions generatorOptions) { var readExpectedTask = EmbededResourceReader.GetEmbeddedResourceAsync(expectedLocation); - var generator = new Generator(generatorOptions); + var generator = new Generator(generatorOptions, logger); var interceptor = GeneratorOutputInterceptor.CreateInterceptor(generator); await generator.GenerateAsync(new[] { generationSpec }); diff --git a/src/TypeGen/TypeGen.FileContentTest/TsClassExtendsTsInterface/TsClassExtendsTsInterfaceTest.cs b/src/TypeGen/TypeGen.FileContentTest/TsClassExtendsTsInterface/TsClassExtendsTsInterfaceTest.cs index bb3400f1..be61b1bf 100644 --- a/src/TypeGen/TypeGen.FileContentTest/TsClassExtendsTsInterface/TsClassExtendsTsInterfaceTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/TsClassExtendsTsInterface/TsClassExtendsTsInterfaceTest.cs @@ -4,11 +4,14 @@ using TypeGen.FileContentTest.TestingUtils; using TypeGen.FileContentTest.TsClassExtendsTsInterface.Entities; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.TsClassExtendsTsInterface; public class TsClassExtendsTsInterfaceTest : GenerationTestBase { + public TsClassExtendsTsInterfaceTest(ITestOutputHelper output) : base(output) { } + [Fact] public async Task TsClassExtendsTsInterface_Test() { diff --git a/src/TypeGen/TypeGen.FileContentTest/TsInterfaceInheritance/TsInterfaceInheritanceTest.cs b/src/TypeGen/TypeGen.FileContentTest/TsInterfaceInheritance/TsInterfaceInheritanceTest.cs index 0a898080..86050fd0 100644 --- a/src/TypeGen/TypeGen.FileContentTest/TsInterfaceInheritance/TsInterfaceInheritanceTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/TsInterfaceInheritance/TsInterfaceInheritanceTest.cs @@ -4,11 +4,14 @@ using TypeGen.FileContentTest.TestingUtils; using TypeGen.FileContentTest.TsInterfaceInheritance.Entities; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.TsInterfaceInheritance; public class TsInterfaceInheritanceTest : GenerationTestBase { + public TsInterfaceInheritanceTest(ITestOutputHelper output) : base(output) { } + [Fact] public async Task cs_classes_which_are_ts_interfaces_should_respect_ts_interface_inheritance() { diff --git a/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj b/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj index b8922566..72d17b0b 100644 --- a/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj +++ b/src/TypeGen/TypeGen.FileContentTest/TypeGen.FileContentTest.csproj @@ -21,6 +21,8 @@ + + @@ -56,6 +58,8 @@ + + diff --git a/src/TypeGen/TypeGen.FileContentTest/UseDefaultExportBreaksInterfaceInheritance/UseDefaultExportBreaksInterfaceInheritanceTest.cs b/src/TypeGen/TypeGen.FileContentTest/UseDefaultExportBreaksInterfaceInheritance/UseDefaultExportBreaksInterfaceInheritanceTest.cs index b3ad0a36..23674323 100644 --- a/src/TypeGen/TypeGen.FileContentTest/UseDefaultExportBreaksInterfaceInheritance/UseDefaultExportBreaksInterfaceInheritanceTest.cs +++ b/src/TypeGen/TypeGen.FileContentTest/UseDefaultExportBreaksInterfaceInheritance/UseDefaultExportBreaksInterfaceInheritanceTest.cs @@ -4,11 +4,14 @@ using TypeGen.FileContentTest.TestingUtils; using TypeGen.FileContentTest.UseDefaultExportBreaksInterfaceInheritance.Entities; using Xunit; +using Xunit.Abstractions; namespace TypeGen.FileContentTest.UseDefaultExportBreaksInterfaceInheritance; public class UseDefaultExportBreaksInterfaceInheritanceTest : GenerationTestBase { + public UseDefaultExportBreaksInterfaceInheritanceTest(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(typeof(ProductDto), "TypeGen.FileContentTest.UseDefaultExportBreaksInterfaceInheritance.Expected.product-dto.ts")] public async Task TestUseDefaultExportBreaksInterfaceInheritanceGenerationSpec(Type type, string expectedLocation) diff --git a/src/TypeGen/TypeGen.FileContentTest/tgconfig.json b/src/TypeGen/TypeGen.FileContentTest/tgconfig.json index 23fac4de..3f1b9601 100644 --- a/src/TypeGen/TypeGen.FileContentTest/tgconfig.json +++ b/src/TypeGen/TypeGen.FileContentTest/tgconfig.json @@ -1,7 +1,8 @@ { "externalAssemblyPaths": ["C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"], "outputPath": "generated-typescript", - "propertyNameConverters": ["JsonMemberName", "PascalCaseToCamelCaseJson"], + "propertyNameConverters": [ "JsonMemberName", "PascalCaseToCamelCaseJson" ], + "fileNameConverters": [ "PascalCaseToKebabCase", "AddFolder" ], "csNullableTranslation": null, "createIndexFile": true } \ No newline at end of file