Skip to content

Commit

Permalink
Fix #456
Browse files Browse the repository at this point in the history
  • Loading branch information
mganss committed Dec 11, 2023
1 parent 8c1c5b1 commit 02e2605
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 46 deletions.
3 changes: 2 additions & 1 deletion XmlSchemaClassGenerator.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
{ "cit|collectionImplementationType=", "the default collection type implementation to use (default is null)", v => collectionImplementationType = v == null ? null : Type.GetType(v, true) },
{ "csm|collectionSettersMode=", @"generate a private, public or public setters
without backing field initialization for collections
(default is Private; can be: {Private, Public, PublicWithoutConstructorInitialization})",
(default is Private; can be: {Private, Public, PublicWithoutConstructorInitialization, Init})",
v =>
{
collectionSettersMode = v switch
Expand All @@ -129,6 +129,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
"pu" or "Public" => CollectionSettersMode.Public,
"puwci" or "PublicWithoutConstructorInitialization" =>
CollectionSettersMode.PublicWithoutConstructorInitialization,
"in" or "Init" => CollectionSettersMode.Init,
_ => CollectionSettersMode.Private
};
}},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<LangVersion>latest</LangVersion>
<LangVersion>preview</LangVersion>
<CoverletOutputFormat>opencover</CoverletOutputFormat>
<CoverletOutput>../coverage.xml</CoverletOutput>
<Include>[XmlSchemaClassGenerator]*</Include>
Expand Down
105 changes: 69 additions & 36 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,9 @@
namespace XmlSchemaClassGenerator.Tests
{
[TestCaseOrderer("XmlSchemaClassGenerator.Tests.PriorityOrderer", "XmlSchemaClassGenerator.Tests")]
public class XmlTests
public class XmlTests(ITestOutputHelper output)
{
private readonly ITestOutputHelper Output;

public XmlTests(ITestOutputHelper output)
{
Output = output;
}
private readonly ITestOutputHelper Output = output;

private static IEnumerable<string> ConvertXml(string name, IEnumerable<string> xsds, Generator generatorPrototype = null)
{
Expand Down Expand Up @@ -300,7 +295,8 @@ public void TestListWithPublicPropertySetters()
[Fact]
public void TestListWithPublicPropertySettersWithoutConstructors()
{
var assembly = Compiler.Generate("ListPublicWithoutConstructorInitialization", ListPattern, new Generator {
var assembly = Compiler.Generate("ListPublicWithoutConstructorInitialization", ListPattern, new Generator
{
GenerateNullables = true,
IntegerDataType = typeof(int),
DataAnnotationMode = DataAnnotationMode.All,
Expand All @@ -317,10 +313,45 @@ public void TestListWithPublicPropertySettersWithoutConstructors()
var myClassType = assembly.GetType("List.MyClass");
Assert.NotNull(myClassType);
var iListType = typeof(Collection<>);
var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p=>p.Name).ToList();
var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).OrderBy(p=>p.Name).ToList();
var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p => p.Name).ToList();
var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).OrderBy(p => p.Name).ToList();
Assert.NotEmpty(collectionPropertyInfos);
Assert.Equal(collectionPropertyInfos, publicCollectionPropertyInfos);
var myClassInstance = Activator.CreateInstance(myClassType);
foreach (var collectionPropertyInfo in publicCollectionPropertyInfos)
{
Assert.Null(collectionPropertyInfo.GetValue(myClassInstance));
}
}

[Fact]
public void TestListWithInitPropertySettersWithoutConstructors()
{
var assembly = Compiler.Generate("ListInitWithoutConstructorInitialization", ListPattern, new Generator
{
GenerateNullables = true,
IntegerDataType = typeof(int),
DataAnnotationMode = DataAnnotationMode.All,
GenerateDesignerCategoryAttribute = false,
GenerateComplexTypesForCollections = true,
EntityFramework = false,
GenerateInterfaces = true,
NamespacePrefix = "List",
GenerateDescriptionAttribute = true,
TextValuePropertyName = "Value",
CollectionSettersMode = CollectionSettersMode.Init
});
Assert.NotNull(assembly);
var myClassType = assembly.GetType("List.MyClass");
Assert.NotNull(myClassType);
var iListType = typeof(Collection<>);
var collectionPropertyInfos = myClassType.GetProperties().Where(p => p.PropertyType.IsGenericType && iListType.IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())).OrderBy(p => p.Name).ToList();
var publicCollectionPropertyInfos = collectionPropertyInfos.Where(p => p.SetMethod.IsPublic).OrderBy(p => p.Name).ToList();
Assert.NotEmpty(collectionPropertyInfos);
Assert.Equal(collectionPropertyInfos, publicCollectionPropertyInfos);
var requiredCustomModifiers = collectionPropertyInfos.Select(p => p.SetMethod.ReturnParameter.GetRequiredCustomModifiers()).ToList();
Assert.Equal(collectionPropertyInfos.Count, requiredCustomModifiers.Count);
Assert.All(requiredCustomModifiers, m => Assert.Contains(typeof(System.Runtime.CompilerServices.IsExternalInit), m));
var myClassInstance = Activator.CreateInstance(myClassType);
foreach (var collectionPropertyInfo in publicCollectionPropertyInfos)
{
Expand Down Expand Up @@ -393,14 +424,16 @@ public void TestListWithPublicPropertySettersWithoutConstructorsSpecified()
}
}

public static IEnumerable<object[]> TestSimpleData() {
public static TheoryData<CodeTypeReferenceOptions, NamingScheme, Type> TestSimpleData() {
var theoryData = new TheoryData<CodeTypeReferenceOptions, NamingScheme, Type>();
foreach (var referenceMode in new[]
{ CodeTypeReferenceOptions.GlobalReference, /*CodeTypeReferenceOptions.GenericTypeParameter*/ })
foreach (var namingScheme in new[] { NamingScheme.Direct, NamingScheme.PascalCase })
foreach (var collectionType in new[] { typeof(Collection<>), /*typeof(Array)*/ })
{
yield return new object[] { referenceMode, namingScheme, collectionType };
theoryData.Add(referenceMode, namingScheme, collectionType);
}
return theoryData;
}


Expand Down Expand Up @@ -686,28 +719,28 @@ void UnknownAttributeHandler(object sender, XmlAttributeEventArgs e)

private static readonly XmlQualifiedName AnyType = new("anyType", XmlSchema.Namespace);

public static IEnumerable<object[]> Classes => new List<object[]>
{
new object[] { "ApartmentBuy" },
new object[] { "ApartmentRent" },
new object[] { "AssistedLiving" },
new object[] { "CompulsoryAuction" },
new object[] { "GarageBuy" },
new object[] { "GarageRent" },
new object[] { "Gastronomy" },
new object[] { "HouseBuy" },
new object[] { "HouseRent" },
new object[] { "HouseType" },
new object[] { "Industry" },
new object[] { "Investment" },
new object[] { "LivingBuySite" },
new object[] { "LivingRentSite" },
new object[] { "Office" },
new object[] { "SeniorCare" },
new object[] { "ShortTermAccommodation" },
new object[] { "SpecialPurpose" },
new object[] { "Store" },
new object[] { "TradeSite" },
public static TheoryData<string> Classes => new()
{
"ApartmentBuy",
"ApartmentRent",
"AssistedLiving",
"CompulsoryAuction",
"GarageBuy",
"GarageRent",
"Gastronomy",
"HouseBuy",
"HouseRent",
"HouseType",
"Industry",
"Investment",
"LivingBuySite",
"LivingRentSite",
"Office",
"SeniorCare",
"ShortTermAccommodation",
"SpecialPurpose",
"Store",
"TradeSite",
};

[Theory, TestPriority(2)]
Expand Down Expand Up @@ -772,7 +805,7 @@ public void TestCustomNamespaces()
{ diXsd, "DiagramElement" }
};

List<string> customNamespaceConfig = new();
List<string> customNamespaceConfig = [];

foreach (var ns in xsdToCsharpNsMap)
customNamespaceConfig.Add(string.Format(customNsPattern, ns.Key, ns.Value));
Expand Down Expand Up @@ -1478,7 +1511,7 @@ public void DoNotGenerateIntermediaryClassForArrayElements()

var fooType = assembly.DefinedTypes.Single(t => t.FullName == "Test.Foo");
Assert.NotNull(fooType);
Assert.True(fooType.FullName == "Test.Foo");
Assert.Equal("Test.Foo", fooType.FullName);
}

[Fact]
Expand Down
8 changes: 6 additions & 2 deletions XmlSchemaClassGenerator/CollectionSettersMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ public enum CollectionSettersMode
/// </summary>
Public,
/// <summary>
/// All collection setters are public and baking collections fields not initialized in constructors
/// All collection setters are public and backing collections fields are not initialized in constructors
/// </summary>
PublicWithoutConstructorInitialization
PublicWithoutConstructorInitialization,
/// <summary>
/// All collections setters are init-only
/// </summary>
Init
}
}
18 changes: 12 additions & 6 deletions XmlSchemaClassGenerator/TypeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -548,16 +548,15 @@ public void SetSchemaNameAndNamespace(TypeModel owningTypeModel, IXmlSchemaNode
: xs.QualifiedName.Namespace;
}

internal static string GetAccessors(CodeMemberField backingField = null, bool withDataBinding = false, PropertyValueTypeCode typeCode = PropertyValueTypeCode.Other, bool privateSetter = false)
internal static string GetAccessors(CodeMemberField backingField = null, bool withDataBinding = false, PropertyValueTypeCode typeCode = PropertyValueTypeCode.Other, string setter = "set")
{
var privateString = privateSetter ? "private " : string.Empty;
return backingField == null ? " { get; set; }" : CodeUtilities.NormalizeNewlines($@"
{{
get
{{
return {backingField.Name};
}}
{privateString}set
{setter}
{{{(typeCode, withDataBinding) switch
{
(PropertyValueTypeCode.ValueType, true) => $@"
Expand Down Expand Up @@ -730,7 +729,13 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi
}

var propertyValueTypeCode = IsCollection || isArray ? PropertyValueTypeCode.Array : propertyType.GetPropertyValueTypeCode();
member.Name += GetAccessors(backingField, withDataBinding, propertyValueTypeCode, IsPrivateSetter);
var setter = Configuration.CollectionSettersMode switch
{
CollectionSettersMode.Private when IsEnumerable => "private set",
CollectionSettersMode.Init when IsEnumerable => "init",
_ => "set"
};
member.Name += GetAccessors(backingField, withDataBinding, propertyValueTypeCode, setter);
}
else
{
Expand Down Expand Up @@ -869,7 +874,7 @@ [new CodeMethodReturnStatement(valueExpression)],
var countProperty = collectionType == typeof(Array) ? nameof(Array.Length) : nameof(List<int>.Count);
var countReference = new CodePropertyReferenceExpression(listReference, countProperty);
var notZeroExpression = new CodeBinaryOperatorExpression(countReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(0));
if (Configuration.CollectionSettersMode is CollectionSettersMode.PublicWithoutConstructorInitialization or CollectionSettersMode.Public)
if (Configuration.CollectionSettersMode is CollectionSettersMode.PublicWithoutConstructorInitialization or CollectionSettersMode.Public or CollectionSettersMode.Init)
{
var notNullExpression = new CodeBinaryOperatorExpression(listReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null));
notZeroExpression = new CodeBinaryOperatorExpression(notNullExpression, CodeBinaryOperatorType.BooleanAnd, notZeroExpression);
Expand Down Expand Up @@ -927,7 +932,8 @@ [new CodeMethodReturnStatement(valueExpression)],
member.CustomAttributes.AddRange(attributes);

// initialize List<>
if (isEnumerable && Configuration.CollectionSettersMode != CollectionSettersMode.PublicWithoutConstructorInitialization)
if (isEnumerable && (Configuration.CollectionSettersMode != CollectionSettersMode.PublicWithoutConstructorInitialization)
&& (Configuration.CollectionSettersMode != CollectionSettersMode.Init))
{
var constructor = typeDeclaration.Members.OfType<CodeConstructor>().FirstOrDefault();

Expand Down

0 comments on commit 02e2605

Please sign in to comment.