Skip to content

Commit

Permalink
Added inheritance checking overloads of attribute extension methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jzapdot committed Jul 1, 2021
2 parents eb8a9a9 + 9ee207f commit 8cfca0d
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

using System;
using System.Linq;
using NUnit.Framework;
// ReSharper disable All
Expand Down Expand Up @@ -384,6 +385,30 @@ public static void CanDetectAttributeType()
Assert.IsTrue(nestedTypeSymbol.IsAttribute());
}

[Test]
public static void CanDetectAttributeTypeThatIsInherited()
{
var typeSymbol = TestTools.GetDerivedAttributeClassTypeSymbol();

Assert.IsFalse(typeSymbol.HasAttribute("BaseAttribute"));
Assert.IsTrue(typeSymbol.HasAttribute("BaseAttribute", canInherit:true));
}

[Test]
public static void CanGetAttributeTypeThatIsInherited()
{
var typeSymbol = TestTools.GetDerivedAttributeClassTypeSymbol();

Assert.IsEmpty(typeSymbol.GetAttributes("BaseAttribute"));

var attrData = typeSymbol.GetAttributes("BaseAttribute", canInherit: true);
Assert.IsNotEmpty(attrData);

var firstAttr = attrData.Single();

Assert.AreEqual("FooDerivedAttribute", firstAttr.AttributeClass.Name);
}

[Test]
public static void CanDetectAndFilterAttributesByTypeName()
{
Expand Down
8 changes: 8 additions & 0 deletions ExternalApp/Genesis.Plugin.Tests/TestTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,14 @@ public static ITypeSymbol GetRuntimeTypeSymbol()
return GetAllFixtureTypeSymbols().First(x => x.Name == "RuntimeClass");
}

/// <summary>
/// Returns a <see cref="ITypeSymbol"/> from the "Runtime" assembly.
/// </summary>
public static ITypeSymbol GetDerivedAttributeClassTypeSymbol()
{
return GetAllFixtureTypeSymbols().First(x => x.Name == "FooDerivedClassExample");
}

/// <summary>
/// Returns a <see cref="ITypeSymbol"/> from the "Assembly-Csharp" assembly for a Unity MonoBehaviour derived
/// type.
Expand Down
52 changes: 48 additions & 4 deletions ExternalApp/Genesis.Plugin/Extensions/AttributeDataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,69 @@ public static class AttributeDataExtensions
/// Returns all <see cref="AttributeData"/> from this <paramref name="attributeData"/> where the attribute
/// class's name matches <paramref name="attributeTypeName"/>.
/// </summary>
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="attributeTypeName"/>.</param>
public static IEnumerable<AttributeData> GetAttributes(
this IEnumerable<AttributeData> attributeData,
string attributeTypeName)
string attributeTypeName,
bool canInherit = true)
{
return attributeData
if (canInherit)
{
foreach (var attrData in attributeData)
{
var attrClasses = attrData.AttributeClass.GetBaseTypesAndThis();
foreach (var typeSymbol in attrClasses)
{
if (typeSymbol.Name == attributeTypeName)
{
yield return attrData;
break;
}
}
}
}

foreach (var attrData in attributeData
.Where(
attr =>
attr.AttributeClass != null &&
attr.AttributeClass.Name == attributeTypeName);
attr.AttributeClass.Name == attributeTypeName))
{
yield return attrData;
}
}

/// <summary>
/// Returns true if this <paramref name="attributeData" /> has an <see cref="AttributeData" /> with
/// <paramref name="attributeTypeName"/>.
/// </summary>
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="attributeTypeName"/>.</param>
public static bool HasAttribute(
this IEnumerable<AttributeData> attributeData,
string attributeTypeName)
string attributeTypeName,
bool canInherit = false)
{
if (canInherit)
{
var hasAttribute = false;
foreach (var attrData in attributeData)
{
var attrClasses = attrData.AttributeClass.GetBaseTypesAndThis();
foreach (var typeSymbol in attrClasses)
{
if (typeSymbol.Name == attributeTypeName)
{
hasAttribute = true;
break;
}
}
}

return hasAttribute;
}

return attributeData.Any(attr =>
attr.AttributeClass != null &&
attr.AttributeClass.Name == attributeTypeName);
Expand Down
27 changes: 21 additions & 6 deletions ExternalApp/Genesis.Plugin/Extensions/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,15 @@ public static bool IsAttribute(this ITypeSymbol typeSymbol)
/// Returns true if this <paramref name="typeSymbol" /> has <see cref="Attribute" /> with
/// <paramref name="attributeTypeName"/>.
/// </summary>
public static bool HasAttribute(this ITypeSymbol typeSymbol, string attributeTypeName)
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="attributeTypeName"/>, otherwise only the current attribute type will be
/// checked.</param>
public static bool HasAttribute(
this ITypeSymbol typeSymbol,
string attributeTypeName,
bool canInherit = false)
{
return typeSymbol.GetAttributes().HasAttribute(attributeTypeName);
return typeSymbol.GetAttributes().HasAttribute(attributeTypeName, canInherit);
}

/// <summary>
Expand All @@ -440,23 +446,32 @@ public static bool HasAttribute(this ITypeSymbol typeSymbol, string attributeTyp
/// <typeparamref name="T" /> must be an attribute, otherwise an assertion
/// will be thrown.
/// </exception>
public static bool HasAttribute<T>(this ITypeSymbol typeSymbol)
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="typeSymbol"/>, otherwise only the current attribute type will be
/// checked.</param>
public static bool HasAttribute<T>(this ITypeSymbol typeSymbol, bool canInherit = false)
{
if (!typeof(Attribute).IsAssignableFrom(typeof(T)))
{
throw new ArgumentException("T must be assignable to Attribute.");
}

return typeSymbol.HasAttribute(typeof(T).Name);
return typeSymbol.HasAttribute(typeof(T).Name, canInherit);
}

/// <summary>
/// Returns all <see cref="AttributeData"/> decorated on this <see cref="ITypeSymbol"/> where the
/// attribute class's name matches <paramref name="attributeTypeName"/>.
/// </summary>
public static IEnumerable<AttributeData> GetAttributes(this ITypeSymbol typeSymbol, string attributeTypeName)
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="attributeTypeName"/>, otherwise only the current attribute type will be
/// checked.</param>
public static IEnumerable<AttributeData> GetAttributes(
this ITypeSymbol typeSymbol,
string attributeTypeName,
bool canInherit = false)
{
return typeSymbol.GetAttributes().GetAttributes(attributeTypeName);
return typeSymbol.GetAttributes().GetAttributes(attributeTypeName, canInherit);
}

/// <summary>
Expand Down
44 changes: 11 additions & 33 deletions ExternalApp/Genesis.Plugin/Roslyn/CachedNamedTypeSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,7 @@ public CachedNamedTypeSymbol(INamedTypeSymbol namedTypeSymbol)
NamedTypeSymbol = namedTypeSymbol;
}

/// <summary>
/// Returns true if this <see cref="NamedTypeSymbol"/> is decorated with interface type
/// <typeparamref name="T"/>.
/// </summary>
/// <exception cref="ArgumentException">
/// <typeparamref name="T" /> must be an interface, otherwise an exception will be thrown.
/// </exception>
/// <inheritdoc />
public bool ImplementsInterface<T>()
{
if (!typeof(T).IsInterface)
Expand All @@ -115,50 +109,34 @@ public bool ImplementsInterface<T>()
return ImplementsInterface(interfaceName);
}

/// <summary>
/// Returns true if <see cref="NamedTypeSymbol" /> implements an interface matching
/// <paramref name="interfaceTypeName" />.
/// </summary>
/// <inheritdoc />
public bool ImplementsInterface(string interfaceTypeName)
{
return NamedTypeSymbol.Name != interfaceTypeName &&
InterfaceTypeSymbols.Any(interfaceTypeSymbol => interfaceTypeSymbol.Name == interfaceTypeName);
}

/// <summary>
/// Returns all <see cref="Microsoft.CodeAnalysis.AttributeData"/> for <see cref="NamedTypeSymbol"/> where
/// the attribute class's name matches <paramref name="attributeTypeName"/>.
/// </summary>
public IEnumerable<AttributeData> GetAttributes(string attributeTypeName)
/// <inheritdoc />
public IEnumerable<AttributeData> GetAttributes(string attributeTypeName, bool canInherit = false)
{
return AttributeData.GetAttributes(attributeTypeName);
return AttributeData.GetAttributes(attributeTypeName, canInherit);
}

/// <summary>
/// Returns true if <see cref="NamedTypeSymbol"/> has <see cref="Attribute" />-derived type
/// <typeparamref name="T" />.
/// </summary>
/// <exception cref="Exception">
/// <typeparamref name="T" /> must be an attribute, otherwise an assertion
/// will be thrown.
/// </exception>
public bool HasAttribute<T>()
/// <inheritdoc />
public bool HasAttribute<T>(bool canInherit = false)
{
if (!typeof(Attribute).IsAssignableFrom(typeof(T)))
{
throw new ArgumentException("T must be assignable to Attribute.");
}

return HasAttribute(typeof(T).Name);
return HasAttribute(typeof(T).Name, canInherit);
}

/// <summary>
/// Returns true if <see cref="NamedTypeSymbol" /> has an
/// <see cref="Microsoft.CodeAnalysis.AttributeData" /> with <paramref name="attributeTypeName"/>.
/// </summary>
public bool HasAttribute(string attributeTypeName)
/// <inheritdoc />
public bool HasAttribute(string attributeTypeName, bool canInherit = false)
{
return AttributeData.HasAttribute(attributeTypeName);
return AttributeData.HasAttribute(attributeTypeName, canInherit);
}

#region Static Helpers
Expand Down
15 changes: 12 additions & 3 deletions ExternalApp/Genesis.Plugin/Roslyn/ICachedNamedTypeSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ public interface ICachedNamedTypeSymbol
/// Returns all <see cref="Microsoft.CodeAnalysis.AttributeData"/> for <see cref="NamedTypeSymbol"/> where
/// the attribute class's name matches <paramref name="attributeTypeName"/>.
/// </summary>
IEnumerable<AttributeData> GetAttributes(string attributeTypeName);
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="attributeTypeName"/>, otherwise only the current attribute type will be
/// checked.</param>
IEnumerable<AttributeData> GetAttributes(string attributeTypeName, bool canInherit = false);

/// <summary>
/// Returns true if <see cref="NamedTypeSymbol"/> has <see cref="Attribute" />-derived type
Expand All @@ -75,12 +78,18 @@ public interface ICachedNamedTypeSymbol
/// <typeparamref name="T" /> must be an attribute, otherwise an assertion
/// will be thrown.
/// </exception>
bool HasAttribute<T>();
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="attributeTypeName"/>, otherwise only the current attribute type will be
/// checked.</param>
bool HasAttribute<T>(bool canInherit = false);

/// <summary>
/// Returns true if <see cref="NamedTypeSymbol" /> has an
/// <see cref="Microsoft.CodeAnalysis.AttributeData" /> with <paramref name="attributeTypeName"/>.
/// </summary>
bool HasAttribute(string attributeTypeName);
/// <param name="canInherit">If true, all base classes of any attributes will be checked and compared for
/// equality against <paramref name="attributeTypeName"/>, otherwise only the current attribute type will be
/// checked.</param>
bool HasAttribute(string attributeTypeName, bool canInherit = false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Fixtures
{
[AttributeUsage(AttributeTargets.Class)]
public class BarDerivedAttribute : BaseAttribute
{
/// <inheritdoc />
public BarDerivedAttribute() : base(AttributeType.Bar)
{
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace Fixtures
{
public enum AttributeType
{
Foo,
Bar
}


public abstract class BaseAttribute : Attribute
{
public AttributeType Type { get; }

protected BaseAttribute(AttributeType type)
{
Type = type;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace Fixtures
{
[AttributeUsage(AttributeTargets.Class)]
public class FooDerivedAttribute : BaseAttribute
{
/// <inheritdoc />
public FooDerivedAttribute() : base(AttributeType.Foo)
{

}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Fixtures
{
[FooDerived]
public class FooDerivedClassExample
{

}
}
Loading

0 comments on commit 8cfca0d

Please sign in to comment.