Skip to content

Commit

Permalink
Individual generation now working for all files based on Events() gen…
Browse files Browse the repository at this point in the history
…eric hook
  • Loading branch information
glennawatson committed Sep 24, 2020
1 parent 3ffdc5d commit a1aaf97
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 247 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,126 +27,174 @@ namespace ReactiveMarbles.ObservableEvents.SourceGenerator
[Generator]
public class EventGeneratorHook : ISourceGenerator
{
private const string AttributeText = @"// <auto-generated />
private const string ExtensionMethodText = @"
// <auto-generated />
using System;
namespace ReactiveMarbles.ObservableEvents
{
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct)]
public class TypeEventsToObservablesAttribute : Attribute
internal static partial class ObservableGeneratorExtensions
{
public TypeEventsToObservablesAttribute()
[System.Obsolete(""There are no events on your type and therefore Events() is not available."", false)]
public static NullEvents Events<T>(this T eventHost)
{
return default(NullEvents);
}
}
public TypeEventsToObservablesAttribute(Type type)
{
Type = type;
}
public Type Type { get; }
internal struct NullEvents
{
}
}";

private static InstanceEventGenerator _eventGenerator = new InstanceEventGenerator();
private static StaticEventGenerator _staticEventGenerator = new StaticEventGenerator();

private static SymbolDisplayFormat _symbolDisplayFormat = new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);

/// <inheritdoc />
public void Execute(GeneratorExecutionContext context)
{
// add the attribute text.
context.AddSource("EventsToObservablesAttribute.SourceGenerated.cs", SourceText.From(AttributeText, Encoding.UTF8));

// retreive the populated receiver.
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
{
return;
}
context.AddSource("TestExtensions.SourceGenerated.cs", SourceText.From(ExtensionMethodText, Encoding.UTF8));

var options = (context.Compilation as CSharpCompilation)?.SyntaxTrees[0]?.Options as CSharpParseOptions;
var compilation = context.Compilation;
var options = (compilation as CSharpCompilation)?.SyntaxTrees[0].Options as CSharpParseOptions;
compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(ExtensionMethodText, Encoding.UTF8), options));

var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(AttributeText, Encoding.UTF8), options));
var methodInvocationExtensions = new List<MethodDeclarationSyntax>();

GetAvailableTypes(receiver, compilation, out var instanceNamespaceList, out var staticNamespaceList);
GetAvailableTypes(compilation, out var instanceNamespaceList, out var staticNamespaceList);

foreach (var namespaceItem in staticNamespaceList)
{
GenerateEvents(context, _staticEventGenerator, namespaceItem.Key, "Static", namespaceItem.Value);
}
GenerateEvents(context, _staticEventGenerator, true, staticNamespaceList);
GenerateEvents(context, _eventGenerator, false, instanceNamespaceList, methodInvocationExtensions);

foreach (var namespaceItem in instanceNamespaceList)
{
GenerateEvents(context, _eventGenerator, namespaceItem.Key, "Instance", namespaceItem.Value);
}
GenerateEventExtensionMethods(context, methodInvocationExtensions);
}

/// <inheritdoc />
public void Initialize(GeneratorInitializationContext context)
{
// Register a syntax receiver that will be created for each generation pass
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}

private static void GetAvailableTypes(SyntaxReceiver receiver, Compilation compilation, out SortedDictionary<string, List<INamedTypeSymbol>> instanceNamespaceList, out SortedDictionary<string, List<INamedTypeSymbol>> staticNamespaceList)
private static void GenerateEventExtensionMethods(GeneratorExecutionContext context, List<MethodDeclarationSyntax> methodInvocationExtensions)
{
var classDeclaration = ClassDeclaration("ObservableGeneratorExtensions")
.WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.PartialKeyword)))
.WithMembers(List<MemberDeclarationSyntax>(methodInvocationExtensions));

var namespaceDeclaration = NamespaceDeclaration(IdentifierName("ReactiveMarbles.ObservableEvents"))
.WithMembers(SingletonList<MemberDeclarationSyntax>(classDeclaration))
.WithLeadingTrivia(XmlSyntaxFactory.GenerateDocumentationString("<auto-generated />"));

var compilationUnit = CompilationUnit().WithMembers(SingletonList<MemberDeclarationSyntax>(namespaceDeclaration));

context.AddSource("TestExtensions.FoundEvents.SourceGenerated.cs", SourceText.From(compilationUnit.NormalizeWhitespace().ToFullString(), Encoding.UTF8));
}

private static void GetAvailableTypes(Compilation compilation, out List<INamedTypeSymbol> instanceNamespaceList, out List<INamedTypeSymbol> staticNamespaceList)
{
instanceNamespaceList = new SortedDictionary<string, List<INamedTypeSymbol>>();
staticNamespaceList = new SortedDictionary<string, List<INamedTypeSymbol>>();
for (int i = 0; i < receiver.CandidateTypes.Count; ++i)
var candidates = new List<INamedTypeSymbol>();

GetEventMethodCallTypes(compilation, candidates);

instanceNamespaceList = new List<INamedTypeSymbol>(candidates.Count);
staticNamespaceList = new List<INamedTypeSymbol>(candidates.Count);
foreach (var candidate in candidates)
{
var candidate = receiver.CandidateTypes[i];
var list = candidate.IsStatic ? staticNamespaceList : instanceNamespaceList;

var rootSymbol = compilation.GetSemanticModel(candidate.SyntaxTree)?.GetDeclaredSymbol(candidate);
list.Add(candidate);
}
}

private static void GetEventMethodCallTypes(Compilation compilation, List<INamedTypeSymbol> candidates)
{
var observableGeneratorExtensions = compilation.GetTypeByMetadataName("ReactiveMarbles.ObservableEvents.ObservableGeneratorExtensions")!;

if (rootSymbol == null)
foreach (var tree in compilation.SyntaxTrees)
{
var semanticModel = compilation.GetSemanticModel(tree);

foreach (var invocation in tree.GetRoot().DescendantNodesAndSelf().OfType<InvocationExpressionSyntax>())
{
continue;
}
var methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol;

var namespaceDictionary = rootSymbol.IsStatic ? staticNamespaceList : instanceNamespaceList;
if (methodSymbol == null)
{
continue;
}

var symbolList = rootSymbol.GetBaseTypesAndThis(RoslynHelpers.HasEvents);
if (!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, observableGeneratorExtensions))
{
continue;
}

foreach (var symbol in symbolList)
{
if (!namespaceDictionary.TryGetValue(symbol.ContainingNamespace.Name, out var list))
if (methodSymbol.TypeArguments.Length != 1)
{
list = new List<INamedTypeSymbol>();
namespaceDictionary[symbol.ContainingNamespace.ToDisplayString(_symbolDisplayFormat)] = list;
continue;
}

int index = list.BinarySearch(symbol, TypeDefinitionNameComparer.Default);
var callingSymbol = methodSymbol.TypeArguments[0] as INamedTypeSymbol;

if (index < 0)
if (callingSymbol == null)
{
list.Insert(~index, symbol);
continue;
}

candidates.Add(callingSymbol);
}
}
}

private static void GenerateEvents(GeneratorExecutionContext context, IEventSymbolGenerator symbolGenerator, string namespaceName, string suffixName, List<INamedTypeSymbol> symbols)
private static bool GenerateEvents(GeneratorExecutionContext context, IEventSymbolGenerator symbolGenerator, bool isStatic, IReadOnlyList<INamedTypeSymbol> symbols, List<MethodDeclarationSyntax>? methodInvocationExtensions = null)
{
var namespaceSyntax = symbolGenerator.Generate(namespaceName, symbols);
var processingStack = new Stack<INamedTypeSymbol>(symbols);
var processedItems = new HashSet<INamedTypeSymbol>(TypeDefinitionNameComparer.Default);

if (namespaceSyntax == null)
var fileType = isStatic ? "Static" : "Instance";

while (processingStack.Count != 0)
{
return;
}
var item = processingStack.Pop();

if (processedItems.Contains(item))
{
continue;
}

processedItems.Add(item);

var compilationUnit = CompilationUnit().WithMembers(SingletonList<MemberDeclarationSyntax>(namespaceSyntax))
.WithLeadingTrivia(
XmlSyntaxFactory.GenerateDocumentationString(
"<auto-generated />"));
var baseClassWithEvents = item.GetBasesWithCondition(RoslynHelpers.HasEvents).ToList();

var name = $"SourceClass{namespaceName}{suffixName}.SourceGenerated.cs";
var alwaysGenerate = symbols.Contains(item) && (baseClassWithEvents.Count != 0 || item.GetMembers<IEventSymbol>().Any());

var sourceText = compilationUnit.NormalizeWhitespace().ToFullString();
context.AddSource(
name,
SourceText.From(sourceText, Encoding.UTF8));
var namespaceItem = symbolGenerator.Generate(item, alwaysGenerate);

foreach (var childItem in baseClassWithEvents)
{
processingStack.Push(childItem);
}

if (namespaceItem == null)
{
continue;
}

var compilationUnit = CompilationUnit().WithMembers(SingletonList<MemberDeclarationSyntax>(namespaceItem))
.WithLeadingTrivia(
XmlSyntaxFactory.GenerateDocumentationString(
"<auto-generated />"));

var sourceText = compilationUnit.NormalizeWhitespace().ToFullString();

var name = $"SourceClass{item.ToDisplayString(RoslynHelpers.SymbolDisplayFormat)}-{fileType}Events.SourceGenerated.cs";

context.AddSource(
name,
SourceText.From(sourceText, Encoding.UTF8));

methodInvocationExtensions?.Add(MethodGenerator.GenerateMethod(item));
}

File.WriteAllText(Path.Combine("C:/Temp", name), sourceText);
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace ReactiveMarbles.ObservableEvents.SourceGenerator.EventGenerators.Gener
internal abstract class EventGeneratorBase : IEventSymbolGenerator
{
/// <inheritdoc />
public abstract NamespaceDeclarationSyntax? Generate(string namespaceName, IReadOnlyList<INamedTypeSymbol> namedTypes);
public abstract NamespaceDeclarationSyntax? Generate(INamedTypeSymbol item, bool generateEmpty);

/// <summary>
/// Generates an observable declaration that wraps a event.
Expand All @@ -29,7 +29,7 @@ internal abstract class EventGeneratorBase : IEventSymbolGenerator
/// <param name="dataObjectName">The name of the item where the event is stored.</param>
/// <param name="prefix">A prefix to append to the name.</param>
/// <returns>The property declaration.</returns>
protected static PropertyDeclarationSyntax? GenerateEventWrapperObservable(IEventSymbol eventDetails, string dataObjectName, string? prefix = null)
protected static PropertyDeclarationSyntax? GenerateEventWrapperObservable(IEventSymbol eventDetails, string dataObjectName, string? prefix)
{
prefix ??= string.Empty;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ internal interface IEventSymbolGenerator
/// <summary>
/// Generates a compilation unit based on generating event observable wrappers.
/// </summary>
/// <param name="namespaceName">The name of the namespace.</param>
/// <param name="namedTypes">The symbols to generate for.</param>
/// <param name="item">The symbol to generate for.</param>
/// <param name="generateEmpty">If this symbol should generate a empty shell regardless if it has events. Eg for inheritance.</param>
/// <returns>The new compilation unit.</returns>
NamespaceDeclarationSyntax? Generate(string namespaceName, IReadOnlyList<INamedTypeSymbol> namedTypes);
NamespaceDeclarationSyntax? Generate(INamedTypeSymbol item, bool generateEmpty);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// ReactiveUI Association Inc licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using Microsoft.CodeAnalysis;
Expand All @@ -17,56 +19,22 @@ internal class InstanceEventGenerator : EventGeneratorBase
{
private const string DataFieldName = "_data";

public override NamespaceDeclarationSyntax? Generate(string namespaceName, IReadOnlyList<INamedTypeSymbol> namedTypes)
/// <inheritdoc />
public override NamespaceDeclarationSyntax? Generate(INamedTypeSymbol item, bool generateEmpty)
{
var orderedTypeDeclarations = namedTypes.GetOrderedTypeEvents();
var namespaceName = item.ContainingNamespace.ToDisplayString(RoslynHelpers.SymbolDisplayFormat);

if (orderedTypeDeclarations.Count == 0)
{
return null;
}

var members = new List<ClassDeclarationSyntax>(orderedTypeDeclarations.Count);
var eventWrapper = GenerateEventWrapperClass(item, item.GetEvents(), generateEmpty);

members.Add(GenerateStaticClass(namespaceName, orderedTypeDeclarations));

members.AddRange(orderedTypeDeclarations.Select(GenerateEventWrapperClass).Where(x => x != null));

if (members.Count > 0)
if (eventWrapper != null)
{
return NamespaceDeclaration(IdentifierName(namespaceName))
.WithMembers(List<MemberDeclarationSyntax>(members));
return NamespaceDeclaration(IdentifierName(namespaceName))
.WithMembers(SingletonList<MemberDeclarationSyntax>(eventWrapper));
}

return null;
}

private static ClassDeclarationSyntax GenerateStaticClass(string namespaceName, IEnumerable<TypeEvents> declarations)
{
// Produces:
// public static class EventExtensions
// contents of members above
return ClassDeclaration("EventExtensions")
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("A class that contains extension methods to wrap events for classes contained within the {0} namespace.", namespaceName))
.WithMembers(List<MemberDeclarationSyntax>(declarations.Select(declaration =>
{
var eventsClassName = IdentifierName("Rx" + declaration.Type.Name + "Events");
return MethodDeclaration(eventsClassName, Identifier("Events"))
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
.WithParameterList(ParameterList(SingletonSeparatedList(
Parameter(Identifier("item"))
.WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword)))
.WithType(IdentifierName(declaration.Type.GenerateFullGenericName())))))
.WithExpressionBody(ArrowExpressionClause(
ObjectCreationExpression(eventsClassName)
.WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("item")))))))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithObsoleteAttribute(declaration.Type)
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("A wrapper class which wraps all the events contained within the {0} class.", declaration.Type.ConvertToDocument()));
})));
}

private static ConstructorDeclarationSyntax GenerateEventWrapperClassConstructor(INamedTypeSymbol typeDefinition, bool hasBaseClass)
{
const string dataParameterName = "data";
Expand Down Expand Up @@ -102,17 +70,21 @@ private static FieldDeclarationSyntax GenerateEventWrapperField(INamedTypeSymbol
.WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)));
}

private static ClassDeclarationSyntax GenerateEventWrapperClass(TypeEvents typeEvents)
private static ClassDeclarationSyntax GenerateEventWrapperClass(INamedTypeSymbol typeDefinition, IReadOnlyList<IEventSymbol> events, bool generateAlways)
{
var typeDefinition = typeEvents.Type;
var baseTypeDefinition = typeDefinition.GetBasesWithCondition(RoslynHelpers.HasEvents).FirstOrDefault();
var events = typeEvents.Events;

var members = new List<MemberDeclarationSyntax> { GenerateEventWrapperField(typeDefinition), GenerateEventWrapperClassConstructor(typeDefinition, baseTypeDefinition != null) };

foreach (var eventSymbol in typeEvents.Events)
if (!generateAlways && events.Count == 0)
{
return null;
}

for (int i = 0; i < events.Count; ++i)
{
var eventWrapper = GenerateEventWrapperObservable(eventSymbol, DataFieldName);
var eventSymbol = events[i];
var eventWrapper = GenerateEventWrapperObservable(eventSymbol, DataFieldName, null);

if (eventWrapper == null)
{
Expand All @@ -130,7 +102,7 @@ private static ClassDeclarationSyntax GenerateEventWrapperClass(TypeEvents typeE

if (baseTypeDefinition != null)
{
classDeclaration = classDeclaration.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(IdentifierName($"global::{baseTypeDefinition.ContainingNamespace.Name}.Rx{baseTypeDefinition.Name}Events")))));
classDeclaration = classDeclaration.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(IdentifierName($"global::{baseTypeDefinition.ContainingNamespace.ToDisplayString(RoslynHelpers.SymbolDisplayFormat)}.Rx{baseTypeDefinition.Name}Events")))));
}

return classDeclaration;
Expand Down
Loading

0 comments on commit a1aaf97

Please sign in to comment.