using Mono.Documentation; using Mono.Documentation.Updater.Formatters; using NUnit.Framework; using System; using System.Linq; namespace mdoc.Test { public class NullableReferenceTypesTests : BasicFormatterTests { private const string NullableReferenceTypesAssemblyPath = "../../../../external/Test/mdoc.Test.NullableReferenceTypes.dll"; private CSharpMemberFormatter csharpMemberFormatter = new CSharpMemberFormatter(); protected override CSharpMemberFormatter formatter => csharpMemberFormatter; [TestCase("dynamic", "DynamicType")] [TestCase("dynamic?", "NullableDynamicType")] [TestCase("DayOfWeek", "Enumeration")] [TestCase("DayOfWeek?", "NullableEnumeration")] [TestCase("int", "ValueType")] [TestCase("int?", "NullableValueType")] [TestCase("int[]", "ArrayOfValueType")] [TestCase("int?[]", "ArrayOfValueTypeNullable")] [TestCase("int[]?", "NullableArrayOfValueType")] [TestCase("int?[]?", "NullableArrayOfNullableValueType")] [TestCase("int[][]", "DimensionalArrayOfValueType")] [TestCase("int?[][]", "DimensionalArrayOfNullableValueType")] [TestCase("int?[]?[]", "DimensionalArrayOfNullableValueTypeOfNullableRow")] [TestCase("int?[]?[]?", "NullableDimensionalArrayOfNullableValueTypeOfNullableRow")] [TestCase("int?[][]?", "NullableDimensionalArrayOfNullableValueType")] [TestCase("int[][]?", "NullableDimensionalArrayOfValueType")] [TestCase("int[][]?[][]?", "NullableFourDimensionalArrayOfValueTypeOfMiddleNullableArray")] [TestCase("int?[][]?[][]", "FourDimensionalArrayOfNullableValueTypeOfMiddleNullableArray")] [TestCase("Tuple", "TupleOfValueType")] [TestCase("Tuple?", "NullableTupleOfValueType")] [TestCase("Tuple", "TupleOfNullableValueType")] [TestCase("Tuple?", "NullableTupleOfNullableValueType")] [TestCase("(int,int)", "ValueTupleOfValueType")] [TestCase("(int,int)?", "NullableValueTupleOfValueType")] [TestCase("(int?,int?)", "ValueTupleOfNullableValueType")] [TestCase("(int?,int?)?", "NullableValueTupleOfNullableValueType")] [TestCase("ICollection", "InterfaceOfValueType")] [TestCase("ICollection?", "NullableInterfaceOfValueType")] [TestCase("ICollection?", "NullableInterfaceOfNullableValueType")] [TestCase("Action", "ActionOfValueType")] [TestCase("Action", "ActionOfNullableValueType")] [TestCase("Action?", "NullableActionOfValueType")] [TestCase("Action?", "NullableActionOfNullableValueType")] [TestCase("Dictionary", "DictionaryOfValueType")] [TestCase("Dictionary?", "NullableDictionaryOfValueType")] [TestCase("Dictionary", "DictionaryOfNullableValueType")] [TestCase("Dictionary?", "NullableDictionaryOfNullableValueType")] [TestCase("Dictionary", "DictionaryOfNullableValueTypeValue")] [TestCase("Dictionary?", "NullableDictionaryOfNullableValueTypeValue")] [TestCase("Dictionary?", "NullableDictionaryOfNullableValueTypeKey")] [TestCase("Dictionary>", "DictionaryOfValueTypeKeyAndDictionaryOfValueTypeValue")] [TestCase("Dictionary>?", "NullableDictionaryOfValueTypeKeyAndDictionaryOfValueTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableValueTypeKeyAndNullableDictionaryOfValueTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableValueTypeKeyAndNullableDictionaryOfNullableValueTypeValue")] [TestCase("Dictionary>", "DictionaryOfValueTypeKeyAndTupleOfValueTypeValue")] [TestCase("Dictionary>?", "NullableDictionaryOfValueTypeKeyAndTupleOfValueTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableValueTypeKeyAndNullableTupleOfValueTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableValueTypeKeyAndNullableTupleOfNullableValueTypeValue")] [TestCase("Dictionary,Dictionary>", "DictionaryOfDictionaryOfValueType")] [TestCase("Dictionary?,Dictionary?>?", "NullableDictionaryOfNullableDictionaryOfValueType")] [TestCase("Dictionary?,Dictionary?>?", "NullableDictionaryOfNullableDictionaryOfNullableValueType")] [TestCase("string", "ReferenceType")] [TestCase("string?", "NullableReferenceType")] [TestCase("string[]", "ArrayOfReferenceType")] [TestCase("string?[]", "ArrayOfNullableReferenceType")] [TestCase("string[]?", "NullableArrayOfReferenceType")] [TestCase("string?[]?", "NullableArrayOfNullableReferenceType")] [TestCase("string[][]", "DimensionalArrayOfReferenceType")] [TestCase("string?[][]", "DimensionalArrayOfNullableReferenceType")] [TestCase("string?[]?[]", "DimensionalArrayOfNullableReferenceTypeOfNullableRow")] [TestCase("string?[]?[]?", "NullableDimensionalArrayOfNullableReferenceTypeOfNullableRow")] [TestCase("string?[][]?", "NullableDimensionalArrayOfNullableReferenceType")] [TestCase("string[][]?", "NullableDimensionalArrayOfReferenceType")] [TestCase("string[][]?[][]?", "NullableFourDimensionalArrayOfReferenceTypeOfMiddleNullableArray")] [TestCase("string?[][]?[][]", "FourDimensionalArrayOfNullableReferenceTypeOfMiddleNullableArray")] [TestCase("Tuple", "TupleOfReferenceType")] [TestCase("Tuple?", "NullableTupleOfReferenceType")] [TestCase("Tuple", "TupleOfNullableReferenceType")] [TestCase("Tuple?", "NullableTupleOfNullableReferenceType")] [TestCase("(string,string)", "ValueTupleOfReferenceType")] [TestCase("(string,string)?", "NullableValueTupleOfReferenceType")] [TestCase("(string?,string?)", "ValueTupleOfNullableReferenceType")] [TestCase("(string?,string?)?", "NullableValueTupleOfNullableReferenceType")] [TestCase("ICollection", "InterfaceOfReferenceType")] [TestCase("ICollection?", "NullableInterfaceOfReferenceType")] [TestCase("ICollection?", "NullableInterfaceOfNullableReferenceType")] [TestCase("ICollection", "InterfaceOfDynamicType")] [TestCase("ICollection?", "NullableInterfaceOfDynamicType")] [TestCase("ICollection?", "NullableInterfaceOfNullableDynamicType")] [TestCase("Action", "ActionOfReferenceType")] [TestCase("Action", "ActionOfNullableReferenceType")] [TestCase("Action?", "NullableActionOfReferenceType")] [TestCase("Action?", "NullableActionOfNullableReferenceType")] [TestCase("Dictionary", "DictionaryOfReferenceType")] [TestCase("Dictionary?", "NullableDictionaryOfReferenceType")] [TestCase("Dictionary", "DictionaryOfNullableReferenceType")] [TestCase("Dictionary?", "NullableDictionaryOfNullableReferenceType")] [TestCase("Dictionary", "DictionaryOfNullableReferenceTypeValue")] [TestCase("Dictionary?", "NullableDictionaryOfNullableReferenceTypeValue")] [TestCase("Dictionary?", "NullableDictionaryOfNullableReferenceTypeKey")] [TestCase("Dictionary,Dictionary>", "DictionaryOfDictionaryOfReferenceType")] [TestCase("Dictionary?,Dictionary?>?", "NullableDictionaryOfNullableDictionaryOfReferenceType")] [TestCase("Dictionary?,Dictionary?>?", "NullableDictionaryOfNullableDictionaryOfNullableReferenceType")] [TestCase("Dictionary>", "DictionaryOfReferenceTypeKeyAndDictionaryOfReferenceTypeValue")] [TestCase("Dictionary>?", "NullableDictionaryOfReferenceTypeKeyAndDictionaryOfReferenceTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableReferenceTypeKeyAndNullableDictionaryOfReferenceTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableReferenceTypeKeyAndNullableDictionaryOfNullableReferenceTypeValue")] [TestCase("Dictionary>", "DictionaryOfReferenceTypeKeyAndTupleOfReferenceTypeValue")] [TestCase("Dictionary>?", "NullableDictionaryOfReferenceTypeKeyAndTupleOfReferenceTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableReferenceTypeKeyAndNullableTupleOfReferenceTypeValue")] [TestCase("Dictionary?>?", "NullableDictionaryOfNullableReferenceTypeKeyAndNullableTupleOfNullableReferenceTypeValue")] public void TypeName(string returnType, string methodName) { // Test single parameter and return type for method, extension method, property, field, event, delegate. // They have the same process logic that we just test once the type of them. TestMethodSignature(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.CommonType", methodName, $"public {returnType} {methodName} ();"); } [TestCase("ParamsArrayOfNullableValueType", "int i, params int?[] array")] [TestCase("ParamsArrayOfNullableReferenceType", "string s, params object?[] array")] [TestCase("NullableAndNonNullableValueType", "int i1, int? i2, int i3")] [TestCase("NullableAndNonNullableReferenceType", "string s1, string? s2, string s3")] [TestCase("NullableGenericValueTypeOfValueType", "ReadOnlySpan s1, ReadOnlySpan s2, ReadOnlySpan s3")] [TestCase("NullableGenericValueTypeOfReferenceType", "ReadOnlySpan s1, ReadOnlySpan s2, ReadOnlySpan s3")] [TestCase("NullableAndNonNullableInterfaceOfValueType", "ICollection collection1, ICollection? collection2, ICollection collection3")] [TestCase("NullableAndNonNullableInterfaceOfReferenceType", "ICollection collection1, ICollection? collection2, ICollection collection3")] [TestCase("NonNullableValueTypeWithOutModifier", "string? value, out bool result")] [TestCase("NonNullableValueTypeWithRefModifier", "string? value, ref bool result")] public void MethodParameter(string methodName, string methodParameter) { TestMethodSignature(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.MethodParameter", methodName, $"public void {methodName} ({methodParameter});"); } [TestCase("GenericType", " ({0})", "T t")] [TestCase("GenericReferenceType", " ({0}) where T : class", "T t")] [TestCase("GenericNullableReferenceType", " ({0}) where T : class", "T? t")] [TestCase("ActionOfGenericNullableReferenceType", " ({0}) where T : class", "Action t")] [TestCase("NullableActionOfGenericNullableReferenceType", " ({0}) where T : class", "Action? t")] [TestCase("FuncGenericNullableReferenceType", " ({0}) where T : class", "Func t")] [TestCase("NullableFuncGenericNullableReferenceType", " ({0}) where T : class", "Func? t")] [TestCase("GenericNonNullableAndNullableReferenceType", " ({0}) where T2 : class", "T1 t1, T2? t2")] [TestCase("GenericValueType", " ({0}) where T : struct", "T t")] [TestCase("GenericNullableValueType", " ({0}) where T : struct", "T? t")] [TestCase("ActionOfGenericNullableValueType", " ({0}) where T : struct", "Action action")] [TestCase("NullableActionOfGenericNullableValueType", " ({0}) where T : struct", "Action? action")] [TestCase("FuncGenericNullableValueType", " ({0}) where T : struct", "Func func")] [TestCase("NullableFuncGenericNullableValueType", " ({0}) where T : struct", "Func? func")] [TestCase("GenericNonNullableAndNullableValueType", " ({0}) where T2 : struct", "T1 t1, T2? t2")] public void GenericMethodParameter(string methodName, string otherPartOfMethodSignature, string genericParameter) { TestMethodSignature(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.GenericMethodParameter", methodName, $"public void {methodName}{string.Format(otherPartOfMethodSignature, genericParameter)};"); } [TestCase("T", "GenericType", " ()")] [TestCase("T", "GenericReferenceType", " () where T : class")] [TestCase("T?", "GenericNullableReferenceType", " () where T : class")] [TestCase("T", "GenericValueType", " () where T : struct")] [TestCase("T?", "GenericNullableValueType", " () where T : struct")] public void GenericMethodReturnType(string returnType, string methodName, string otherPartOfMethodSignature) { TestMethodSignature(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.GenericMethodReturnType", methodName, $"public {returnType} {methodName}{otherPartOfMethodSignature};"); } [TestCase("T", "GenericType")] [TestCase("TClass", "GenericReferenceType")] [TestCase("TClass?", "GenericNullableReferenceType")] [TestCase("TStruct", "GenericValueType")] [TestCase("TStruct?", "GenericNullableValueType")] public void GenericPropertyReturnType(string returnType, string propertyName) { TestPropertySignature(NullableReferenceTypesAssemblyPath, $"mdoc.Test.NullableReferenceTypes.{GetGenericTypeILName("GenericPropertyReturnType")}", propertyName, $"public {returnType} {propertyName} {{ get; }}"); } [TestCase("EventHandler", "EventHandler")] [TestCase("EventHandler?", "NullableEventHandler")] [TestCase("EventHandler", "GenericEventHandler")] [TestCase("EventHandler", "GenericEventHandlerOfNullableEventArgs")] [TestCase("EventHandler?", "NullableGenericEventHandler")] [TestCase("EventHandler?", "NullableGenericEventHandlerOfNullableEventArgs")] public void EventDelegateType(string delegateType, string eventName) { TestEventSignature(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.Event", eventName, $"public event {delegateType} {eventName};"); } [TestCase("Handler", "object sender, EventArgs args")] [TestCase("NullableSender", "object? sender, EventArgs args")] [TestCase("NullableSenderAndEventArgs", "object? sender, EventArgs? args")] public void DelegateTypeParameter(string delegateName, string delegateParameter) { TestTypeSignature(NullableReferenceTypesAssemblyPath, $"mdoc.Test.NullableReferenceTypes.Delegate.{delegateName}", $"public delegate void {delegateName}({delegateParameter});"); } [TestCase("GenericHandler", "object sender, TEventArgs args", "{0}")] [TestCase("NullableSender", "object? sender, TEventArgs args", "{0}")] [TestCase("NullableSenderAndEventArgs", "object? sender, TEventArgs? args", "{0} where TEventArgs : class")] [TestCase("ActionHandler", "TClass t1, TStruct t2", "{0} where TClass : class where TStruct : struct")] [TestCase("NullableActionHandler", "TClass? t1, TStruct? t2", "{0} where TClass : class where TStruct : struct")] public void GenericDelegateTypeParameter(string delegateName, string delegateParameter, string otherPartOfMethodSignature) { TestTypeSignature(NullableReferenceTypesAssemblyPath, $"mdoc.Test.NullableReferenceTypes.Delegate.{GetGenericTypeILName(delegateName)}", $"public delegate void {delegateName}{string.Format(otherPartOfMethodSignature, $"({delegateParameter})")};"); } [TestCase("TReturn", "FuncHandler", "()")] [TestCase("TReturn?", "NullableReferenceType", "() where TReturn : class")] [TestCase("TReturn?", "NullableValueType", "() where TReturn : struct")] public void GenericDelegateTypeReturnType(string returnType, string delegateName, string otherPartOfMethodSignature) { TestTypeSignature(NullableReferenceTypesAssemblyPath, $"mdoc.Test.NullableReferenceTypes.Delegate.{GetGenericTypeILName(delegateName)}", $"public delegate {returnType} {delegateName}{otherPartOfMethodSignature};"); } [TestCase("NonNullableAndNullableValueType", "int i1, int? i2, int i3")] [TestCase("NonNullableAndNullableReferenceType", "string s1, string? s2, string s3")] [TestCase("InterfaceOfValueType", "ICollection collection1, ICollection? collection2, ICollection collection3")] [TestCase("InterfaceOfReferenceType", "ICollection collection1, ICollection? collection2, ICollection collection3")] public void ConstructorParameter(string typeName, string constructorParameter) { TestMethodSignature(NullableReferenceTypesAssemblyPath, $"mdoc.Test.NullableReferenceTypes.Constructor.{typeName}", ".ctor", $"public {typeName} ({constructorParameter});"); } [TestCase("GenericFieldType", "T", "GenericType")] [TestCase("GenericFieldTypeOfValueType", "T", "GenericType")] [TestCase("GenericFieldTypeOfValueType", "T?", "NullableGenericType")] [TestCase("GenericFieldTypeOfValueType", "ICollection", "InterfaceOfGenericType")] [TestCase("GenericFieldTypeOfValueType", "ICollection", "InterfaceOfNullableGenericType")] [TestCase("GenericFieldTypeOfValueType", "ICollection?", "NullableInterfaceOfGenericType")] [TestCase("GenericFieldTypeOfValueType", "ICollection?", "NullableInterfaceOfNullableGenericType")] [TestCase("GenericFieldTypeOfValueType", "Dictionary,string>", "DictionaryOfDictionary")] [TestCase("GenericFieldTypeOfValueType", "Dictionary,string>", "DictionaryOfDictionaryOfNullableGenericTypeKey")] [TestCase("GenericFieldTypeOfValueType", "Dictionary,string>?", "NullableDictionaryOfDictionaryOfNullableGenericTypeKey")] [TestCase("GenericFieldTypeOfReferenceType", "T", "GenericType")] [TestCase("GenericFieldTypeOfReferenceType", "T?", "NullableGenericType")] [TestCase("GenericFieldTypeOfReferenceType", "ICollection", "InterfaceOfGenericType")] [TestCase("GenericFieldTypeOfReferenceType", "ICollection", "InterfaceOfNullableGenericType")] [TestCase("GenericFieldTypeOfReferenceType", "ICollection?", "NullableInterfaceOfGenericType")] [TestCase("GenericFieldTypeOfReferenceType", "ICollection?", "NullableInterfaceOfNullableGenericType")] [TestCase("GenericFieldTypeOfReferenceType", "Dictionary,string>", "DictionaryOfDictionary")] [TestCase("GenericFieldTypeOfReferenceType", "Dictionary,string>", "DictionaryOfDictionaryOfNullableGenericTypeKey")] [TestCase("GenericFieldTypeOfReferenceType", "Dictionary,string>?", "NullableDictionaryOfDictionaryOfNullableGenericTypeKey")] public void GenericFieldReturnType(string typeName, string returnType, string fieldName) { TestFieldSignature(NullableReferenceTypesAssemblyPath, $"mdoc.Test.NullableReferenceTypes.{GetGenericTypeILName(typeName)}", fieldName, $"public {returnType} {fieldName};"); } [TestCase("NullableAndNonNullableValueType", "this int? type, int? i1, int i2, int? i3")] [TestCase("NullableAndNonNullableNullableReferenceType", "this string? type, string? s1, string s2, string? s3")] [TestCase("NullableAndNonNullableNullableReferenceTypeAndValueType", "this string? type, string? s1, int? i1, int i2, string s2, string? s3")] public void ExtensionMethodParameter(string methodName, string methodParameter) { TestMethodSignature(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.ExtensionMethod", methodName, $"public static void {methodName} ({methodParameter});"); } [TestCase("Student", "op_Addition", "Student operator +", "Student s1, Student s2")] [TestCase("Student", "op_Subtraction", "Student? operator -", "Student? s1, Student? s2")] [TestCase("Student", "op_Multiply", "Student operator *", "Student s1, Student? s2")] [TestCase("Student", "op_Division", "Student operator /", "Student? s1, Student s2")] [TestCase("Student", "op_Implicit", "implicit operator ExamScore", "Student? s")] [TestCase("Student", "op_Explicit", "explicit operator Student?", "ExamScore? s")] [TestCase("ExamScore", "op_Addition", "ExamScore operator +", "ExamScore s1, ExamScore s2")] [TestCase("ExamScore", "op_Subtraction", "ExamScore? operator -", "ExamScore? s1, ExamScore? s2")] [TestCase("ExamScore", "op_Multiply", "ExamScore operator *", "ExamScore s1, ExamScore? s2")] [TestCase("ExamScore", "op_Division", "ExamScore operator /", "ExamScore? s1, ExamScore s2")] [TestCase("ExamScore", "op_Implicit", "implicit operator ExamScore", "Student? s")] [TestCase("ExamScore", "op_Explicit", "explicit operator Student?", "ExamScore? s")] public void OperatorOverloading(string typeName, string methodName, string operatorName, string methodParameter) { TestMethodSignature(NullableReferenceTypesAssemblyPath, $"mdoc.Test.NullableReferenceTypes.OperatorOverloading.{typeName}", methodName, $"public static {operatorName} ({methodParameter});"); } private string GetGenericTypeILName(string genericTypeName) { var startParameterIndex = genericTypeName.IndexOf('<'); var endParameterIndex = genericTypeName.IndexOf('>'); if (startParameterIndex != -1 && endParameterIndex != -1 && endParameterIndex == genericTypeName.Length - 1) { var parameterList = genericTypeName.Substring(startParameterIndex + 1, endParameterIndex - startParameterIndex - 1); var parameterCount = parameterList.Split(',').Count(); var genericTypeNameWithoutParameter = genericTypeName.Substring(0, startParameterIndex); return $"{genericTypeNameWithoutParameter}`{parameterCount}"; } throw new ArgumentException("The generic type name is not a valid value.", nameof(genericTypeName)); } } }