Skip to content

Commit

Permalink
Factoring out some utilities
Browse files Browse the repository at this point in the history
Added tests for base class search
  • Loading branch information
raffaeler committed Oct 29, 2021
1 parent 9c411e2 commit f909ae8
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 43 deletions.
52 changes: 51 additions & 1 deletion src/SharedGenerators/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.CodeAnalysis;
Expand All @@ -15,9 +16,58 @@ public static bool HasBaseType(this ClassDeclarationSyntax classDeclaration)
.FirstOrDefault()
.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.SimpleBaseType);

if (hasBaseType == null || !hasBaseType.Value) return true;
if (hasBaseType == true) return true;

return false;
}

public static INamedTypeSymbol? SearchBaseTypes(this ClassDeclarationSyntax classDeclaration,
SemanticModel model, Func<INamedTypeSymbol, bool> func)
{
var classTypeSymbol = model.GetDeclaredSymbol(classDeclaration) as ITypeSymbol;
if (classTypeSymbol == null) return null;

var baseTypeSymbol = classTypeSymbol.BaseType;
while (baseTypeSymbol != null)
{
var found= func(baseTypeSymbol);
if (found) return baseTypeSymbol;
baseTypeSymbol = baseTypeSymbol.BaseType;
}

return null;
}

public static bool HasMemberCalled(this INamedTypeSymbol symbol, string name)
{
if (symbol == null) return false;
return symbol.GetMembers(name).Length > 0;
}

public static IMethodSymbol? GetBestMatchForPropertyChangedMethod(this INamedTypeSymbol symbol)
{
return symbol.GetMembers()
.OfType<IMethodSymbol>()
.Where(s => s.Name.Contains("PropertyChanged") &&
s.Parameters.Length == 1 &&
(s.Parameters[0].Type.Name == "String" ||
s.Parameters[0].Type.Name == "string"))
.FirstOrDefault();
}

public static (bool generateEvent, string triggerName) GetPropertyChangedGenerationInfo(
this ClassDeclarationSyntax classDeclaration, SemanticModel model)
{
var baseTypeWithEvent = classDeclaration.SearchBaseTypes(model,
symbol => symbol.HasMemberCalled(ClassInfo.PropertyChangedEventName));

if (baseTypeWithEvent == null) return (true, ClassInfo.OnPropertyChangedMethodName);

var candidateMethod = baseTypeWithEvent.GetBestMatchForPropertyChangedMethod();
if (candidateMethod == null) return (false, ClassInfo.OnPropertyChangedMethodName);

return (false, candidateMethod.Name);
}

}
}
73 changes: 72 additions & 1 deletion src/SpeedyGenerators.Tests/SemanticTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,84 @@ public partial class Test
Assert.IsTrue(ns9.SequenceEqual(new[] { "NamespaceA1" }));
}

[TestMethod]
public void GetBaseClassInfo1()
{
SourceText sourceText = SourceText.From(@"
namespace SomeNamespace
{
public interface IFace { }
public partial class SomeClass : Intermediate, IFace { }
public partial class Intermediate : Base, IFace {}
public partial class Base : IFace
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected virtual void SomethingOnPropertyChangedXYZ([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { }
}
}", Encoding.UTF8);

var tree = CSharpSyntaxTree.ParseText(sourceText);
if (tree == null) { Assert.Fail("Tree is null"); return; }

var compilation = CSharpCompilation.Create("fake", new[] { tree }, null, null);
var model = compilation.GetSemanticModel(tree);
if (model == null) { Assert.Fail(); return; }

var someClassDeclaration = tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.FirstOrDefault(c => c.Identifier.ToString() == "SomeClass");
if (someClassDeclaration == null) { Assert.Fail(); return; }

var (generateEvent, triggerName) = someClassDeclaration.GetPropertyChangedGenerationInfo(model);
Assert.IsFalse(generateEvent);
Assert.AreEqual("SomethingOnPropertyChangedXYZ", triggerName);
}


[TestMethod]
public void GetBaseClassInfo2()
{
SourceText sourceText = SourceText.From(@"
namespace SomeNamespace
{
public interface IFace { }
public partial class SomeClass : Intermediate, IFace { }
public partial class Intermediate : Base, IFace {}
public partial class Base : IFace
{
}
}", Encoding.UTF8);

var tree = CSharpSyntaxTree.ParseText(sourceText);
if (tree == null) { Assert.Fail("Tree is null"); return; }

var compilation = CSharpCompilation.Create("fake", new[] { tree }, null, null);
var model = compilation.GetSemanticModel(tree);
if (model == null) { Assert.Fail(); return; }

var someClassDeclaration = tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.FirstOrDefault(c => c.Identifier.ToString() == "SomeClass");
if (someClassDeclaration == null) { Assert.Fail(); return; }

var (generateEvent, triggerName) = someClassDeclaration.GetPropertyChangedGenerationInfo(model);
Assert.IsTrue(generateEvent);
Assert.AreEqual("OnPropertyChanged", triggerName);
}






private (IList<FieldDeclarationSyntax> fields, SemanticModel? model) GetFields(SourceText sourceText)
{
var tree = CSharpSyntaxTree.ParseText(sourceText);
if (tree == null) { Assert.Fail("Tree is null"); return (new List<FieldDeclarationSyntax>(), null); }
if (tree == null)
{
Assert.Fail("Tree is null");
return (new List<FieldDeclarationSyntax>(), null);
}

var compilation = CSharpCompilation.Create("fake", new[] { tree }, null, null);
var model = compilation.GetSemanticModel(tree);
Expand Down
43 changes: 2 additions & 41 deletions src/SpeedyGenerators/PropertyChangedGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public void Execute(GeneratorExecutionContext context)
var syntaxTree = classInfo.ClassDeclaration.SyntaxTree;
var semanticModel = context.Compilation.GetSemanticModel(syntaxTree);

var (generateEvent, triggerMethodName) = LookupBaseType(semanticModel, classInfo.ClassDeclaration);
var (generateEvent, triggerMethodName) =
classInfo.ClassDeclaration.GetPropertyChangedGenerationInfo(semanticModel);
classInfo.GenerateEvent = generateEvent;
classInfo.TriggerMethodName = triggerMethodName;

Expand All @@ -57,46 +58,6 @@ public void Execute(GeneratorExecutionContext context)
}
}

private (bool generateEvent, string triggerName) LookupBaseType(SemanticModel model, ClassDeclarationSyntax classDeclaration)
{
if(!classDeclaration.HasBaseType()) return (true, ClassInfo.OnPropertyChangedMethodName);

var classTypeSymbol = model.GetDeclaredSymbol(classDeclaration) as ITypeSymbol;
if (classTypeSymbol == null) return (true, ClassInfo.OnPropertyChangedMethodName);

var baseTypeSymbol = classTypeSymbol.BaseType;
var generateEvent = true;
while (baseTypeSymbol != null && generateEvent)
{
generateEvent = baseTypeSymbol.GetMembers(ClassInfo.PropertyChangedEventName).Length == 0;
if(generateEvent)
{
baseTypeSymbol = baseTypeSymbol.BaseType;
continue;
}

if (baseTypeSymbol.GetMembers(ClassInfo.OnPropertyChangedMethodName).Length > 0)
{
return (generateEvent, ClassInfo.OnPropertyChangedMethodName);
}

var existent = baseTypeSymbol.GetMembers()
.OfType<IMethodSymbol>()
.Where(s => s.Name.Contains("PropertyChanged") &&
s.Parameters.Length == 1 &&
(s.Parameters[0].Type.Name == "String" ||
s.Parameters[0].Type.Name == "string"))
.FirstOrDefault();

var guessMethod = existent != null
? existent.Name
: ClassInfo.OnPropertyChangedMethodName;
return (generateEvent, guessMethod);
}

return (true, ClassInfo.OnPropertyChangedMethodName);
}

/// <summary>
/// Generate a new dictionary:
/// key => namespace.name (identical to the incoming dictionary)
Expand Down

0 comments on commit f909ae8

Please sign in to comment.