From 0eb3900b2d964ceefb41e1ffaaa1ff8476a6e108 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Sun, 10 Nov 2019 10:19:29 +0100 Subject: [PATCH 1/4] Propagate DefaultMemberAttribute to IndexerNameAttribute --- src/PublicApiGenerator/ApiGenerator.cs | 30 +++++++--- .../AttributeNameBuilder.cs | 10 ++++ .../Indexer_properties.cs | 59 +++++++++++++++++++ 3 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 src/PublicApiGenerator/AttributeNameBuilder.cs create mode 100644 src/PublicApiGeneratorTests/Indexer_properties.cs diff --git a/src/PublicApiGenerator/ApiGenerator.cs b/src/PublicApiGenerator/ApiGenerator.cs index 178aa32..5e403b1 100644 --- a/src/PublicApiGenerator/ApiGenerator.cs +++ b/src/PublicApiGenerator/ApiGenerator.cs @@ -180,6 +180,7 @@ static bool IsDotNetTypeMember(IMemberDefinition m, string[] whitelistedNamespac } static void AddMemberToTypeDeclaration(CodeTypeDeclaration typeDeclaration, + IMemberDefinition typeDeclarationInfo, IMemberDefinition memberInfo, AttributeFilter attributeFilter) { @@ -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) { @@ -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)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)) { @@ -445,10 +446,7 @@ bool IsSpecialConstraint(GenericParameterConstraint constraint) static CodeAttributeDeclaration GenerateCodeAttributeDeclaration(Func 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))); @@ -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; @@ -675,9 +673,10 @@ 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), @@ -685,6 +684,19 @@ static void AddPropertyToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Pr HasSet = hasSet }; + // DefaultMemberAttribute on type gets propagated to IndexerNameAttribute + var defaultMemberAttribute = typeDeclarationInfo.CustomAttributes.SingleOrDefault(x => + x.AttributeType.FullName == "System.Reflection.DefaultMemberAttribute"); + var defaultMemberAttributeArgument = defaultMemberAttribute?.ConstructorArguments.SingleOrDefault(); + var value = defaultMemberAttributeArgument?.Value as string; + if (!string.IsNullOrEmpty(value) && propertyName != "Item") + { + property.Name = "Item"; + var codeAttributeDeclaration = new CodeAttributeDeclaration(AttributeNameBuilder.Get("System.Runtime.CompilerServices.IndexerNameAttribute")); + codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(value))); + property.CustomAttributes.Add(codeAttributeDeclaration); + } + // 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) diff --git a/src/PublicApiGenerator/AttributeNameBuilder.cs b/src/PublicApiGenerator/AttributeNameBuilder.cs new file mode 100644 index 0000000..16908b0 --- /dev/null +++ b/src/PublicApiGenerator/AttributeNameBuilder.cs @@ -0,0 +1,10 @@ +namespace PublicApiGenerator +{ + public static class AttributeNameBuilder + { + public static string Get(string name) + { + return name != "System.ParamArrayAttribute" ? $"{name}{CodeNormalizer.AttributeMarker}" : name; + } + } +} diff --git a/src/PublicApiGeneratorTests/Indexer_properties.cs b/src/PublicApiGeneratorTests/Indexer_properties.cs new file mode 100644 index 0000000..42b07a0 --- /dev/null +++ b/src/PublicApiGeneratorTests/Indexer_properties.cs @@ -0,0 +1,59 @@ +using System.Runtime.CompilerServices; +using PublicApiGeneratorTests.Examples; +using Xunit; + +namespace PublicApiGeneratorTests +{ + public class Indexer_properties : ApiGeneratorTestsBase + { + [Fact] + public void Should_output_indexer() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithIndexer + { + public ClassWithIndexer() { } + public int this[int x] { get; } + } +}"); + } + + [Fact] + public void Should_output_named_indexer() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithNamedIndexer + { + public ClassWithNamedIndexer() { } + [System.Runtime.CompilerServices.IndexerName(""Bar"")] + public int this[int x] { get; } + } +}"); + } + } + + + // 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; + } + } + // ReSharper restore ValueParameterNotUsed + // ReSharper restore UnusedMember.Global + // ReSharper restore ClassNeverInstantiated.Global +} From 17354035237ca548729e6f62ec45fd11a78dd007 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Mon, 11 Nov 2019 08:42:47 +0100 Subject: [PATCH 2/4] Invert condition --- src/PublicApiGenerator/AttributeNameBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PublicApiGenerator/AttributeNameBuilder.cs b/src/PublicApiGenerator/AttributeNameBuilder.cs index 16908b0..ede2d41 100644 --- a/src/PublicApiGenerator/AttributeNameBuilder.cs +++ b/src/PublicApiGenerator/AttributeNameBuilder.cs @@ -4,7 +4,8 @@ public static class AttributeNameBuilder { public static string Get(string name) { - return name != "System.ParamArrayAttribute" ? $"{name}{CodeNormalizer.AttributeMarker}" : name; + // ParamArrayAttribute cannot be augment with the attribute marker, it would trip up CodeDom + return name == "System.ParamArrayAttribute" ? name : $"{name}{CodeNormalizer.AttributeMarker}"; } } } From d04025d2a00024bb9fa814168a2512a84a7f7b22 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Mon, 11 Nov 2019 08:43:02 +0100 Subject: [PATCH 3/4] Get, set test --- .../Indexer_properties.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/PublicApiGeneratorTests/Indexer_properties.cs b/src/PublicApiGeneratorTests/Indexer_properties.cs index 42b07a0..50a78a3 100644 --- a/src/PublicApiGeneratorTests/Indexer_properties.cs +++ b/src/PublicApiGeneratorTests/Indexer_properties.cs @@ -32,6 +32,21 @@ public class ClassWithNamedIndexer [System.Runtime.CompilerServices.IndexerName(""Bar"")] public int this[int x] { get; } } +}"); + } + + [Fact] + public void Should_output_named_indexer_with_getset() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithNamedIndexerGetSet + { + public ClassWithNamedIndexerGetSet() { } + [System.Runtime.CompilerServices.IndexerName(""Bar"")] + public int this[int x] { get; set; } + } }"); } } @@ -52,6 +67,18 @@ 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 From 61573a50fd1a6210b64d45bbe9f6cafc2d0d44d9 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Mon, 11 Nov 2019 08:43:19 +0100 Subject: [PATCH 4/4] Bit more fancy panty code --- src/PublicApiGenerator/ApiGenerator.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/PublicApiGenerator/ApiGenerator.cs b/src/PublicApiGenerator/ApiGenerator.cs index 5e403b1..1594d68 100644 --- a/src/PublicApiGenerator/ApiGenerator.cs +++ b/src/PublicApiGenerator/ApiGenerator.cs @@ -685,16 +685,18 @@ static void AddPropertyToTypeDeclaration(CodeTypeDeclaration typeDeclaration, IM }; // DefaultMemberAttribute on type gets propagated to IndexerNameAttribute - var defaultMemberAttribute = typeDeclarationInfo.CustomAttributes.SingleOrDefault(x => - x.AttributeType.FullName == "System.Reflection.DefaultMemberAttribute"); - var defaultMemberAttributeArgument = defaultMemberAttribute?.ConstructorArguments.SingleOrDefault(); - var value = defaultMemberAttributeArgument?.Value as string; - if (!string.IsNullOrEmpty(value) && propertyName != "Item") + var defaultMemberAttributeValue = typeDeclarationInfo.CustomAttributes.SingleOrDefault(x => + x.AttributeType.FullName == "System.Reflection.DefaultMemberAttribute") + ?.ConstructorArguments.Select(x => x.Value).OfType().SingleOrDefault(); + if (!string.IsNullOrEmpty(defaultMemberAttributeValue) && propertyName != "Item") { property.Name = "Item"; - var codeAttributeDeclaration = new CodeAttributeDeclaration(AttributeNameBuilder.Get("System.Runtime.CompilerServices.IndexerNameAttribute")); - codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(value))); - property.CustomAttributes.Add(codeAttributeDeclaration); + 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