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

fix: Change so that if a event matches the standard event pattern #17

Merged
merged 3 commits into from
Jun 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 31 additions & 29 deletions src/Pharmacist.Core/Generation/Generators/DelegateGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Pharmacist.Core.Generation.XmlSyntaxFactory;

namespace Pharmacist.Core.Generation.Generators
{
/// <summary>
Expand All @@ -21,8 +24,8 @@ namespace Pharmacist.Core.Generation.Generators
/// </summary>
internal static class DelegateGenerator
{
private static readonly QualifiedNameSyntax _subjectNamespace = SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("Pharmacist"), SyntaxFactory.IdentifierName("Common"));
private static readonly GenericNameSyntax _subjectType = SyntaxFactory.GenericName(SyntaxFactory.Identifier("SingleAwaitSubject"));
private static readonly QualifiedNameSyntax _subjectNamespace = QualifiedName(IdentifierName("Pharmacist"), IdentifierName("Common"));
private static readonly GenericNameSyntax _subjectType = GenericName(Identifier("SingleAwaitSubject"));

/// <summary>
/// Generate our namespace declarations. These will contain our helper classes.
Expand All @@ -40,9 +43,8 @@ internal static IEnumerable<NamespaceDeclarationSyntax> Generate(IEnumerable<(IT

if (members.Count > 0)
{
yield return SyntaxFactory
.NamespaceDeclaration(SyntaxFactory.IdentifierName(namespaceName))
.WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>(members));
yield return NamespaceDeclaration(IdentifierName(namespaceName))
.WithMembers(List<MemberDeclarationSyntax>(members));
}
}
}
Expand All @@ -57,13 +59,13 @@ internal static IEnumerable<NamespaceDeclarationSyntax> Generate(IEnumerable<(IT
private static ClassDeclarationSyntax GenerateClass(ITypeDefinition typeDefinition, bool isAbstract, IEnumerable<IMethod> methods)
{
var modifiers = typeDefinition.IsAbstract || isAbstract
? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.AbstractKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword))
: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword));
return SyntaxFactory.ClassDeclaration(typeDefinition.Name + "Rx")
? TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.AbstractKeyword), Token(SyntaxKind.PartialKeyword))
: TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.PartialKeyword));
return ClassDeclaration(typeDefinition.Name + "Rx")
.WithModifiers(modifiers)
.WithMembers(SyntaxFactory.List(GenerateObservableMembers(methods)))
.WithBaseList(SyntaxFactory.BaseList(SyntaxFactory.SingletonSeparatedList<BaseTypeSyntax>(SyntaxFactory.SimpleBaseType(SyntaxFactory.IdentifierName(typeDefinition.GenerateFullGenericName())))))
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("Wraps delegates events from {0} into Observables.", typeDefinition.GenerateFullGenericName()))
.WithMembers(List(GenerateObservableMembers(methods)))
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(IdentifierName(typeDefinition.GenerateFullGenericName())))))
.WithLeadingTrivia(GenerateSummarySeeAlsoComment("Wraps delegates events from {0} into Observables.", typeDefinition.GenerateFullGenericName()))
.WithObsoleteAttribute(typeDefinition);
}

Expand Down Expand Up @@ -94,12 +96,12 @@ private static PropertyDeclarationSyntax GeneratePropertyDeclaration(string obse
{
// Produces:
// public System.IObservable<type> MethodNameObs => _observableName;
return SyntaxFactory.PropertyDeclaration(method.GenerateObservableTypeArguments().GenerateObservableType(), SyntaxFactory.Identifier(method.Name + "Obs"))
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
return PropertyDeclaration(method.GenerateObservableTypeArguments().GenerateObservableType(), Identifier(method.Name + "Obs"))
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
.WithObsoleteAttribute(method)
.WithExpressionBody(SyntaxFactory.ArrowExpressionClause(SyntaxFactory.IdentifierName(observableName)))
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("Gets an observable which signals when the {0} method is invoked.", method.FullName));
.WithExpressionBody(ArrowExpressionClause(IdentifierName(observableName)))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithLeadingTrivia(GenerateSummarySeeAlsoComment("Gets an observable which signals when the {0} method is invoked.", method.FullName));
}

/// <summary>
Expand All @@ -112,24 +114,24 @@ private static FieldDeclarationSyntax GenerateFieldDeclaration(string observable
{
// Produces:
// private readonly ReactiveUI.Events.SingleAwaitSubject<type> _methodName = new ReactiveUI.Events.SingleAwaitSubject<type>();
var typeName = SyntaxFactory.QualifiedName(_subjectNamespace, _subjectType.WithTypeArgumentList(method.GenerateObservableTypeArguments()));
var typeName = QualifiedName(_subjectNamespace, _subjectType.WithTypeArgumentList(method.GenerateObservableTypeArguments()));

return SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration(typeName)
return FieldDeclaration(VariableDeclaration(typeName)
.WithVariables(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(observableName))
SingletonSeparatedList(
VariableDeclarator(Identifier(observableName))
.WithInitializer(
SyntaxFactory.EqualsValueClause(
SyntaxFactory.ObjectCreationExpression(typeName).WithArgumentList(SyntaxFactory.ArgumentList()))))))
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)));
EqualsValueClause(
ObjectCreationExpression(typeName).WithArgumentList(ArgumentList()))))))
.WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)));
}

private static MethodDeclarationSyntax GenerateMethodDeclaration(string observableName, IMethod method)
{
// Produces:
// /// <inheritdoc />
// public override void MethodName(params..) => _methodName.OnNext(...);
var methodBody = SyntaxFactory.InvocationExpression(SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName(observableName), SyntaxFactory.IdentifierName("OnNext")));
var methodBody = InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(observableName), IdentifierName("OnNext")));

var methodParameterList = method.GenerateMethodParameters();

Expand All @@ -152,13 +154,13 @@ private static MethodDeclarationSyntax GenerateMethodDeclaration(string observab
methodBody = methodBody.WithArgumentList(RoslynHelpers.ReactiveUnitArgumentList);
}

return SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), method.Name)
.WithExpressionBody(SyntaxFactory.ArrowExpressionClause(methodBody))
return MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), method.Name)
.WithExpressionBody(ArrowExpressionClause(methodBody))
.WithParameterList(methodParameterList)
.WithObsoleteAttribute(method)
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.OverrideKeyword)))
.WithLeadingTrivia(XmlSyntaxFactory.InheritdocSyntax)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)))
.WithLeadingTrivia(InheritdocSyntax)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
}
}
}
85 changes: 47 additions & 38 deletions src/Pharmacist.Core/Generation/Generators/EventGeneratorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Pharmacist.Core.Generation.XmlSyntaxFactory;

namespace Pharmacist.Core.Generation.Generators
{
/// <summary>
Expand Down Expand Up @@ -48,79 +51,85 @@ protected static PropertyDeclarationSyntax GenerateEventWrapperObservable(IEvent
}

SyntaxTokenList modifiers = eventDetails.IsStatic
? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword))
: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword));
? TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword))
: TokenList(Token(SyntaxKind.PublicKeyword));

// Produces for static: public static global::System.IObservable<(argType1, argType2)> EventName => (contents of expression body)
// Produces for instance: public global::System.IObservable<(argType1, argType2)> EventName => (contents of expression body)
return SyntaxFactory.PropertyDeclaration(observableEventArgType, prefix + eventDetails.Name)
return PropertyDeclaration(observableEventArgType, prefix + eventDetails.Name)
.WithModifiers(modifiers)
.WithExpressionBody(expressionBody)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithObsoleteAttribute(eventDetails)
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("Gets an observable which signals when the {0} event triggers.", eventDetails.FullName));
.WithLeadingTrivia(GenerateSummarySeeAlsoComment("Gets an observable which signals when the {0} event triggers.", eventDetails.FullName));
}

private static (ArrowExpressionClauseSyntax, TypeSyntax) GenerateFromEventExpression(IEvent eventDetails, IMethod invokeMethod, string dataObjectName)
{
var returnType = SyntaxFactory.IdentifierName(eventDetails.ReturnType.GenerateFullGenericName());
var returnType = IdentifierName(eventDetails.ReturnType.GenerateFullGenericName());

ArgumentListSyntax methodParametersArgumentList;
TypeSyntax eventArgsType;

// If we have any members call our observables with the parameters.
if (invokeMethod.Parameters.Count > 0)
// If we are using a standard approach of using 2 parameters only send the "Value", not the sender.
if (invokeMethod.Parameters.Count == 2 && invokeMethod.Parameters[0].Type.FullName == "System.Object")
{
methodParametersArgumentList = invokeMethod.Parameters[1].GenerateArgumentList();
eventArgsType = IdentifierName(invokeMethod.Parameters[1].Type.GenerateFullGenericName());
}
else if (invokeMethod.Parameters.Count > 0)
{
// If we have any members call our observables with the parameters.
// If we have only one member, produces arguments: (arg1);
// If we have greater than one member, produces arguments with value type: ((arg1, arg2))
methodParametersArgumentList = invokeMethod.Parameters.Count == 1 ? invokeMethod.Parameters[0].GenerateArgumentList() : invokeMethod.Parameters.GenerateTupleArgumentList();
eventArgsType = invokeMethod.Parameters.Count == 1 ? SyntaxFactory.IdentifierName(invokeMethod.Parameters[0].Type.GenerateFullGenericName()) : invokeMethod.Parameters.Select(x => x.Type).GenerateTupleType();
eventArgsType = invokeMethod.Parameters.Count == 1 ? IdentifierName(invokeMethod.Parameters[0].Type.GenerateFullGenericName()) : invokeMethod.Parameters.Select(x => (x.Type, x.Name)).GenerateTupleType();
}
else
{
// Produces argument: (global::System.Reactive.Unit.Default)
methodParametersArgumentList = RoslynHelpers.ReactiveUnitArgumentList;
eventArgsType = SyntaxFactory.IdentifierName(RoslynHelpers.ObservableUnitName);
eventArgsType = IdentifierName(RoslynHelpers.ObservableUnitName);
}

var eventName = eventDetails.Name;

// Produces local function: void Handler(DataType1 eventParam1, DataType2 eventParam2) => eventHandler(eventParam1, eventParam2)
var localFunctionExpression = SyntaxFactory.LocalFunctionStatement(
SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)),
SyntaxFactory.Identifier("Handler"))
var localFunctionExpression = LocalFunctionStatement(
PredefinedType(Token(SyntaxKind.VoidKeyword)),
Identifier("Handler"))
.WithParameterList(invokeMethod.GenerateMethodParameters())
.WithExpressionBody(
SyntaxFactory.ArrowExpressionClause(
SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName("eventHandler"))
ArrowExpressionClause(
InvocationExpression(IdentifierName("eventHandler"))
.WithArgumentList(methodParametersArgumentList)))
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));

// Produces lambda expression: eventHandler => (local function above); return Handler;
var conversionLambdaExpression = SyntaxFactory.SimpleLambdaExpression(
SyntaxFactory.Parameter(SyntaxFactory.Identifier("eventHandler")),
SyntaxFactory.Block(localFunctionExpression, SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName("Handler"))));
var conversionLambdaExpression = SimpleLambdaExpression(
Parameter(Identifier("eventHandler")),
Block(localFunctionExpression, ReturnStatement(IdentifierName("Handler"))));

// Produces type parameters: <EventArg1Type, EventArg2Type>
var fromEventTypeParameters = SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList<TypeSyntax>(new SyntaxNodeOrToken[] { returnType, SyntaxFactory.Token(SyntaxKind.CommaToken), eventArgsType }));
var fromEventTypeParameters = TypeArgumentList(SeparatedList<TypeSyntax>(new SyntaxNodeOrToken[] { returnType, Token(SyntaxKind.CommaToken), eventArgsType }));

// Produces: => global::System.Reactive.Linq.Observable.FromEvent<TypeParameters>(h => (handler from above), x => x += DataObject.Event, x => x -= DataObject.Event);
var expression = SyntaxFactory.ArrowExpressionClause(
SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
var expression = ArrowExpressionClause(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("global::System.Reactive.Linq.Observable"),
SyntaxFactory.GenericName(SyntaxFactory.Identifier("FromEvent"))
IdentifierName("global::System.Reactive.Linq.Observable"),
GenericName(Identifier("FromEvent"))
.WithTypeArgumentList(fromEventTypeParameters)))
.WithArgumentList(
SyntaxFactory.ArgumentList(
SyntaxFactory.SeparatedList<ArgumentSyntax>(
ArgumentList(
SeparatedList<ArgumentSyntax>(
new SyntaxNodeOrToken[]
{
SyntaxFactory.Argument(conversionLambdaExpression),
SyntaxFactory.Token(SyntaxKind.CommaToken),
Argument(conversionLambdaExpression),
Token(SyntaxKind.CommaToken),
GenerateArgumentEventAccessor(SyntaxKind.AddAssignmentExpression, eventName, dataObjectName),
SyntaxFactory.Token(SyntaxKind.CommaToken),
Token(SyntaxKind.CommaToken),
GenerateArgumentEventAccessor(SyntaxKind.SubtractAssignmentExpression, eventName, dataObjectName)
}))));

Expand All @@ -130,16 +139,16 @@ private static (ArrowExpressionClauseSyntax, TypeSyntax) GenerateFromEventExpres
private static ArgumentSyntax GenerateArgumentEventAccessor(SyntaxKind accessor, string eventName, string dataObjectName)
{
// This produces "x => dataObject.EventName += x" and also "x => dataObject.EventName -= x" depending on the accessor passed in.
return SyntaxFactory.Argument(
SyntaxFactory.SimpleLambdaExpression(
SyntaxFactory.Parameter(SyntaxFactory.Identifier("x")),
SyntaxFactory.AssignmentExpression(
return Argument(
SimpleLambdaExpression(
Parameter(Identifier("x")),
AssignmentExpression(
accessor,
SyntaxFactory.MemberAccessExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName(dataObjectName),
SyntaxFactory.IdentifierName(eventName)),
SyntaxFactory.IdentifierName("x"))));
IdentifierName(dataObjectName),
IdentifierName(eventName)),
IdentifierName("x"))));
}
}
}