Skip to content

Commit

Permalink
Merge pull request #31 from Frederik91/issue/20
Browse files Browse the repository at this point in the history
Fixed issue #30 and added Exclude property to exclude members from in…
  • Loading branch information
Frederik91 committed Jan 25, 2024
2 parents f5c46a2 + 69288cb commit e6a166b
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
namespace MakeInterface.Contracts.Attributes;
public class GenerateInterfaceAttribute : Attribute
{
public List<string>? ExcludedMembers { get; set; }
}
2 changes: 1 addition & 1 deletion MakeInterface.Contracts/MakeInterface.Contracts.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<OutputType>library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>MakeInterface.Contracts</PackageId>
<Version>0.3.3</Version>
<Version>0.4.0</Version>
<Authors>Frederik Tegnander</Authors>
<Company>COWI</Company>
<PackageDescription>Contains attriubutes to use with Interfaces.SourceGenerator</PackageDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Text;

namespace MakeInterface.Generator.Attributes;

public class GenerateInterfaceAttribute : Attribute
{
public string[]? Exclude { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public static string GetName(this MemberDeclarationSyntax syntax)
{
PropertyDeclarationSyntax property => property.Identifier.Text,
MethodDeclarationSyntax method => method.Identifier.Text,
_ => throw new NotImplementedException(),
FieldDeclarationSyntax field => field.Declaration.Variables.First().Identifier.Text,
_ => throw new NotImplementedException($"Syntax of kind {syntax.GetType().Name} is not implemented"),
};
}
}
103 changes: 100 additions & 3 deletions MakeInterface.Generator/InterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,31 @@ private void Generate(SourceProductionContext ctx, (SemanticModel, CompilationUn
{
var members = new List<MemberDeclarationSyntax>();

var attribute = classSyntax.AttributeLists
.SelectMany(x => x.Attributes)
.FirstOrDefault(x => x.Name.ToString() == nameof(GenerateInterfaceAttribute) || x.Name.ToString() + "Attribute" == nameof(GenerateInterfaceAttribute));

if (attribute is null)
throw new Exception($"Class '{classSyntax.Identifier.Text}' does not have the '{nameof(GenerateInterfaceAttribute)}' attribute");

var excludedMembers = GetExcludedMembers(attribute);
var membersFromImplementedTypes = GetMembersDeclaredByInterfacesTypes(classSyntax, semanticModel);

foreach (var memberSyntax in classSyntax.Members)
{
if (IsNotValidInterfaceNamber(memberSyntax.Modifiers))
continue;

var name = memberSyntax.GetName();
if (excludedMembers.Contains(name))
continue;

if (membersFromImplementedTypes.Contains(memberSyntax.GetName()))
continue;

var publicModifier = memberSyntax.Modifiers.FirstOrDefault(x => x.IsKind(SyntaxKind.PublicKeyword));
if (memberSyntax is FieldDeclarationSyntax fieldDeclarationSyntax && ContainsAttributeWithName(fieldDeclarationSyntax.AttributeLists, "ObservableProperty"))
{
var name = fieldDeclarationSyntax.Declaration.Variables.First().Identifier.Text;
name = GetObservablePropertyName(name);
// Create property from field
var propertyDeclarationSyntax = SyntaxFactory.PropertyDeclaration(fieldDeclarationSyntax.Declaration.Type, name);
Expand All @@ -144,6 +160,9 @@ private void Generate(SourceProductionContext ctx, (SemanticModel, CompilationUn
var asyncModifier = modifiers.FirstOrDefault(x => x.IsKind(SyntaxKind.AsyncKeyword));
modifiers = modifiers.Remove(asyncModifier);

var overrideModifier = modifiers.FirstOrDefault(x => x.IsKind(SyntaxKind.OverrideKeyword));
modifiers = modifiers.Remove(overrideModifier);

var newMethod = methodSyntax
.WithBody(null)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
Expand All @@ -156,6 +175,8 @@ private void Generate(SourceProductionContext ctx, (SemanticModel, CompilationUn
var newProperty = propertySyntax
.WithModifiers(propertySyntax.Modifiers.Remove(publicModifier));

var overrideModifier = newProperty.Modifiers.FirstOrDefault(x => x.IsKind(SyntaxKind.OverrideKeyword));
newProperty = newProperty.WithModifiers(newProperty.Modifiers.Remove(overrideModifier));

if (newProperty.ExpressionBody is not null)
{
Expand Down Expand Up @@ -186,7 +207,7 @@ private void Generate(SourceProductionContext ctx, (SemanticModel, CompilationUn
.WithBody(null)
.WithExpressionBody(null)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));

newAccessors = newAccessors.Add(newAccessor);
}
newProperty = newProperty.WithAccessorList(SyntaxFactory.AccessorList(newAccessors));
Expand All @@ -206,6 +227,77 @@ private void Generate(SourceProductionContext ctx, (SemanticModel, CompilationUn
return interfaceDeclaration;
}

private List<string> GetExcludedMembers(AttributeSyntax attribute)
{
if (attribute.ArgumentList is null)
return [];

var result = new List<string>();
foreach (var argument in attribute.ArgumentList.Arguments)
{
if (argument.Expression is not ArrayCreationExpressionSyntax literalExpressionSyntax)
continue;

if (literalExpressionSyntax.Initializer is not InitializerExpressionSyntax initializerExpressionSyntax)
continue;

foreach (var expression in initializerExpressionSyntax.Expressions)
{
if (expression is not LiteralExpressionSyntax literalExpression)
continue;

if (literalExpression.Token.Value is string value)
result.Add(value);
}
}
return result;
}

private List<string> GetMembersDeclaredByInterfacesTypes(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel)
{
var baseTypes = classSyntax.BaseList?.Types
.OfType<SimpleBaseTypeSyntax>()
.Select(x => semanticModel.GetSymbolInfo(x.Type).Symbol)
.OfType<ITypeSymbol>();

var result = new List<string>();
foreach (var baseType in baseTypes ?? new List<ITypeSymbol>())
{
var interfaceMembers = GetMembersFromInterfaces(baseType);
result.AddRange(interfaceMembers);
}
return result;
}

private List<string> GetMembersFromInterfaces(ITypeSymbol baseType)
{
var result = new List<string>();
foreach (var interfaceType in baseType.AllInterfaces)
{
var interfaceMembers = GetMembersFromType(interfaceType);
result.AddRange(interfaceMembers);
}
return result;
}

private static List<string> GetMembersFromType(ITypeSymbol baseType)
{
List<string> members = new();

foreach (var member in baseType.GetMembers())
{
if (baseType.TypeKind == TypeKind.Class && IsNotValidInterfaceNamber(member))
continue;

if (member is IMethodSymbol methodSymbol && methodSymbol.MethodKind is MethodKind.PropertyGet or MethodKind.PropertySet)
continue;

members.Add(member.Name);
}

return members;
}

private InterfaceDeclarationSyntax AddInterfaces(InterfaceDeclarationSyntax interfaceDeclaration, ClassDeclarationSyntax classSyntax, SemanticModel semanticModel)
{
var baseInterfaces = classSyntax.BaseList?.Types
Expand Down Expand Up @@ -299,7 +391,12 @@ private static bool IsNotValidInterfaceNamber(ISymbol member)

private static bool IsNotValidInterfaceNamber(SyntaxTokenList tokenList)
{
return tokenList.IsStatic() || tokenList.IsOverride();
if (tokenList.IsStatic())
{
return true;
}

return false;
}

private SyntaxList<UsingDirectiveSyntax> CreateUsingSyntax(ClassDeclarationSyntax classSyntax)
Expand Down
4 changes: 2 additions & 2 deletions MakeInterface.Generator/MakeInterface.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<LangVersion>12</LangVersion>
<NoWarn>NU5128</NoWarn>
</PropertyGroup>

<PropertyGroup>
<OutputType>library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>MakeInterface.Generator</PackageId>
<Version>0.3.3</Version>
<Version>0.4.0</Version>
<Authors>Frederik Tegnander</Authors>
<Company>COWI</Company>
<PackageTags>Interfaces;SourceGenerator;MakeInterface</PackageTags>
Expand Down
77 changes: 63 additions & 14 deletions MakeInterface.Tests/InterfaceGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,6 @@ namespace MakeInterface.Tests;
[UsesVerify]
public class InterfaceGeneratorTests
{
private static readonly string _header = """
// <auto-generated/>
#pragma warning disable
#nullable enable

""";

private readonly ImmutableArray<string> references = AppDomain.CurrentDomain
.GetAssemblies()
.Where(assembly => !assembly.IsDynamic)
.Select(assembly => assembly.Location)
.ToImmutableArray();

[Fact]
public Task CreateInterface()
{
Expand All @@ -46,10 +33,14 @@ public void RefMethod(ref string data) { }
public void DefaultNullMethod(string? data = default) { }
public void DefaultMethod(int data = default) { }

public override string AbstractPropertyFromInterface { get; set; }
public override string AbstractProperty { get; set; }
public override void AbstractMethod { get; set; }
public override void AbstractMethodFromInterface { get; set; }
public override string VirtualProperty { get; set; }
public override string VirtualPropertyFromInterface { get; set; }
public override void VirtualMethod() { }
public override void VirtualMethodFromInterface() { }

public async Task AsyncMethod() { }

Expand All @@ -62,13 +53,21 @@ public partial class Class1
public void Method2() { }
}

public abstract class BaseClass
public abstract class BaseClass : IBaseClass
{
public abstract string AbstractProperty { get; set; }
public abstract void AbstractMethod() { }
public virtual string VirtualProperty { get; set; }
public virtual void VirtualMethod() { }
}

public interface IBaseClass
{
string AbstractPropertyFromInterface { get; set; }
void AbstractMethodFromInterface();
string VirtualPropertyFromInterface { get; set; }
void VirtualMethodFromInterface();
}
}

namespace MakeInterface.Tests.Models
Expand Down Expand Up @@ -249,6 +248,56 @@ public string Test
}
}
}
""";

return TestHelper.Verify(source);
}

[Fact]
public Task OverridenMembers()
{
var source = """
namespace MakeInterface.Tests
{
public class BaseClass
{
public abstract string Get();
}

[GenerateInterface]
public class Class : BaseClass
{
public override string Get()
{
return "foo";
}
}
}
""";

return TestHelper.Verify(source);
}

[Fact]
public Task ExcludedMembers()
{
var source = """
namespace MakeInterface.Tests
{
[GenerateInterface(Exclude = new string[] { "Get2" })]
public class Class
{
public string Get()
{
return "foo";
}

public string Get2()
{
return "foo";
}
}
}
""";

return TestHelper.Verify(source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#nullable enable
namespace MakeInterface.Tests
{
public partial interface IClass1 :
public partial interface IClass1
{
void Method1();
TestModel Test();
Expand All @@ -23,6 +23,13 @@ void Test3<T>(T data)
void RefMethod(ref string data);
void DefaultNullMethod(string? data = default);
void DefaultMethod(int data = default);
string AbstractProperty { get; set; }

void AbstractMethod { get; set; }

string VirtualProperty { get; set; }

void VirtualMethod();
Task AsyncMethod();
string PropertyWithDefault { get; }

Expand All @@ -35,7 +42,7 @@ void Test3<T>(T data)
#nullable enable
namespace MakeInterface.Tests.Models
{
public partial interface ITestModel :
public partial interface ITestModel
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ void Test3<T>(T data)
void RefMethod(ref string data);
void DefaultNullMethod(string? data = default);
void DefaultMethod(int data = default);
string AbstractProperty { get; set; }

void AbstractMethod { get; set; }

string VirtualProperty { get; set; }

void VirtualMethod();
Task AsyncMethod();
string PropertyWithDefault { get; }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//HintName: ITest.g.cs
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MakeInterface.Tests
{
public partial interface IClass
{
string Get();
string Get2();
}
}
Loading

0 comments on commit e6a166b

Please sign in to comment.