From c5d1888c6a890eb12367f7fb11dcbd37e1a83168 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 1 Nov 2019 23:03:16 -0400 Subject: [PATCH 1/9] Add tests showing that 'sealed override' members and event scope modifiers are not supported by CodeDOM --- .../Event_modifiers.cs | 138 ++++++++++++++++++ .../Method_modifiers.cs | 27 +++- .../Property_modifiers.cs | 34 ++++- 3 files changed, 192 insertions(+), 7 deletions(-) create mode 100644 src/PublicApiGeneratorTests/Event_modifiers.cs diff --git a/src/PublicApiGeneratorTests/Event_modifiers.cs b/src/PublicApiGeneratorTests/Event_modifiers.cs new file mode 100644 index 0000000..e0c2b71 --- /dev/null +++ b/src/PublicApiGeneratorTests/Event_modifiers.cs @@ -0,0 +1,138 @@ +using PublicApiGeneratorTests.Examples; +using System; +using Xunit; + +namespace PublicApiGeneratorTests +{ + public class Event_modifiers : ApiGeneratorTestsBase + { + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + public void Should_output_abstract_modifier() + { + AssertPublicApi( +@"namespace PublicApiGeneratorTests.Examples +{ + public abstract class ClassWithAbstractEvent + { + protected ClassWithAbstractEvent() { } + public abstract event System.EventHandler Event; + } +}"); + } + + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + public void Should_output_static_modifier() + { + AssertPublicApi( +@"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithStaticEvent + { + public ClassWithStaticEvent() { } + public static event System.EventHandler Event; + } +}"); + } + + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + public void Should_output_virtual_modifier() + { + AssertPublicApi( +@"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithVirtualEvent + { + public ClassWithVirtualEvent() { } + public virtual event System.EventHandler Event; + } +}"); + } + + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + public void Should_output_override_modifier() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithOverridingEvent : PublicApiGeneratorTests.Examples.ClassWithVirtualEvent + { + public ClassWithOverridingEvent() { } + public override event System.EventHandler Event; + } +}"); + } + + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + public void Should_output_sealed_modifier() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithSealedOverridingEvent : PublicApiGeneratorTests.Examples.ClassWithVirtualEvent + { + public ClassWithSealedOverridingEvent() { } + public sealed override event System.EventHandler Event; + } +}"); + } + + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + public void Should_output_new_modifier() + { + AssertPublicApi( +@"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithEventHiding : PublicApiGeneratorTests.Examples.ClassWithEvent + { + public ClassWithEventHiding() { } + public new event System.EventHandler Event; + } +}"); + } + } + + // ReSharper disable UnusedMember.Global + // ReSharper disable ClassNeverInstantiated.Global + // ReSharper disable ValueParameterNotUsed + namespace Examples + { + public abstract class ClassWithAbstractEvent + { + public abstract event EventHandler Event; + } + + public class ClassWithStaticEvent + { + public static event EventHandler Event; + } + + public class ClassWithVirtualEvent + { + public virtual event EventHandler Event; + } + + public class ClassWithOverridingEvent : ClassWithVirtualEvent + { + public override event EventHandler Event; + } + + public class ClassWithSealedOverridingEvent : ClassWithVirtualEvent + { + public sealed override event EventHandler Event; + } + + public class ClassWithEventHiding : ClassWithVirtualEvent + { + public new event EventHandler Event; + } + } + // ReSharper restore ValueParameterNotUsed + // ReSharper restore ClassNeverInstantiated.Global + // ReSharper restore UnusedMember.Global +} diff --git a/src/PublicApiGeneratorTests/Method_modifiers.cs b/src/PublicApiGeneratorTests/Method_modifiers.cs index dc7f436..2f2fab6 100644 --- a/src/PublicApiGeneratorTests/Method_modifiers.cs +++ b/src/PublicApiGeneratorTests/Method_modifiers.cs @@ -50,8 +50,8 @@ public class ClassWithVirtualMethod [Fact] public void Should_output_override_modifier() { - AssertPublicApi( -@"namespace PublicApiGeneratorTests.Examples + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples { public class ClassWithOverridingMethod : PublicApiGeneratorTests.Examples.ClassWithVirtualMethod { @@ -61,6 +61,21 @@ public class ClassWithOverridingMethod : PublicApiGeneratorTests.Examples.ClassW }"); } + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Sealed override members not supported by CodeDOM")] + public void Should_output_sealed_modifier() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithSealedOverridingMethod : PublicApiGeneratorTests.Examples.ClassWithVirtualMethod + { + public ClassWithSealedOverridingMethod() { } + public sealed override void DoSomething() { } + } +}"); + } + [Fact] public void Should_allow_overriding_object_methods() { @@ -153,6 +168,14 @@ public override void DoSomething() } } + public class ClassWithSealedOverridingMethod : ClassWithVirtualMethod + { + public sealed override void DoSomething() + { + base.DoSomething(); + } + } + public class ClassWithMethodOverridingObjectMethod { public override string ToString() diff --git a/src/PublicApiGeneratorTests/Property_modifiers.cs b/src/PublicApiGeneratorTests/Property_modifiers.cs index f2c89b8..582a10d 100644 --- a/src/PublicApiGeneratorTests/Property_modifiers.cs +++ b/src/PublicApiGeneratorTests/Property_modifiers.cs @@ -50,17 +50,32 @@ public class ClassWithVirtualProperty [Fact] public void Should_output_override_modifier() { - AssertPublicApi( -@"namespace PublicApiGeneratorTests.Examples + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples { - public class ClassWithOverriddenProperty : PublicApiGeneratorTests.Examples.ClassWithVirtualProperty + public class ClassWithOverridingProperty : PublicApiGeneratorTests.Examples.ClassWithVirtualProperty { - public ClassWithOverriddenProperty() { } + public ClassWithOverridingProperty() { } public override string Value { get; set; } } }"); } + [Fact(Skip = "Not supported by CodeDOM")] + [Trait("TODO", "Sealed override members not supported by CodeDOM")] + public void Should_output_sealed_modifier() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithSealedOverridingProperty : PublicApiGeneratorTests.Examples.ClassWithVirtualProperty + { + public ClassWithSealedOverridingProperty() { } + public sealed override string Value { get; set; } + } +}"); + } + [Fact] public void Should_output_new_modifier() { @@ -113,7 +128,7 @@ public virtual string Value } } - public class ClassWithOverriddenProperty : ClassWithVirtualProperty + public class ClassWithOverridingProperty : ClassWithVirtualProperty { public override string Value { @@ -122,6 +137,15 @@ public override string Value } } + public class ClassWithSealedOverridingProperty : ClassWithVirtualProperty + { + public sealed override string Value + { + get { return string.Empty; } + set { } + } + } + public class ClassWithPropertyHiding : ClassWithProperty { public new string Value From 0e841709d34871a8a889d36a52ba0d177ea0b311 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 13:54:11 +0100 Subject: [PATCH 2/9] Enable tests --- src/PublicApiGenerator/EventNameBuilder.cs | 45 +++++++++++++++++++ .../Event_modifiers.cs | 20 +++------ .../Interface_event_attributes.cs | 4 +- .../Interface_events.cs | 27 ++++++++--- .../Interface_member_order.cs | 10 ++--- 5 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 src/PublicApiGenerator/EventNameBuilder.cs diff --git a/src/PublicApiGenerator/EventNameBuilder.cs b/src/PublicApiGenerator/EventNameBuilder.cs new file mode 100644 index 0000000..e8c6dac --- /dev/null +++ b/src/PublicApiGenerator/EventNameBuilder.cs @@ -0,0 +1,45 @@ +using System; +using System.CodeDom; +using Mono.Cecil; + +namespace PublicApiGenerator +{ + public static class EventNameBuilder + { + public static string AugmentEventNameWithEventModifierMarkerTemplate(EventDefinition eventDefinition, string name, + MemberAttributes addAccessorAttributes, MemberAttributes removeAccessorAttributes) + { + if (eventDefinition.AddMethod.IsStatic && eventDefinition.RemoveMethod.IsStatic) + { + return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "static")}{name}"; + } + + var addScopeAttributes = (addAccessorAttributes & MemberAttributes.ScopeMask); + var removeScopeAttributes = (removeAccessorAttributes & MemberAttributes.ScopeMask); + if (eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual && + addScopeAttributes == MemberAttributes.Override && removeScopeAttributes == MemberAttributes.Override) + { + return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "override")}{name}"; + } + + if (eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual && + !eventDefinition.AddMethod.IsAbstract && !eventDefinition.RemoveMethod.IsAbstract) + { + return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "virtual")}{name}"; + } + + if (eventDefinition.AddMethod.IsAbstract && eventDefinition.RemoveMethod.IsAbstract && + eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual) + { + return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "abstract")}{name}"; + } + + if (eventDefinition.AddMethod.IsFinal && eventDefinition.RemoveMethod.IsFinal) + { + return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "sealed")}{name}"; + } + + return name; + } + } +} diff --git a/src/PublicApiGeneratorTests/Event_modifiers.cs b/src/PublicApiGeneratorTests/Event_modifiers.cs index e0c2b71..1077c81 100644 --- a/src/PublicApiGeneratorTests/Event_modifiers.cs +++ b/src/PublicApiGeneratorTests/Event_modifiers.cs @@ -6,8 +6,7 @@ namespace PublicApiGeneratorTests { public class Event_modifiers : ApiGeneratorTestsBase { - [Fact(Skip = "Not supported by CodeDOM")] - [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + [Fact] public void Should_output_abstract_modifier() { AssertPublicApi( @@ -21,8 +20,7 @@ public abstract class ClassWithAbstractEvent }"); } - [Fact(Skip = "Not supported by CodeDOM")] - [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + [Fact] public void Should_output_static_modifier() { AssertPublicApi( @@ -36,8 +34,7 @@ public class ClassWithStaticEvent }"); } - [Fact(Skip = "Not supported by CodeDOM")] - [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + [Fact] public void Should_output_virtual_modifier() { AssertPublicApi( @@ -51,8 +48,7 @@ public class ClassWithVirtualEvent }"); } - [Fact(Skip = "Not supported by CodeDOM")] - [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + [Fact] public void Should_output_override_modifier() { AssertPublicApi( @@ -66,8 +62,7 @@ public class ClassWithOverridingEvent : PublicApiGeneratorTests.Examples.ClassWi }"); } - [Fact(Skip = "Not supported by CodeDOM")] - [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + [Fact] public void Should_output_sealed_modifier() { AssertPublicApi( @@ -81,14 +76,13 @@ public class ClassWithSealedOverridingEvent : PublicApiGeneratorTests.Examples.C }"); } - [Fact(Skip = "Not supported by CodeDOM")] - [Trait("TODO", "Scope modifiers on events not supported by CodeDOM")] + [Fact] public void Should_output_new_modifier() { AssertPublicApi( @"namespace PublicApiGeneratorTests.Examples { - public class ClassWithEventHiding : PublicApiGeneratorTests.Examples.ClassWithEvent + public class ClassWithEventHiding : PublicApiGeneratorTests.Examples.ClassWithVirtualEvent { public ClassWithEventHiding() { } public new event System.EventHandler Event; diff --git a/src/PublicApiGeneratorTests/Interface_event_attributes.cs b/src/PublicApiGeneratorTests/Interface_event_attributes.cs index 3dfdb4f..eb24401 100644 --- a/src/PublicApiGeneratorTests/Interface_event_attributes.cs +++ b/src/PublicApiGeneratorTests/Interface_event_attributes.cs @@ -16,7 +16,7 @@ public void Should_add_attribute_to_event() public interface IInterfaceWithEventWithAttribute { [PublicApiGeneratorTests.Examples.Simple] - public event System.EventHandler OnClicked; + event System.EventHandler OnClicked; } }"); } @@ -34,7 +34,7 @@ public void Should_skip_excluded_attribute() { public interface IInterfaceWithEventWithAttribute { - public event System.EventHandler OnClicked; + event System.EventHandler OnClicked; } }", options); } diff --git a/src/PublicApiGeneratorTests/Interface_events.cs b/src/PublicApiGeneratorTests/Interface_events.cs index caa503a..ae34a42 100644 --- a/src/PublicApiGeneratorTests/Interface_events.cs +++ b/src/PublicApiGeneratorTests/Interface_events.cs @@ -9,15 +9,25 @@ public class Interface_events : ApiGeneratorTestsBase [Fact] public void Should_output_event() { - // TODO: CodeDOM outputs "public" in event declarations in interfaces - // This looks like a bug? That's what the implementation does, but it's - // not valid C#... AssertPublicApi( @"namespace PublicApiGeneratorTests.Examples { public interface ISimpleEvent { - public event System.EventHandler Event; + event System.EventHandler Event; + } +}"); + } + + [Fact] + public void Should_output_event_redeclaration() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public interface IInheritedEvent : PublicApiGeneratorTests.Examples.ISimpleEvent + { + new event System.EventHandler Event; } }"); } @@ -30,7 +40,7 @@ public void Should_output_event_with_generics() { public interface IGenericEventHandler { - public event System.EventHandler Event; + event System.EventHandler Event; } }"); } @@ -48,6 +58,11 @@ public interface IGenericEventHandler { event EventHandler Event; } + + public interface IInheritedEvent : ISimpleEvent + { + new event EventHandler Event; + } } // ReSharper restore EventNeverSubscribedTo.Global -} \ No newline at end of file +} diff --git a/src/PublicApiGeneratorTests/Interface_member_order.cs b/src/PublicApiGeneratorTests/Interface_member_order.cs index 0f452a7..77155ce 100644 --- a/src/PublicApiGeneratorTests/Interface_member_order.cs +++ b/src/PublicApiGeneratorTests/Interface_member_order.cs @@ -19,10 +19,10 @@ public interface IInterfaceMemberOrder int Property1 { get; set; } int Property2 { get; set; } int iProperty2 { get; set; } - public event System.EventHandler Event1; - public event System.EventHandler Event2; - public event System.EventHandler IEvent1; - public event System.EventHandler iEvent2; + event System.EventHandler Event1; + event System.EventHandler Event2; + event System.EventHandler IEvent1; + event System.EventHandler iEvent2; void IMethod1(); void Method1(); void Method2(); @@ -60,4 +60,4 @@ public interface IInterfaceMemberOrder // ReSharper restore ClassNeverInstantiated.Global // ReSharper restore EventNeverSubscribedTo.Global // ReSharper restore EventNeverInvoked -} \ No newline at end of file +} From d466ea69530ba7ae1daf04bce1594c9a08f4b0df Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 13:54:46 +0100 Subject: [PATCH 3/9] Replacement magic for event modifiers --- src/PublicApiGenerator/ApiGenerator.cs | 3 +- src/PublicApiGenerator/CodeNormalizer.cs | 10 +++++ src/PublicApiGenerator/EventNameBuilder.cs | 45 +++++++++++++++------- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/PublicApiGenerator/ApiGenerator.cs b/src/PublicApiGenerator/ApiGenerator.cs index 8522beb..1d76c7b 100644 --- a/src/PublicApiGenerator/ApiGenerator.cs +++ b/src/PublicApiGenerator/ApiGenerator.cs @@ -684,9 +684,10 @@ static void AddEventToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Event if (!(ShouldIncludeMember(addAccessorAttributes) || ShouldIncludeMember(removeAccessorAttributes))) return; + var @event = new CodeMemberEvent { - Name = eventDefinition.Name, + Name = EventNameBuilder.AugmentEventNameWithEventModifierMarkerTemplate(eventDefinition, eventDefinition.Name, addAccessorAttributes, removeAccessorAttributes), Attributes = CecilEx.CombineAccessorAttributes(addAccessorAttributes, removeAccessorAttributes), CustomAttributes = CreateCustomAttributes(eventDefinition, attributeFilter), Type = eventDefinition.EventType.CreateCodeTypeReference(eventDefinition) diff --git a/src/PublicApiGenerator/CodeNormalizer.cs b/src/PublicApiGenerator/CodeNormalizer.cs index 7347c08..7aea568 100644 --- a/src/PublicApiGenerator/CodeNormalizer.cs +++ b/src/PublicApiGenerator/CodeNormalizer.cs @@ -19,6 +19,8 @@ internal static class CodeNormalizer internal const string StaticMarker = "static_C91E2709_C00B-4CAB_8BBC_B2B11DC75E50 "; internal const string ReadonlyMarker = "readonly_79D3ED2A_0B60_4C3B_8432_941FE471A38B "; internal const string AttributeMarker = "_attribute_292C96C3_C42E_4C07_BEED_73F5DAA0A6DF_"; + internal const string EventModifierMarkerTemplate = "_{0}_292C96C3C42E4C07BEED73F5DAA0A6DF_"; + internal const string EventRemovePublicMarker = "removepublic"; public static string NormalizeGeneratedCode(StringWriter writer) { @@ -49,6 +51,7 @@ public static string NormalizeGeneratedCode(StringWriter writer) RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace); // SingleLine is required for multi line params arrays + gennedClass = Regex.Replace(gennedClass, @"(.*) _(.*)_292C96C3C42E4C07BEED73F5DAA0A6DF_(.*)", EventModifierMatcher, RegexOptions.IgnorePatternWhitespace); gennedClass = gennedClass.Replace("class " + StaticMarker, "static class "); gennedClass = gennedClass.Replace("struct " + ReadonlyMarker, "readonly struct "); gennedClass = gennedClass.Replace(ReadonlyMarker, string.Empty); // remove magic marker from readonly struct ctor @@ -58,6 +61,13 @@ public static string NormalizeGeneratedCode(StringWriter writer) return gennedClass; } + static string EventModifierMatcher(Match group) + { + var modifier = @group.Groups[2].Value; + var replacement = modifier == EventRemovePublicMarker ? $"event " : $"public {modifier} event "; + return group.ToString().Replace(string.Format(EventModifierMarkerTemplate, modifier), string.Empty).Replace("public event ", replacement); + } + static string RemoveUnnecessaryWhiteSpace(string publicApi) { return string.Join(Environment.NewLine, publicApi.Split(new[] diff --git a/src/PublicApiGenerator/EventNameBuilder.cs b/src/PublicApiGenerator/EventNameBuilder.cs index e8c6dac..7899886 100644 --- a/src/PublicApiGenerator/EventNameBuilder.cs +++ b/src/PublicApiGenerator/EventNameBuilder.cs @@ -1,4 +1,3 @@ -using System; using System.CodeDom; using Mono.Cecil; @@ -9,34 +8,52 @@ public static class EventNameBuilder public static string AugmentEventNameWithEventModifierMarkerTemplate(EventDefinition eventDefinition, string name, MemberAttributes addAccessorAttributes, MemberAttributes removeAccessorAttributes) { - if (eventDefinition.AddMethod.IsStatic && eventDefinition.RemoveMethod.IsStatic) + var addVTableAttributes = (addAccessorAttributes & MemberAttributes.VTableMask); + var removeVTableAttributes = (removeAccessorAttributes & MemberAttributes.VTableMask); + + if (eventDefinition.DeclaringType.IsInterface) { - return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "static")}{name}"; + if (addVTableAttributes == MemberAttributes.New && removeVTableAttributes == MemberAttributes.New) + { + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "new")}{name}"; + } + + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, CodeNormalizer.EventRemovePublicMarker)}{name}";; } var addScopeAttributes = (addAccessorAttributes & MemberAttributes.ScopeMask); var removeScopeAttributes = (removeAccessorAttributes & MemberAttributes.ScopeMask); - if (eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual && - addScopeAttributes == MemberAttributes.Override && removeScopeAttributes == MemberAttributes.Override) + + if (addScopeAttributes == MemberAttributes.Static && removeScopeAttributes == MemberAttributes.Static) { - return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "override")}{name}"; + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "static")}{name}"; } - if (eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual && - !eventDefinition.AddMethod.IsAbstract && !eventDefinition.RemoveMethod.IsAbstract) + + if (addScopeAttributes == MemberAttributes.Override && removeScopeAttributes == MemberAttributes.Override) { - return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "virtual")}{name}"; + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "override")}{name}"; } - if (eventDefinition.AddMethod.IsAbstract && eventDefinition.RemoveMethod.IsAbstract && - eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual) + if (addScopeAttributes == (MemberAttributes.Final | MemberAttributes.Override) && removeScopeAttributes == (MemberAttributes.Final | MemberAttributes.Override)) { - return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "abstract")}{name}"; + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "sealed override")}{name}"; } - if (eventDefinition.AddMethod.IsFinal && eventDefinition.RemoveMethod.IsFinal) + if (addScopeAttributes == MemberAttributes.Abstract && removeScopeAttributes == MemberAttributes.Abstract) + { + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "abstract")}{name}"; + } + + if (addVTableAttributes == MemberAttributes.New && removeVTableAttributes == MemberAttributes.New) + { + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "new")}{name}"; + } + + if (eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual && + !eventDefinition.AddMethod.IsAbstract && !eventDefinition.RemoveMethod.IsAbstract) { - return $"{String.Format(CodeNormalizer.EventModifierMarkerTemplate, "sealed")}{name}"; + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "virtual")}{name}"; } return name; From 8575b060a15fe834aad1fb3b5cde51c0ce49c3ed Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 14:47:21 +0100 Subject: [PATCH 4/9] Remove newline --- src/PublicApiGenerator/ApiGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PublicApiGenerator/ApiGenerator.cs b/src/PublicApiGenerator/ApiGenerator.cs index 1d76c7b..6145ece 100644 --- a/src/PublicApiGenerator/ApiGenerator.cs +++ b/src/PublicApiGenerator/ApiGenerator.cs @@ -684,7 +684,6 @@ static void AddEventToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Event if (!(ShouldIncludeMember(addAccessorAttributes) || ShouldIncludeMember(removeAccessorAttributes))) return; - var @event = new CodeMemberEvent { Name = EventNameBuilder.AugmentEventNameWithEventModifierMarkerTemplate(eventDefinition, eventDefinition.Name, addAccessorAttributes, removeAccessorAttributes), From 72d397a116340c5648b105d06d2684d21df926ac Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 14:47:44 +0100 Subject: [PATCH 5/9] Fix special case --- src/PublicApiGenerator/CodeNormalizer.cs | 18 ++++++++++++++++-- src/PublicApiGenerator/EventNameBuilder.cs | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/PublicApiGenerator/CodeNormalizer.cs b/src/PublicApiGenerator/CodeNormalizer.cs index 7aea568..20c156b 100644 --- a/src/PublicApiGenerator/CodeNormalizer.cs +++ b/src/PublicApiGenerator/CodeNormalizer.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; namespace PublicApiGenerator @@ -64,8 +65,21 @@ public static string NormalizeGeneratedCode(StringWriter writer) static string EventModifierMatcher(Match group) { var modifier = @group.Groups[2].Value; - var replacement = modifier == EventRemovePublicMarker ? $"event " : $"public {modifier} event "; - return group.ToString().Replace(string.Format(EventModifierMarkerTemplate, modifier), string.Empty).Replace("public event ", replacement); + + var replacementBuilder = new StringBuilder(); + if (modifier.EndsWith(EventRemovePublicMarker)) + { + replacementBuilder.Append(modifier == EventRemovePublicMarker + ? "event " + : $"{modifier.Replace(EventRemovePublicMarker, string.Empty)} event "); + } + else + { + replacementBuilder.Append($"public {modifier} event "); + } + + return group.ToString().Replace(string.Format(EventModifierMarkerTemplate, modifier), string.Empty) + .Replace("public event ", replacementBuilder.ToString()); } static string RemoveUnnecessaryWhiteSpace(string publicApi) diff --git a/src/PublicApiGenerator/EventNameBuilder.cs b/src/PublicApiGenerator/EventNameBuilder.cs index 7899886..505a8b5 100644 --- a/src/PublicApiGenerator/EventNameBuilder.cs +++ b/src/PublicApiGenerator/EventNameBuilder.cs @@ -15,7 +15,7 @@ public static class EventNameBuilder { if (addVTableAttributes == MemberAttributes.New && removeVTableAttributes == MemberAttributes.New) { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "new")}{name}"; + return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, $"new{CodeNormalizer.EventRemovePublicMarker}")}{name}"; } return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, CodeNormalizer.EventRemovePublicMarker)}{name}";; From 01406251371ff3392ffe7e2856959a4be469515d Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 21:41:00 +0100 Subject: [PATCH 6/9] More test cases including new abstract --- .../Event_modifiers.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/PublicApiGeneratorTests/Event_modifiers.cs b/src/PublicApiGeneratorTests/Event_modifiers.cs index 1077c81..7125d6d 100644 --- a/src/PublicApiGeneratorTests/Event_modifiers.cs +++ b/src/PublicApiGeneratorTests/Event_modifiers.cs @@ -20,6 +20,20 @@ public abstract class ClassWithAbstractEvent }"); } + [Fact] + public void Should_output_abstract_new_modifier() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public abstract class ClassWithAbstractNewEvent : PublicApiGeneratorTests.Examples.ClassWithVirtualEvent + { + protected ClassWithAbstractNewEvent() { } + public new abstract event System.EventHandler Event; + } +}"); + } + [Fact] public void Should_output_static_modifier() { @@ -87,6 +101,20 @@ public class ClassWithEventHiding : PublicApiGeneratorTests.Examples.ClassWithVi public ClassWithEventHiding() { } public new event System.EventHandler Event; } +}"); + } + + [Fact] + public void Should_output_new_modifier_even_under_evil_circumstances() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public abstract class ClassBeingReallyEvilWithInheritingEvent : PublicApiGeneratorTests.Examples.ClassInheritingVirtualGenericEvent + { + public ClassBeingReallyEvilWithInheritingEvent() { } + public new abstract event System.EventHandler Event; + } }"); } } @@ -101,6 +129,11 @@ public abstract class ClassWithAbstractEvent public abstract event EventHandler Event; } + public abstract class ClassWithAbstractNewEvent : ClassWithVirtualEvent + { + public new abstract event EventHandler Event; + } + public class ClassWithStaticEvent { public static event EventHandler Event; @@ -125,6 +158,23 @@ public class ClassWithEventHiding : ClassWithVirtualEvent { public new event EventHandler Event; } + + public class ClassWithVirtualGenericEvent + { + public virtual event EventHandler Event; + } + + public class ClassInheritingVirtualGenericEvent : ClassWithVirtualGenericEvent + { + } + + public abstract class ClassBeingReallyEvilWithInheritingEvent : ClassInheritingVirtualGenericEvent + { + public ClassBeingReallyEvilWithInheritingEvent() + { + } + public new abstract event EventHandler Event; + } } // ReSharper restore ValueParameterNotUsed // ReSharper restore ClassNeverInstantiated.Global From 0a4cc349a590bf68ffde2f6255d21a597e0a59e4 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 21:41:27 +0100 Subject: [PATCH 7/9] Refactor EventBuilderName and support new abstract --- src/PublicApiGenerator/EventNameBuilder.cs | 67 +++++++++++----------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/PublicApiGenerator/EventNameBuilder.cs b/src/PublicApiGenerator/EventNameBuilder.cs index 505a8b5..7e1c79f 100644 --- a/src/PublicApiGenerator/EventNameBuilder.cs +++ b/src/PublicApiGenerator/EventNameBuilder.cs @@ -1,4 +1,6 @@ +using System; using System.CodeDom; +using System.Linq; using Mono.Cecil; namespace PublicApiGenerator @@ -8,52 +10,51 @@ public static class EventNameBuilder public static string AugmentEventNameWithEventModifierMarkerTemplate(EventDefinition eventDefinition, string name, MemberAttributes addAccessorAttributes, MemberAttributes removeAccessorAttributes) { - var addVTableAttributes = (addAccessorAttributes & MemberAttributes.VTableMask); - var removeVTableAttributes = (removeAccessorAttributes & MemberAttributes.VTableMask); - - if (eventDefinition.DeclaringType.IsInterface) - { - if (addVTableAttributes == MemberAttributes.New && removeVTableAttributes == MemberAttributes.New) - { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, $"new{CodeNormalizer.EventRemovePublicMarker}")}{name}"; - } - - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, CodeNormalizer.EventRemovePublicMarker)}{name}";; - } - - var addScopeAttributes = (addAccessorAttributes & MemberAttributes.ScopeMask); - var removeScopeAttributes = (removeAccessorAttributes & MemberAttributes.ScopeMask); - - if (addScopeAttributes == MemberAttributes.Static && removeScopeAttributes == MemberAttributes.Static) - { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "static")}{name}"; - } - - - if (addScopeAttributes == MemberAttributes.Override && removeScopeAttributes == MemberAttributes.Override) + if (addAccessorAttributes != removeAccessorAttributes) { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "override")}{name}"; + return name; } - if (addScopeAttributes == (MemberAttributes.Final | MemberAttributes.Override) && removeScopeAttributes == (MemberAttributes.Final | MemberAttributes.Override)) + if (eventDefinition.DeclaringType.IsInterface) { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "sealed override")}{name}"; + return (addAccessorAttributes & MemberAttributes.VTableMask) == MemberAttributes.New + ? string.Format(CodeNormalizer.EventModifierMarkerTemplate, $"new{CodeNormalizer.EventRemovePublicMarker}") + name + : string.Format(CodeNormalizer.EventModifierMarkerTemplate, CodeNormalizer.EventRemovePublicMarker) + name; } - if (addScopeAttributes == MemberAttributes.Abstract && removeScopeAttributes == MemberAttributes.Abstract) + bool? isNew = null; + var baseType = eventDefinition.DeclaringType.BaseType; + while (baseType != null) { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "abstract")}{name}"; + var typeDef = baseType as TypeDefinition; + isNew = typeDef?.Methods.Any(e => e.Name.Equals(eventDefinition.AddMethod.Name, StringComparison.Ordinal)); + if (isNew.HasValue && isNew.Value) + { + break; + } + baseType = typeDef?.BaseType; } - if (addVTableAttributes == MemberAttributes.New && removeVTableAttributes == MemberAttributes.New) + var addScopeAttributes = addAccessorAttributes & MemberAttributes.ScopeMask; + switch (addScopeAttributes) { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "new")}{name}"; + case MemberAttributes.Static when addScopeAttributes == MemberAttributes.Static: + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "static")+ name; + case MemberAttributes.Override when addScopeAttributes == MemberAttributes.Override: + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "override")+ name; + case MemberAttributes.Final | MemberAttributes.Override when addScopeAttributes == (MemberAttributes.Final | MemberAttributes.Override): + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "sealed override")+ name; + case MemberAttributes.Final when addScopeAttributes == MemberAttributes.Final && isNew.HasValue && isNew.Value: + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "new")+ name; + case MemberAttributes.Abstract when addScopeAttributes == MemberAttributes.Abstract && !isNew.HasValue: + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "abstract")+ name; + case MemberAttributes.Abstract when addScopeAttributes == MemberAttributes.Abstract && isNew.HasValue && isNew.Value: + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "new abstract")+ name; } - if (eventDefinition.AddMethod.IsVirtual && eventDefinition.RemoveMethod.IsVirtual && - !eventDefinition.AddMethod.IsAbstract && !eventDefinition.RemoveMethod.IsAbstract) + if (eventDefinition.AddMethod.IsVirtual && !eventDefinition.AddMethod.IsAbstract) { - return $"{string.Format(CodeNormalizer.EventModifierMarkerTemplate, "virtual")}{name}"; + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "virtual")+ name; } return name; From da8c262d705ddcb3d59636545c8a23f1e1b459ce Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 21:43:15 +0100 Subject: [PATCH 8/9] Removed redundant name --- src/PublicApiGenerator/ApiGenerator.cs | 2 +- src/PublicApiGenerator/EventNameBuilder.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PublicApiGenerator/ApiGenerator.cs b/src/PublicApiGenerator/ApiGenerator.cs index 6145ece..97bcefa 100644 --- a/src/PublicApiGenerator/ApiGenerator.cs +++ b/src/PublicApiGenerator/ApiGenerator.cs @@ -686,7 +686,7 @@ static void AddEventToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Event var @event = new CodeMemberEvent { - Name = EventNameBuilder.AugmentEventNameWithEventModifierMarkerTemplate(eventDefinition, eventDefinition.Name, addAccessorAttributes, removeAccessorAttributes), + Name = EventNameBuilder.AugmentEventNameWithEventModifierMarkerTemplate(eventDefinition, addAccessorAttributes, removeAccessorAttributes), Attributes = CecilEx.CombineAccessorAttributes(addAccessorAttributes, removeAccessorAttributes), CustomAttributes = CreateCustomAttributes(eventDefinition, attributeFilter), Type = eventDefinition.EventType.CreateCodeTypeReference(eventDefinition) diff --git a/src/PublicApiGenerator/EventNameBuilder.cs b/src/PublicApiGenerator/EventNameBuilder.cs index 7e1c79f..6fbd86a 100644 --- a/src/PublicApiGenerator/EventNameBuilder.cs +++ b/src/PublicApiGenerator/EventNameBuilder.cs @@ -7,9 +7,10 @@ namespace PublicApiGenerator { public static class EventNameBuilder { - public static string AugmentEventNameWithEventModifierMarkerTemplate(EventDefinition eventDefinition, string name, + public static string AugmentEventNameWithEventModifierMarkerTemplate(EventDefinition eventDefinition, MemberAttributes addAccessorAttributes, MemberAttributes removeAccessorAttributes) { + string name = eventDefinition.Name; if (addAccessorAttributes != removeAccessorAttributes) { return name; From 9b2733acf2759786a6be86797cb7ff92a789f9b2 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Fri, 8 Nov 2019 21:52:27 +0100 Subject: [PATCH 9/9] Static new --- src/PublicApiGenerator/EventNameBuilder.cs | 4 +++- .../Event_modifiers.cs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/PublicApiGenerator/EventNameBuilder.cs b/src/PublicApiGenerator/EventNameBuilder.cs index 6fbd86a..77eab34 100644 --- a/src/PublicApiGenerator/EventNameBuilder.cs +++ b/src/PublicApiGenerator/EventNameBuilder.cs @@ -39,8 +39,10 @@ public static class EventNameBuilder var addScopeAttributes = addAccessorAttributes & MemberAttributes.ScopeMask; switch (addScopeAttributes) { - case MemberAttributes.Static when addScopeAttributes == MemberAttributes.Static: + case MemberAttributes.Static when addScopeAttributes == MemberAttributes.Static && !isNew.HasValue: return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "static")+ name; + case MemberAttributes.Static when addScopeAttributes == MemberAttributes.Static && isNew.HasValue && isNew.Value: + return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "static new")+ name; case MemberAttributes.Override when addScopeAttributes == MemberAttributes.Override: return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "override")+ name; case MemberAttributes.Final | MemberAttributes.Override when addScopeAttributes == (MemberAttributes.Final | MemberAttributes.Override): diff --git a/src/PublicApiGeneratorTests/Event_modifiers.cs b/src/PublicApiGeneratorTests/Event_modifiers.cs index 7125d6d..df88f7c 100644 --- a/src/PublicApiGeneratorTests/Event_modifiers.cs +++ b/src/PublicApiGeneratorTests/Event_modifiers.cs @@ -48,6 +48,20 @@ public class ClassWithStaticEvent }"); } + [Fact] + public void Should_output_static_new_modifier() + { + AssertPublicApi( + @"namespace PublicApiGeneratorTests.Examples +{ + public class ClassWithStaticNewEvent : PublicApiGeneratorTests.Examples.ClassWithStaticEvent + { + public ClassWithStaticNewEvent() { } + public static new event System.EventHandler Event; + } +}"); + } + [Fact] public void Should_output_virtual_modifier() { @@ -139,6 +153,11 @@ public class ClassWithStaticEvent public static event EventHandler Event; } + public class ClassWithStaticNewEvent : ClassWithStaticEvent + { + public new static event EventHandler Event; + } + public class ClassWithVirtualEvent { public virtual event EventHandler Event;