diff --git a/src/PublicApiGenerator/ApiGenerator.cs b/src/PublicApiGenerator/ApiGenerator.cs index 92e39df..8f9cbcb 100644 --- a/src/PublicApiGenerator/ApiGenerator.cs +++ b/src/PublicApiGenerator/ApiGenerator.cs @@ -226,7 +226,7 @@ static CodeTypeDeclaration CreateTypeDeclaration(TypeDefinition publicType, stri if (declaration.IsInterface && publicType.BaseType != null) throw new NotImplementedException("Base types for interfaces needs testing"); - PopulateGenericParameters(publicType, declaration.TypeParameters); + PopulateGenericParameters(publicType, declaration.TypeParameters, excludeAttributes); if (publicType.BaseType != null && ShouldOutputBaseType(publicType)) { @@ -280,7 +280,7 @@ static CodeTypeDeclaration CreateDelegateDeclaration(TypeDefinition publicType, // CodeDOM. No support. Return type attributes. PopulateCustomAttributes(invokeMethod.MethodReturnType, declaration.CustomAttributes, type => ModifyCodeTypeReference(type, "return:"), excludeAttributes); - PopulateGenericParameters(publicType, declaration.TypeParameters); + PopulateGenericParameters(publicType, declaration.TypeParameters, excludeAttributes); PopulateMethodParameters(invokeMethod, declaration.Parameters, excludeAttributes); // Of course, CodeDOM doesn't support generic type parameters for delegates. Of course. @@ -299,26 +299,32 @@ static bool ShouldOutputBaseType(TypeDefinition publicType) return publicType.BaseType.FullName != "System.Object" && publicType.BaseType.FullName != "System.ValueType"; } - static void PopulateGenericParameters(IGenericParameterProvider publicType, CodeTypeParameterCollection parameters) + static void PopulateGenericParameters(IGenericParameterProvider publicType, CodeTypeParameterCollection parameters, HashSet excludeAttributes) { foreach (var parameter in publicType.GenericParameters) { - if (parameter.HasCustomAttributes) - throw new NotImplementedException("Attributes on type parameters is not supported. And weird"); - // A little hacky. Means we get "in" and "out" prefixed on any constraints, but it's either that - // or add it as a custom attribute, which looks even weirder + // or add it as a custom attribute var name = parameter.Name; if (parameter.IsCovariant) name = "out " + name; if (parameter.IsContravariant) name = "in " + name; + var attributeCollection = new CodeAttributeDeclarationCollection(); + if (parameter.HasCustomAttributes) + { + PopulateCustomAttributes(parameter, attributeCollection, excludeAttributes); + } + var typeParameter = new CodeTypeParameter(name) { HasConstructorConstraint = parameter.HasDefaultConstructorConstraint && !parameter.HasNotNullableValueTypeConstraint }; + + typeParameter.CustomAttributes.AddRange(attributeCollection.OfType().ToArray()); + if (parameter.HasNotNullableValueTypeConstraint) typeParameter.Constraints.Add(" struct"); // Extra space is a hack! if (parameter.HasReferenceTypeConstraint) @@ -428,7 +434,7 @@ static string ConvertAttributeToCode(Func static bool ShouldIncludeAttribute(CustomAttribute attribute, HashSet excludeAttributes) { var attributeTypeDefinition = attribute.AttributeType.Resolve(); - return attributeTypeDefinition != null && !excludeAttributes.Contains(attribute.AttributeType.FullName) && attributeTypeDefinition.IsPublic; + return attributeTypeDefinition != null && !excludeAttributes.Contains(attribute.AttributeType.FullName) && (attributeTypeDefinition.IsPublic || attributeTypeDefinition.FullName == "System.Runtime.CompilerServices.NullableAttribute"); } static CodeExpression CreateInitialiserExpression(CustomAttributeArgument attributeArgument) @@ -558,7 +564,7 @@ static void AddMethodToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Meth ReturnType = returnType, }; PopulateCustomAttributes(member.MethodReturnType, method.ReturnTypeCustomAttributes, excludeAttributes); - PopulateGenericParameters(member, method.TypeParameters); + PopulateGenericParameters(member, method.TypeParameters, excludeAttributes); PopulateMethodParameters(member, method.Parameters, excludeAttributes, IsExtensionMethod(member)); typeDeclaration.Members.Add(method); diff --git a/src/PublicApiGeneratorTests/Class_generics.cs b/src/PublicApiGeneratorTests/Class_generics.cs index a002d9d..6f8df1a 100644 --- a/src/PublicApiGeneratorTests/Class_generics.cs +++ b/src/PublicApiGeneratorTests/Class_generics.cs @@ -19,6 +19,19 @@ public class ClassWithGenericType }"); } + [Fact] + public void Should_output_generic_type_parameter_attribute() + { + AssertPublicApi(typeof(ClassWithGenericTypeAttribute<>), +@"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithGenericTypeAttribute<[PublicApiGeneratorTests.Examples.MyTypeAttribute()] T> + { + public ClassWithGenericTypeAttribute() { } + } +}"); + } + [Fact] public void Should_output_multiple_generic_type_parameters() { @@ -141,6 +154,15 @@ public class ClassWithGenericType { } + public class ClassWithGenericTypeAttribute<[MyType]T> + { + } + + [AttributeUsage(AttributeTargets.GenericParameter)] + public sealed class MyTypeAttribute : Attribute + { + } + public class ClassWithMultipleGenericTypes { }