Skip to content

Commit

Permalink
Propagate DefaultMemberAttribute to IndexerNameAttribute (#136)
Browse files Browse the repository at this point in the history
* Propagate DefaultMemberAttribute to IndexerNameAttribute

* Invert condition

* Get, set test

* Bit more fancy panty code
  • Loading branch information
danielmarbach committed Nov 11, 2019
1 parent 987f9ce commit 712b9ed
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 9 deletions.
32 changes: 23 additions & 9 deletions src/PublicApiGenerator/ApiGenerator.cs
Expand Up @@ -180,6 +180,7 @@ static bool IsDotNetTypeMember(IMemberDefinition m, string[] whitelistedNamespac
}

static void AddMemberToTypeDeclaration(CodeTypeDeclaration typeDeclaration,
IMemberDefinition typeDeclarationInfo,
IMemberDefinition memberInfo,
AttributeFilter attributeFilter)
{
Expand All @@ -194,7 +195,7 @@ static bool IsDotNetTypeMember(IMemberDefinition m, string[] whitelistedNamespac
}
else if (memberInfo is PropertyDefinition propertyDefinition)
{
AddPropertyToTypeDeclaration(typeDeclaration, propertyDefinition, attributeFilter);
AddPropertyToTypeDeclaration(typeDeclaration, typeDeclarationInfo, propertyDefinition, attributeFilter);
}
else if (memberInfo is EventDefinition eventDefinition)
{
Expand Down Expand Up @@ -293,14 +294,14 @@ static CodeTypeDeclaration CreateTypeDeclaration(TypeDefinition publicType, stri
declaration.BaseTypes.Add(@interface.InterfaceType.CreateCodeTypeReference(@interface));

foreach (var memberInfo in publicType.GetMembers().Where(memberDefinition => ShouldIncludeMember(memberDefinition, whitelistedNamespacePrefixes)).OrderBy(m => m.Name, StringComparer.Ordinal))
AddMemberToTypeDeclaration(declaration, memberInfo, attributeFilter);
AddMemberToTypeDeclaration(declaration, publicType, memberInfo, attributeFilter);

// Fields should be in defined order for an enum
var fields = !publicType.IsEnum
? publicType.Fields.OrderBy(f => f.Name, StringComparer.Ordinal)
: (IEnumerable<FieldDefinition>)publicType.Fields;
foreach (var field in fields)
AddMemberToTypeDeclaration(declaration, field, attributeFilter);
AddMemberToTypeDeclaration(declaration, publicType, field, attributeFilter);

foreach (var nestedType in publicType.NestedTypes.Where(ShouldIncludeType).OrderBy(t => t.FullName, StringComparer.Ordinal))
{
Expand Down Expand Up @@ -445,10 +446,7 @@ bool IsSpecialConstraint(GenericParameterConstraint constraint)
static CodeAttributeDeclaration GenerateCodeAttributeDeclaration(Func<CodeTypeReference, CodeTypeReference> codeTypeModifier, CustomAttribute customAttribute)
{
var attribute = new CodeAttributeDeclaration(codeTypeModifier(customAttribute.AttributeType.CreateCodeTypeReference(mode: NullableMode.Disable)));
if (attribute.Name != "System.ParamArrayAttribute")
{
attribute.Name = $"{attribute.Name}{CodeNormalizer.AttributeMarker}";
}
attribute.Name = AttributeNameBuilder.Get(attribute.Name);
foreach (var arg in customAttribute.ConstructorArguments)
{
attribute.Arguments.Add(new CodeAttributeArgument(CreateInitialiserExpression(arg)));
Expand Down Expand Up @@ -658,7 +656,7 @@ static object FormatParameterConstant(ParameterDefinition parameter)
return parameter.ParameterType.IsValueType ? "default" : "null";
}

static void AddPropertyToTypeDeclaration(CodeTypeDeclaration typeDeclaration, PropertyDefinition member, AttributeFilter attributeFilter)
static void AddPropertyToTypeDeclaration(CodeTypeDeclaration typeDeclaration, IMemberDefinition typeDeclarationInfo, PropertyDefinition member, AttributeFilter attributeFilter)
{
var getterAttributes = member.GetMethod?.GetMethodAttributes() ?? 0;
var setterAttributes = member.SetMethod?.GetMethodAttributes() ?? 0;
Expand All @@ -675,16 +673,32 @@ static void AddPropertyToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Pr
? new CodeTypeReference(member.PropertyType.Name)
: member.PropertyType.CreateCodeTypeReference(member);

var propertyName = member.Name;
var property = new CodeMemberProperty
{
Name = member.Name,
Name = propertyName,
Type = propertyType,
Attributes = propertyAttributes,
CustomAttributes = CreateCustomAttributes(member, attributeFilter),
HasGet = hasGet,
HasSet = hasSet
};

// DefaultMemberAttribute on type gets propagated to IndexerNameAttribute
var defaultMemberAttributeValue = typeDeclarationInfo.CustomAttributes.SingleOrDefault(x =>
x.AttributeType.FullName == "System.Reflection.DefaultMemberAttribute")
?.ConstructorArguments.Select(x => x.Value).OfType<string>().SingleOrDefault();
if (!string.IsNullOrEmpty(defaultMemberAttributeValue) && propertyName != "Item")
{
property.Name = "Item";
property.CustomAttributes.Add(
new CodeAttributeDeclaration(
AttributeNameBuilder.Get("System.Runtime.CompilerServices.IndexerNameAttribute"))
{
Arguments = {new CodeAttributeArgument(new CodePrimitiveExpression(defaultMemberAttributeValue))}
});
}

// Here's a nice hack, because hey, guess what, the CodeDOM doesn't support
// attributes on getters or setters
if (member.GetMethod != null && member.GetMethod.HasCustomAttributes)
Expand Down
11 changes: 11 additions & 0 deletions src/PublicApiGenerator/AttributeNameBuilder.cs
@@ -0,0 +1,11 @@
namespace PublicApiGenerator
{
public static class AttributeNameBuilder
{
public static string Get(string name)
{
// ParamArrayAttribute cannot be augment with the attribute marker, it would trip up CodeDom
return name == "System.ParamArrayAttribute" ? name : $"{name}{CodeNormalizer.AttributeMarker}";
}
}
}
86 changes: 86 additions & 0 deletions src/PublicApiGeneratorTests/Indexer_properties.cs
@@ -0,0 +1,86 @@
using System.Runtime.CompilerServices;
using PublicApiGeneratorTests.Examples;
using Xunit;

namespace PublicApiGeneratorTests
{
public class Indexer_properties : ApiGeneratorTestsBase
{
[Fact]
public void Should_output_indexer()
{
AssertPublicApi<ClassWithIndexer>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithIndexer
{
public ClassWithIndexer() { }
public int this[int x] { get; }
}
}");
}

[Fact]
public void Should_output_named_indexer()
{
AssertPublicApi<ClassWithNamedIndexer>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithNamedIndexer
{
public ClassWithNamedIndexer() { }
[System.Runtime.CompilerServices.IndexerName(""Bar"")]
public int this[int x] { get; }
}
}");
}

[Fact]
public void Should_output_named_indexer_with_getset()
{
AssertPublicApi<ClassWithNamedIndexerGetSet>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithNamedIndexerGetSet
{
public ClassWithNamedIndexerGetSet() { }
[System.Runtime.CompilerServices.IndexerName(""Bar"")]
public int this[int x] { get; set; }
}
}");
}
}


// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable ValueParameterNotUsed
namespace Examples
{
public class ClassWithIndexer
{
public int this[int x] => x;
}

public class ClassWithNamedIndexer
{
[IndexerName("Bar")]
public int this[int x] => x;
}

public class ClassWithNamedIndexerGetSet
{
private int y;

[IndexerName("Bar")]
public int this[int x]
{
get => y;
set => y = value;
}
}
}
// ReSharper restore ValueParameterNotUsed
// ReSharper restore UnusedMember.Global
// ReSharper restore ClassNeverInstantiated.Global
}

0 comments on commit 712b9ed

Please sign in to comment.