Skip to content

Commit a317132

Browse files
authored
Merge pull request #312 from froebel/master
Support for nullable reference types.
2 parents 375d02e + 08deb9c commit a317132

File tree

8 files changed

+117
-2
lines changed

8 files changed

+117
-2
lines changed

XmlSchemaClassGenerator.Console/Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static void Main(string[] args)
5454
var uniqueTypeNamesAcrossNamespaces = false;
5555
var createGeneratedCodeAttributeVersion = true;
5656
var netCoreSpecificCode = false;
57+
var nullableReferenceAttributes = false;
5758
var generateCommandLineArgs = true;
5859

5960
var options = new OptionSet {
@@ -126,7 +127,8 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
126127
v => commentLanguages = v.Split(',').Select(l => l.Trim()).ToArray() },
127128
{ "un|uniqueTypeNames", "generate type names that are unique across namespaces (default is false)", v => uniqueTypeNamesAcrossNamespaces = v != null },
128129
{ "gc|generatedCodeAttribute", "add version information to GeneratedCodeAttribute (default is true)", v => createGeneratedCodeAttributeVersion = v != null },
129-
{ "nc|netCore", "generate .NET Core specific code that might not work with .NET Framework (default is false)", v => netCoreSpecificCode = v != null },
130+
{ "nc|netCore", "generate .NET Core specific code that might not work with .NET Framework (default is false)", v => netCoreSpecificCode = v != null },
131+
{ "nr|nullableReferenceAttributes", "generate attributes for nullable reference types (default is false)", v => nullableReferenceAttributes = v != null },
130132
{ "ca|commandArgs", "generate a comment with the exact command line arguments that were used to generate the source code (default is true)", v => generateCommandLineArgs = v != null },
131133
};
132134

@@ -203,6 +205,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
203205
UniqueTypeNamesAcrossNamespaces = uniqueTypeNamesAcrossNamespaces,
204206
CreateGeneratedCodeAttributeVersion = createGeneratedCodeAttributeVersion,
205207
NetCoreSpecificCode = netCoreSpecificCode,
208+
EnableNullableReferenceAttributes = nullableReferenceAttributes,
206209
GenerateCommandLineArgumentsComment = generateCommandLineArgs,
207210
};
208211

XmlSchemaClassGenerator.Tests/Compiler.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public static Assembly GenerateFiles(string name, IEnumerable<string> files, Gen
113113
UniqueTypeNamesAcrossNamespaces = generatorPrototype.UniqueTypeNamesAcrossNamespaces,
114114
CreateGeneratedCodeAttributeVersion = generatorPrototype.CreateGeneratedCodeAttributeVersion,
115115
NetCoreSpecificCode = generatorPrototype.NetCoreSpecificCode,
116+
EnableNullableReferenceAttributes = generatorPrototype.EnableNullableReferenceAttributes,
116117
NamingScheme = generatorPrototype.NamingScheme
117118
};
118119

XmlSchemaClassGenerator.Tests/XmlTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.CodeDom;
33
using System.Collections.Generic;
44
using System.Collections.ObjectModel;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.IO;
67
using System.Linq;
78
using System.Reflection;
@@ -14,6 +15,7 @@
1415
using System.Xml.XPath;
1516
using Ganss.IO;
1617
using Microsoft.CodeAnalysis;
18+
using Microsoft.CodeAnalysis.CSharp.Syntax;
1719
using Microsoft.Xml.XMLGen;
1820
using Xunit;
1921
using Xunit.Abstractions;
@@ -105,6 +107,7 @@ private static IEnumerable<string> ConvertXml(string name, string xsd, Generator
105107
const string DtsxPattern = "xsd/dtsx/dtsx2.xsd";
106108
const string WfsPattern = "xsd/wfs/schemas.opengis.net/wfs/2.0/wfs.xsd";
107109
const string EppPattern = "xsd/epp/*.xsd";
110+
const string NullableReferenceAttributesPattern = "xsd/nullablereferenceattributes/nullablereference.xsd";
108111

109112
// IATA test takes too long to perform every time
110113

@@ -2366,6 +2369,42 @@ void UnknownAttributeHandler(object sender, XmlAttributeEventArgs e)
23662369
*/
23672370
}
23682371

2372+
[Fact, TestPriority(1)]
2373+
[UseCulture("en-US")]
2374+
public void TestNullableReferenceAttributes()
2375+
{
2376+
var files = Glob.ExpandNames(NullableReferenceAttributesPattern).OrderByDescending(f => f);
2377+
var generator = new Generator
2378+
{
2379+
EnableNullableReferenceAttributes = true,
2380+
UseShouldSerializePattern = true,
2381+
NamespaceProvider = new NamespaceProvider
2382+
{
2383+
GenerateNamespace = key => "Test"
2384+
}
2385+
};
2386+
var assembly = Compiler.Generate(nameof(TestNullableReferenceAttributes), NullableReferenceAttributesPattern, generator);
2387+
void assertNullable(string typename, bool nullable)
2388+
{
2389+
Type c = assembly.GetType(typename);
2390+
var property = c.GetProperty("Text");
2391+
var setParameter = property.SetMethod.GetParameters();
2392+
var getReturnParameter = property.GetMethod.ReturnParameter;
2393+
var allowNullableAttribute = setParameter.Single().CustomAttributes.SingleOrDefault(a => a.AttributeType == typeof(AllowNullAttribute));
2394+
var maybeNullAttribute = getReturnParameter.CustomAttributes.SingleOrDefault(a => a.AttributeType == typeof(MaybeNullAttribute));
2395+
var hasAllowNullAttribute = allowNullableAttribute != null;
2396+
var hasMaybeNullAttribute = maybeNullAttribute != null;
2397+
Assert.Equal(nullable, hasAllowNullAttribute);
2398+
Assert.Equal(nullable, hasMaybeNullAttribute);
2399+
}
2400+
assertNullable("Test.ElementReferenceNullable", true);
2401+
assertNullable("Test.ElementReferenceList", true);
2402+
assertNullable("Test.ElementReferenceNonNullable", false);
2403+
assertNullable("Test.AttributeReferenceNullable", true);
2404+
assertNullable("Test.AttributeReferenceNonNullable", false);
2405+
assertNullable("Test.AttributeValueNullableInt", false);
2406+
}
2407+
23692408
[Fact, TestPriority(1)]
23702409
public void TestNetex()
23712410
{
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<xs:schema targetNamespace="http://tempuri.org/XMLSchema.xsd"
3+
elementFormDefault="qualified"
4+
xmlns="http://tempuri.org/XMLSchema.xsd"
5+
xmlns:mstns="http://tempuri.org/XMLSchema.xsd"
6+
xmlns:xs="http://www.w3.org/2001/XMLSchema"
7+
>
8+
9+
<xs:element name="ElementReferenceNullable">
10+
<xs:complexType>
11+
<xs:sequence>
12+
<xs:element name="text" type="xs:string" minOccurs="0" />
13+
</xs:sequence>
14+
</xs:complexType>
15+
</xs:element>
16+
<xs:element name="ElementReferenceList">
17+
<xs:complexType>
18+
<xs:sequence>
19+
<xs:element name="text" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
20+
</xs:sequence>
21+
</xs:complexType>
22+
</xs:element>
23+
<xs:element name="ElementReferenceNonNullable">
24+
<xs:complexType>
25+
<xs:sequence>
26+
<xs:element name="text" type="xs:string" minOccurs="1" />
27+
</xs:sequence>
28+
</xs:complexType>
29+
</xs:element>
30+
31+
<xs:element name="AttributeReferenceNullable">
32+
<xs:complexType>
33+
<xs:attribute name="text" type="xs:string" use="optional" />
34+
</xs:complexType>
35+
</xs:element>
36+
<xs:element name="AttributeReferenceNonNullable">
37+
<xs:complexType>
38+
<xs:attribute name="text" type="xs:string" use="required" />
39+
</xs:complexType>
40+
</xs:element>
41+
<xs:element name="AttributeValueNullableInt">
42+
<xs:complexType>
43+
<xs:attribute name="text" type="xs:int" use="optional" />
44+
</xs:complexType>
45+
</xs:element>
46+
</xs:schema>

XmlSchemaClassGenerator/CodeUtilities.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ public static KeyValuePair<NamespaceKey, string> ParseNamespace(string nsArg, st
351351
var parts = nsArg.Split(new[] { '=' }, 2);
352352
if (parts.Length != 2)
353353
{
354-
throw new ArgumentException("XML and C# namespaces should be separated by '='.");
354+
throw new ArgumentException("XML and C# namespaces should be separated by '='. You entered: " + nsArg);
355355
}
356356

357357
var xmlNs = parts[0];

XmlSchemaClassGenerator/Generator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ public bool GenerateNullables
115115
set { _configuration.GenerateNullables = value; }
116116
}
117117

118+
public bool EnableNullableReferenceAttributes
119+
{
120+
get { return _configuration.EnableNullableReferenceAttributes; }
121+
set { _configuration.EnableNullableReferenceAttributes = value; }
122+
}
123+
118124
public bool UseShouldSerializePattern
119125
{
120126
get { return _configuration.UseShouldSerializePattern; }

XmlSchemaClassGenerator/GeneratorConfiguration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ public GeneratorConfiguration()
7676
/// Use XElement instead of XmlElement for Any nodes?
7777
/// </summary>
7878
public bool UseXElementForAny { get; set; }
79+
/// <summary>
80+
/// Generate attributes for nullable references to avoid compiler-warnings in .NET Core and Standard with nullable-checks.
81+
/// </summary>
82+
public bool EnableNullableReferenceAttributes { get; set; }
7983

8084
private NamingScheme namingScheme;
8185

XmlSchemaClassGenerator/TypeModel.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,15 @@ private bool IsNullableValueType
750750
&& IsNullable && !(IsCollection || IsArray) && !IsList
751751
&& ((PropertyType is EnumModel) || (PropertyType is SimpleModel model && model.ValueType.IsValueType));
752752
}
753+
}
754+
755+
private bool IsNullableReferenceType
756+
{
757+
get
758+
{
759+
return DefaultValue == null
760+
&& IsNullable && (IsCollection || IsArray || IsList || PropertyType is ClassModel || PropertyType is SimpleModel model && !model.ValueType.IsValueType);
761+
}
753762
}
754763

755764
private bool IsNillableValueType
@@ -860,6 +869,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi
860869
var isArray = IsArray;
861870
var propertyType = PropertyType;
862871
var isNullableValueType = IsNullableValueType;
872+
var isNullableReferenceType = IsNullableReferenceType;
863873
var typeReference = TypeReference;
864874

865875
var requiresBackingField = withDataBinding || DefaultValue != null || IsCollection || isArray;
@@ -1114,6 +1124,12 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi
11141124
typeDeclaration.Members.Add(specifiedProperty);
11151125
}
11161126

1127+
if (isNullableReferenceType && Configuration.EnableNullableReferenceAttributes)
1128+
{
1129+
member.CustomAttributes.Add(new CodeAttributeDeclaration("System.Diagnostics.CodeAnalysis.AllowNullAttribute"));
1130+
member.CustomAttributes.Add(new CodeAttributeDeclaration("System.Diagnostics.CodeAnalysis.MaybeNullAttribute"));
1131+
}
1132+
11171133
var attributes = GetAttributes(isArray).ToArray();
11181134
member.CustomAttributes.AddRange(attributes);
11191135

0 commit comments

Comments
 (0)