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

Event modifier support #130

Merged
merged 9 commits into from Nov 8, 2019
Merged
2 changes: 1 addition & 1 deletion src/PublicApiGenerator/ApiGenerator.cs
Expand Up @@ -686,7 +686,7 @@ static void AddEventToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Event

var @event = new CodeMemberEvent
{
Name = eventDefinition.Name,
Name = EventNameBuilder.AugmentEventNameWithEventModifierMarkerTemplate(eventDefinition, addAccessorAttributes, removeAccessorAttributes),
Attributes = CecilEx.CombineAccessorAttributes(addAccessorAttributes, removeAccessorAttributes),
CustomAttributes = CreateCustomAttributes(eventDefinition, attributeFilter),
Type = eventDefinition.EventType.CreateCodeTypeReference(eventDefinition)
Expand Down
24 changes: 24 additions & 0 deletions 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
Expand All @@ -19,6 +20,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";
danielmarbach marked this conversation as resolved.
Show resolved Hide resolved

public static string NormalizeGeneratedCode(StringWriter writer)
{
Expand Down Expand Up @@ -49,6 +52,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
Expand All @@ -58,6 +62,26 @@ public static string NormalizeGeneratedCode(StringWriter writer)
return gennedClass;
}

static string EventModifierMatcher(Match group)
{
var modifier = @group.Groups[2].Value;

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)
{
return string.Join(Environment.NewLine, publicApi.Split(new[]
Expand Down
66 changes: 66 additions & 0 deletions src/PublicApiGenerator/EventNameBuilder.cs
@@ -0,0 +1,66 @@
using System;
using System.CodeDom;
using System.Linq;
using Mono.Cecil;

namespace PublicApiGenerator
{
public static class EventNameBuilder
{
public static string AugmentEventNameWithEventModifierMarkerTemplate(EventDefinition eventDefinition,
MemberAttributes addAccessorAttributes, MemberAttributes removeAccessorAttributes)
{
string name = eventDefinition.Name;
if (addAccessorAttributes != removeAccessorAttributes)
{
return name;
}

if (eventDefinition.DeclaringType.IsInterface)
{
return (addAccessorAttributes & MemberAttributes.VTableMask) == MemberAttributes.New
? string.Format(CodeNormalizer.EventModifierMarkerTemplate, $"new{CodeNormalizer.EventRemovePublicMarker}") + name
: string.Format(CodeNormalizer.EventModifierMarkerTemplate, CodeNormalizer.EventRemovePublicMarker) + name;
}

bool? isNew = null;
var baseType = eventDefinition.DeclaringType.BaseType;
while (baseType != null)
{
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;
}

var addScopeAttributes = addAccessorAttributes & MemberAttributes.ScopeMask;
switch (addScopeAttributes)
{
case MemberAttributes.Static when addScopeAttributes == MemberAttributes.Static && !isNew.HasValue:
danielmarbach marked this conversation as resolved.
Show resolved Hide resolved
danielmarbach marked this conversation as resolved.
Show resolved Hide resolved
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):
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.AddMethod.IsAbstract)
{
return string.Format(CodeNormalizer.EventModifierMarkerTemplate, "virtual")+ name;
}

return name;
}
}
}
201 changes: 201 additions & 0 deletions src/PublicApiGeneratorTests/Event_modifiers.cs
@@ -0,0 +1,201 @@
using PublicApiGeneratorTests.Examples;
using System;
using Xunit;

namespace PublicApiGeneratorTests
{
public class Event_modifiers : ApiGeneratorTestsBase
{
[Fact]
public void Should_output_abstract_modifier()
{
AssertPublicApi<ClassWithAbstractEvent>(
@"namespace PublicApiGeneratorTests.Examples
{
public abstract class ClassWithAbstractEvent
{
protected ClassWithAbstractEvent() { }
public abstract event System.EventHandler Event;
}
}");
}

[Fact]
public void Should_output_abstract_new_modifier()
{
AssertPublicApi<ClassWithAbstractNewEvent>(
@"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()
{
AssertPublicApi<ClassWithStaticEvent>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithStaticEvent
{
public ClassWithStaticEvent() { }
public static event System.EventHandler Event;
}
}");
}

[Fact]
public void Should_output_static_new_modifier()
{
AssertPublicApi<ClassWithStaticNewEvent>(
@"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()
{
AssertPublicApi<ClassWithVirtualEvent>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithVirtualEvent
{
public ClassWithVirtualEvent() { }
public virtual event System.EventHandler Event;
}
}");
}

[Fact]
public void Should_output_override_modifier()
{
AssertPublicApi<ClassWithOverridingEvent>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithOverridingEvent : PublicApiGeneratorTests.Examples.ClassWithVirtualEvent
{
public ClassWithOverridingEvent() { }
public override event System.EventHandler Event;
}
}");
}

[Fact]
public void Should_output_sealed_modifier()
{
AssertPublicApi<ClassWithSealedOverridingEvent>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithSealedOverridingEvent : PublicApiGeneratorTests.Examples.ClassWithVirtualEvent
{
public ClassWithSealedOverridingEvent() { }
public sealed override event System.EventHandler Event;
}
}");
}

[Fact]
public void Should_output_new_modifier()
{
AssertPublicApi<ClassWithEventHiding>(
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithEventHiding : PublicApiGeneratorTests.Examples.ClassWithVirtualEvent
{
public ClassWithEventHiding() { }
public new event System.EventHandler Event;
}
}");
}

[Fact]
public void Should_output_new_modifier_even_under_evil_circumstances()
{
AssertPublicApi<ClassBeingReallyEvilWithInheritingEvent>(
@"namespace PublicApiGeneratorTests.Examples
{
public abstract class ClassBeingReallyEvilWithInheritingEvent : PublicApiGeneratorTests.Examples.ClassInheritingVirtualGenericEvent
{
public ClassBeingReallyEvilWithInheritingEvent() { }
public new abstract 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 abstract class ClassWithAbstractNewEvent : ClassWithVirtualEvent
{
public new abstract event EventHandler Event;
}

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;
}

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;
}

public class ClassWithVirtualGenericEvent
{
public virtual event EventHandler<EventArgs> 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
// ReSharper restore UnusedMember.Global
}
4 changes: 2 additions & 2 deletions src/PublicApiGeneratorTests/Interface_event_attributes.cs
Expand Up @@ -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;
}
}");
}
Expand All @@ -34,7 +34,7 @@ public void Should_skip_excluded_attribute()
{
public interface IInterfaceWithEventWithAttribute
{
public event System.EventHandler OnClicked;
event System.EventHandler OnClicked;
}
}", options);
}
Expand Down