Skip to content

Commit

Permalink
Merge pull request #312 from froebel/master
Browse files Browse the repository at this point in the history
Support for nullable reference types.
  • Loading branch information
mganss authored Mar 17, 2022
2 parents 375d02e + 08deb9c commit a317132
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 2 deletions.
5 changes: 4 additions & 1 deletion XmlSchemaClassGenerator.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ static void Main(string[] args)
var uniqueTypeNamesAcrossNamespaces = false;
var createGeneratedCodeAttributeVersion = true;
var netCoreSpecificCode = false;
var nullableReferenceAttributes = false;
var generateCommandLineArgs = true;

var options = new OptionSet {
Expand Down Expand Up @@ -126,7 +127,8 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
v => commentLanguages = v.Split(',').Select(l => l.Trim()).ToArray() },
{ "un|uniqueTypeNames", "generate type names that are unique across namespaces (default is false)", v => uniqueTypeNamesAcrossNamespaces = v != null },
{ "gc|generatedCodeAttribute", "add version information to GeneratedCodeAttribute (default is true)", v => createGeneratedCodeAttributeVersion = v != null },
{ "nc|netCore", "generate .NET Core specific code that might not work with .NET Framework (default is false)", v => netCoreSpecificCode = v != null },
{ "nc|netCore", "generate .NET Core specific code that might not work with .NET Framework (default is false)", v => netCoreSpecificCode = v != null },
{ "nr|nullableReferenceAttributes", "generate attributes for nullable reference types (default is false)", v => nullableReferenceAttributes = v != null },
{ "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 },
};

Expand Down Expand Up @@ -203,6 +205,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
UniqueTypeNamesAcrossNamespaces = uniqueTypeNamesAcrossNamespaces,
CreateGeneratedCodeAttributeVersion = createGeneratedCodeAttributeVersion,
NetCoreSpecificCode = netCoreSpecificCode,
EnableNullableReferenceAttributes = nullableReferenceAttributes,
GenerateCommandLineArgumentsComment = generateCommandLineArgs,
};

Expand Down
1 change: 1 addition & 0 deletions XmlSchemaClassGenerator.Tests/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public static Assembly GenerateFiles(string name, IEnumerable<string> files, Gen
UniqueTypeNamesAcrossNamespaces = generatorPrototype.UniqueTypeNamesAcrossNamespaces,
CreateGeneratedCodeAttributeVersion = generatorPrototype.CreateGeneratedCodeAttributeVersion,
NetCoreSpecificCode = generatorPrototype.NetCoreSpecificCode,
EnableNullableReferenceAttributes = generatorPrototype.EnableNullableReferenceAttributes,
NamingScheme = generatorPrototype.NamingScheme
};

Expand Down
39 changes: 39 additions & 0 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.CodeDom;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
Expand All @@ -14,6 +15,7 @@
using System.Xml.XPath;
using Ganss.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Xml.XMLGen;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -105,6 +107,7 @@ private static IEnumerable<string> ConvertXml(string name, string xsd, Generator
const string DtsxPattern = "xsd/dtsx/dtsx2.xsd";
const string WfsPattern = "xsd/wfs/schemas.opengis.net/wfs/2.0/wfs.xsd";
const string EppPattern = "xsd/epp/*.xsd";
const string NullableReferenceAttributesPattern = "xsd/nullablereferenceattributes/nullablereference.xsd";

// IATA test takes too long to perform every time

Expand Down Expand Up @@ -2366,6 +2369,42 @@ void UnknownAttributeHandler(object sender, XmlAttributeEventArgs e)
*/
}

[Fact, TestPriority(1)]
[UseCulture("en-US")]
public void TestNullableReferenceAttributes()
{
var files = Glob.ExpandNames(NullableReferenceAttributesPattern).OrderByDescending(f => f);
var generator = new Generator
{
EnableNullableReferenceAttributes = true,
UseShouldSerializePattern = true,
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test"
}
};
var assembly = Compiler.Generate(nameof(TestNullableReferenceAttributes), NullableReferenceAttributesPattern, generator);
void assertNullable(string typename, bool nullable)
{
Type c = assembly.GetType(typename);
var property = c.GetProperty("Text");
var setParameter = property.SetMethod.GetParameters();
var getReturnParameter = property.GetMethod.ReturnParameter;
var allowNullableAttribute = setParameter.Single().CustomAttributes.SingleOrDefault(a => a.AttributeType == typeof(AllowNullAttribute));
var maybeNullAttribute = getReturnParameter.CustomAttributes.SingleOrDefault(a => a.AttributeType == typeof(MaybeNullAttribute));
var hasAllowNullAttribute = allowNullableAttribute != null;
var hasMaybeNullAttribute = maybeNullAttribute != null;
Assert.Equal(nullable, hasAllowNullAttribute);
Assert.Equal(nullable, hasMaybeNullAttribute);
}
assertNullable("Test.ElementReferenceNullable", true);
assertNullable("Test.ElementReferenceList", true);
assertNullable("Test.ElementReferenceNonNullable", false);
assertNullable("Test.AttributeReferenceNullable", true);
assertNullable("Test.AttributeReferenceNonNullable", false);
assertNullable("Test.AttributeValueNullableInt", false);
}

[Fact, TestPriority(1)]
public void TestNetex()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://tempuri.org/XMLSchema.xsd"
elementFormDefault="qualified"
xmlns="http://tempuri.org/XMLSchema.xsd"
xmlns:mstns="http://tempuri.org/XMLSchema.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>

<xs:element name="ElementReferenceNullable">
<xs:complexType>
<xs:sequence>
<xs:element name="text" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ElementReferenceList">
<xs:complexType>
<xs:sequence>
<xs:element name="text" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ElementReferenceNonNullable">
<xs:complexType>
<xs:sequence>
<xs:element name="text" type="xs:string" minOccurs="1" />
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="AttributeReferenceNullable">
<xs:complexType>
<xs:attribute name="text" type="xs:string" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="AttributeReferenceNonNullable">
<xs:complexType>
<xs:attribute name="text" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="AttributeValueNullableInt">
<xs:complexType>
<xs:attribute name="text" type="xs:int" use="optional" />
</xs:complexType>
</xs:element>
</xs:schema>
2 changes: 1 addition & 1 deletion XmlSchemaClassGenerator/CodeUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ public static KeyValuePair<NamespaceKey, string> ParseNamespace(string nsArg, st
var parts = nsArg.Split(new[] { '=' }, 2);
if (parts.Length != 2)
{
throw new ArgumentException("XML and C# namespaces should be separated by '='.");
throw new ArgumentException("XML and C# namespaces should be separated by '='. You entered: " + nsArg);
}

var xmlNs = parts[0];
Expand Down
6 changes: 6 additions & 0 deletions XmlSchemaClassGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ public bool GenerateNullables
set { _configuration.GenerateNullables = value; }
}

public bool EnableNullableReferenceAttributes
{
get { return _configuration.EnableNullableReferenceAttributes; }
set { _configuration.EnableNullableReferenceAttributes = value; }
}

public bool UseShouldSerializePattern
{
get { return _configuration.UseShouldSerializePattern; }
Expand Down
4 changes: 4 additions & 0 deletions XmlSchemaClassGenerator/GeneratorConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ public GeneratorConfiguration()
/// Use XElement instead of XmlElement for Any nodes?
/// </summary>
public bool UseXElementForAny { get; set; }
/// <summary>
/// Generate attributes for nullable references to avoid compiler-warnings in .NET Core and Standard with nullable-checks.
/// </summary>
public bool EnableNullableReferenceAttributes { get; set; }

private NamingScheme namingScheme;

Expand Down
16 changes: 16 additions & 0 deletions XmlSchemaClassGenerator/TypeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,15 @@ private bool IsNullableValueType
&& IsNullable && !(IsCollection || IsArray) && !IsList
&& ((PropertyType is EnumModel) || (PropertyType is SimpleModel model && model.ValueType.IsValueType));
}
}

private bool IsNullableReferenceType
{
get
{
return DefaultValue == null
&& IsNullable && (IsCollection || IsArray || IsList || PropertyType is ClassModel || PropertyType is SimpleModel model && !model.ValueType.IsValueType);
}
}

private bool IsNillableValueType
Expand Down Expand Up @@ -860,6 +869,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi
var isArray = IsArray;
var propertyType = PropertyType;
var isNullableValueType = IsNullableValueType;
var isNullableReferenceType = IsNullableReferenceType;
var typeReference = TypeReference;

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

if (isNullableReferenceType && Configuration.EnableNullableReferenceAttributes)
{
member.CustomAttributes.Add(new CodeAttributeDeclaration("System.Diagnostics.CodeAnalysis.AllowNullAttribute"));
member.CustomAttributes.Add(new CodeAttributeDeclaration("System.Diagnostics.CodeAnalysis.MaybeNullAttribute"));
}

var attributes = GetAttributes(isArray).ToArray();
member.CustomAttributes.AddRange(attributes);

Expand Down

0 comments on commit a317132

Please sign in to comment.