Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Try to use object constructors for complex property values where possible #162

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nuget-dotnetcli/dotnet-typegen.nuspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>dotnet-typegen</id>
<id>CIERA.dotnet-typegen</id>
<version>5.0.0</version>
<authors>Jacek Burzynski</authors>
<owners>Jacek Burzynski</owners>
Expand Down
4 changes: 2 additions & 2 deletions nuget/TypeGen.nuspec
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>TypeGen</id>
<id>CIERA.TypeGen</id>
<version>5.0.0</version>
<authors>Jacek Burzynski</authors>
<owners>Jacek Burzynski</owners>
<owners>NCMEC</owners>
<license type="file">LICENSE</license>
<projectUrl>https://github.com/jburzynski/TypeGen</projectUrl>
<iconUrl>https://raw.githubusercontent.com/jburzynski/type-gen/master/docs/icon.png</iconUrl>
Expand Down
13 changes: 10 additions & 3 deletions src/TypeGen/TypeGen.Cli.Test/CliSmokeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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);
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public GeneratorOptions GetGeneratorOptions(TgConfig config, IEnumerable<Assembl
UseDefaultExport = config.UseDefaultExport ?? GeneratorOptions.DefaultUseDefaultExport,
ExportTypesAsInterfacesByDefault = config.ExportTypesAsInterfacesByDefault ?? GeneratorOptions.DefaultExportTypesAsInterfacesByDefault,
UseImportType = config.UseImportType ?? GeneratorOptions.DefaultUseImportType,
TypeBlacklist = GetTypeBlacklist(config.TypeBlacklist, config.TypeWhitelist)
TypeBlacklist = GetTypeBlacklist(config.TypeBlacklist, config.TypeWhitelist),
StrictMode = config.StrictMode ?? GeneratorOptions.DefaultStrictMode,
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/TypeGen/TypeGen.Cli/GenerationConfig/TgConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal class TgConfig
public bool? UseImportType { get; set; }
public string[] TypeBlacklist { get; set; }
public string[] TypeWhitelist { get; set; }
public bool? StrictMode { get; set; }

public TgConfig Normalize()
{
Expand Down Expand Up @@ -101,6 +102,7 @@ public TgConfig MergeWithDefaultParams()
if (UseImportType == null) UseImportType = GeneratorOptions.DefaultUseImportType;
if (TypeBlacklist == null) TypeBlacklist = DefaultTypeBlacklist;
if (TypeWhitelist == null) TypeWhitelist = DefaultTypeWhitelist;
if (StrictMode == null) StrictMode = GeneratorOptions.DefaultStrictMode;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public void GetMemberValueText_MemberGiven_CorrectValueReturned(MemberInfo membe
var tsContentGenerator = new TsContentGenerator(_typeDependencyService, typeService, _templateService, _tsContentParser, _metadataReaderFactory, generatorOptionsProvider, null);

//act
string actual = tsContentGenerator.GetMemberValueText(memberInfo);
string actual = tsContentGenerator.GetMemberValueText(memberInfo, false);

//assert
Assert.Equal(expected, actual);
Expand Down
56 changes: 32 additions & 24 deletions src/TypeGen/TypeGen.Core/Generator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ public class Generator
/// An event that fires when a file's content is generated
/// </summary>
public event EventHandler<FileContentGeneratedArgs> FileContentGenerated;

/// <summary>
/// A logger instance used to log messages raised by a Generator instance
/// </summary>
public ILogger Logger { get; }

/// <summary>
/// Generator options. Cannot be null.
/// </summary>
public GeneratorOptions Options { get; }

private readonly MetadataReaderFactory _metadataReaderFactory;
private readonly ITypeService _typeService;
private readonly ITypeDependencyService _typeDependencyService;
Expand All @@ -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();
Expand All @@ -74,7 +74,7 @@ public Generator(GeneratorOptions options, ILogger logger = null)
generatorOptionsProvider,
logger);
}

public Generator(ILogger logger) : this(new GeneratorOptions(), logger)
{
}
Expand All @@ -91,7 +91,7 @@ public void SubscribeDefaultFileContentGeneratedHandler()
FileContentGenerated -= OnFileContentGenerated;
FileContentGenerated += OnFileContentGenerated;
}

/// <summary>
/// Unsubscribes the default FileContentGenerated event handler, which saves generated sources to the file system
/// </summary>
Expand Down Expand Up @@ -270,20 +270,20 @@ protected virtual void OnFileContentGenerated(object sender, FileContentGenerate
private IEnumerable<string> 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<string>();

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(
Expand All @@ -294,11 +294,11 @@ private IEnumerable<string> 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) };
}

/// <summary>
/// DEPRECATED, can be removed in the future.
/// Generates an `index.ts` file which exports all types within the generated files
Expand Down Expand Up @@ -339,7 +339,7 @@ private IEnumerable<string> GenerateMarkedType(Type type)

return files.Distinct();
}

/// <summary>
/// Contains the actual logic of generating TypeScript files for a given type
/// Should only be used inside GenerateTypeInit(), otherwise use GenerateTypeInit()
Expand Down Expand Up @@ -387,7 +387,7 @@ private IEnumerable<string> 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 });

Expand Down Expand Up @@ -429,6 +429,7 @@ private IEnumerable<string> GenerateClass(Type type, ExportTsClassAttribute clas

string importsText = _tsContentGenerator.GetImportsText(type, outputDir);
string propertiesText = GetClassPropertiesText(type);
string constructorText = _tsContentGenerator.GetConstructorText(type);

// generate the file content

Expand All @@ -445,8 +446,8 @@ private IEnumerable<string> 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));
Expand Down Expand Up @@ -482,7 +483,6 @@ private IEnumerable<string> GenerateInterface(Type type, ExportTsInterfaceAttrib

string importsText = _tsContentGenerator.GetImportsText(type, outputDir);
string propertiesText = GetInterfacePropertiesText(type);

// generate the file content

string tsTypeName = _typeService.GetTsTypeName(type, true);
Expand Down Expand Up @@ -517,7 +517,7 @@ private static List<string> GetNotNullOrEmptyImplementedInterfaceNames(TsCustomB
.Select(x => x.Name)
.Where(x => !string.IsNullOrEmpty(x))
.ToList();

/// <summary>
/// Generates a TypeScript enum file from a class type
/// </summary>
Expand Down Expand Up @@ -552,7 +552,7 @@ private bool IsStaticTsProperty(MemberInfo memberInfo)
if (_metadataReaderFactory.GetInstance().GetAttribute<TsNotStaticAttribute>(memberInfo) != null) return false;
return _metadataReaderFactory.GetInstance().GetAttribute<TsStaticAttribute>(memberInfo) != null || memberInfo.IsStatic();
}

private bool IsReadonlyTsProperty(MemberInfo memberInfo)
{
if (_metadataReaderFactory.GetInstance().GetAttribute<TsNotReadonlyAttribute>(memberInfo) != null) return false;
Expand Down Expand Up @@ -588,20 +588,28 @@ private string GetClassPropertyText(Type type, MemberInfo memberInfo)
isOptional = true;
}

var ctorAttribute = _metadataReaderFactory.GetInstance().GetAttribute<TsConstructorAttribute>(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<TsDefaultValueAttribute>(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);
}

Expand Down
6 changes: 6 additions & 0 deletions src/TypeGen/TypeGen.Core/Generator/GeneratorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> DefaultTypeBlacklist => new(new []
{
Expand Down Expand Up @@ -178,6 +179,11 @@ public string BaseOutputDirectory
/// </summary>
public bool ExportTypesAsInterfacesByDefault { get; set; } = DefaultExportTypesAsInterfacesByDefault;

/// <summary>
/// Whether to append null to type unions where null is the default value.
/// </summary>
public bool StrictMode { get; set; } = DefaultStrictMode;

/// <summary>
/// Whether to use "import type" instead of "import" for imports in TS sources.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ internal interface ITsContentGenerator
/// Gets text to be used as a member value
/// </summary>
/// <param name="memberInfo"></param>
/// <param name="fallback"></param>
/// <returns>The text to be used as a member value. Null if the member has no value or value cannot be determined.</returns>
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);
}
}
8 changes: 8 additions & 0 deletions src/TypeGen/TypeGen.Core/Generator/Services/ITypeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ internal interface ITypeService
/// <returns>TypeScript type name. Null if the passed type cannot be represented as a TypeScript simple type.</returns>
string GetTsBuiltInTypeName(Type type);

/// <summary>
/// Determines whether the type represents a TypeScript class
/// </summary>
/// <param name="type"></param>
/// <returns>True if the type represents a TypeScript class; false otherwise</returns>
/// <exception cref="ArgumentNullException">Thrown if the type is null</exception>
bool IsTsClass(Type type);

/// <summary>
/// Determines whether the type represents a TypeScript class
/// </summary>
Expand Down
Loading