Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix type attribute handling #65

Merged
merged 2 commits into from Jun 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 15 additions & 9 deletions src/PublicApiGenerator/ApiGenerator.cs
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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.
Expand All @@ -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<string> 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<CodeAttributeDeclaration>().ToArray());

if (parameter.HasNotNullableValueTypeConstraint)
typeParameter.Constraints.Add(" struct"); // Extra space is a hack!
if (parameter.HasReferenceTypeConstraint)
Expand Down Expand Up @@ -428,7 +434,7 @@ static string ConvertAttributeToCode(Func<CodeTypeReference, CodeTypeReference>
static bool ShouldIncludeAttribute(CustomAttribute attribute, HashSet<string> 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)
Expand Down Expand Up @@ -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);
Expand Down
22 changes: 22 additions & 0 deletions src/PublicApiGeneratorTests/Class_generics.cs
Expand Up @@ -19,6 +19,19 @@ public class ClassWithGenericType<T>
}");
}

[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()
{
Expand Down Expand Up @@ -141,6 +154,15 @@ public class ClassWithGenericType<T>
{
}

public class ClassWithGenericTypeAttribute<[MyType]T>
{
}

[AttributeUsage(AttributeTargets.GenericParameter)]
public sealed class MyTypeAttribute : Attribute
{
}

public class ClassWithMultipleGenericTypes<T1, T2>
{
}
Expand Down