using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
using Roslyn.Services.Editor;
using RoslynCTPLibrary.Attributes;
using RoslynCTPLibrary.Extensions;
using System.Reactive.Subjects;
namespace EventImplementer {
[ExportSyntaxNodeCodeIssueProvider("EventImplementer", LanguageNames.CSharp, typeof(PropertyDeclarationSyntax))]
class CodeIssueProvider : ICodeIssueProvider {
private readonly ICodeActionEditFactory editFactory;
[ImportingConstructor]
public CodeIssueProvider(ICodeActionEditFactory editFactory) {
this.editFactory = editFactory;
}
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken) {
var property = node as PropertyDeclarationSyntax;
if (!property.Attributes.Any(ad => ad.Attributes.Any(a => a.Name.PlainName == typeof(FiresEventOnChange).Name || a.Name.PlainName == typeof(FiresEventOnQuery).Name))
|| property.GetOwnerType().Members.OfType<FieldDeclarationSyntax>().Any(f => f.Declaration.Variables.First().Identifier.ValueText == Constants.FieldPrefix + property.Identifier.ValueText))
yield break;
yield return new CodeIssue(CodeIssue.Severity.Error, property.Span, "Event needs to be implemented", new CodeAction(editFactory, document, property));
}
public class CodeAction : ICodeAction {
public string Description { get { return "Implement event"; } }
public System.Windows.Media.ImageSource Icon { get { return null; } }
ICodeActionEditFactory EditFactory;
IDocument Document;
PropertyDeclarationSyntax Property;
public CodeAction(ICodeActionEditFactory editFactory, IDocument document, PropertyDeclarationSyntax property) {
EditFactory = editFactory; Document = document; Property = property;
}
public ICodeActionEdit GetEdit(CancellationToken cancellationToken) {
var backingField = Syntax.FieldDeclaration(declaration: Syntax.VariableDeclaration(Property.Type, Syntax.SeparatedList(Syntax.VariableDeclarator(identifier: Syntax.Identifier(Constants.FieldPrefix + Property.Identifier.ValueText)))));
var getStatements = new List<StatementSyntax> { Syntax.ReturnStatement(returnKeyword: Syntax.Token(SyntaxKind.ReturnKeyword), expressionOpt: Syntax.IdentifierName(backingField.Declaration.Variables.First().Identifier)) };
var setStatements = new List<StatementSyntax> { Syntax.ExpressionStatement(Syntax.BinaryExpression(SyntaxKind.AssignExpression, Syntax.IdentifierName(backingField.Declaration.Variables.First().Identifier.ValueText),
right: Syntax.IdentifierName(Syntax.Token(SyntaxKind.ValueKeyword)))) };
var newMembers = new List<MemberDeclarationSyntax> { backingField };
Func<string, FieldDeclarationSyntax> subjectDeclaration = suffix =>
Syntax.FieldDeclaration(modifiers: Syntax.TokenList(Syntax.Token(SyntaxKind.PublicKeyword)),
declaration: Syntax.VariableDeclaration(Syntax.GenericName(Syntax.ParseToken(typeof(Subject).Name), Syntax.TypeArgumentList(arguments: Syntax.SeparatedList<TypeSyntax>(Property.Type))),
variables: Syntax.SeparatedList(Syntax.VariableDeclarator(Syntax.Identifier(Property.Identifier.ValueText + suffix),
initializerOpt: Syntax.EqualsValueClause(value: Syntax.ObjectCreationExpression(
type: Syntax.GenericName(Syntax.ParseToken(typeof(Subject).Name), Syntax.TypeArgumentList(arguments: Syntax.SeparatedList<TypeSyntax>(Property.Type))),
argumentListOpt: Syntax.ArgumentList()))))));
Func<string, ExpressionSyntax, ExpressionStatementSyntax> onNextCall = (suffix, argumentExpression) =>
Syntax.ExpressionStatement(Syntax.InvocationExpression(Syntax.MemberAccessExpression(SyntaxKind.MemberAccessExpression, Syntax.IdentifierName(Property.Identifier.ValueText + suffix),
name: Syntax.IdentifierName("OnNext")), Syntax.ArgumentList(arguments: Syntax.SeparatedList(Syntax.Argument(expression: argumentExpression)))));
if (Property.Attributes.Any(ad => ad.Attributes.Any(a => a.Name.PlainName == typeof(FiresEventOnQuery).Name))) {
newMembers.Add(subjectDeclaration(Constants.QueriedSuffix));
getStatements.Insert(0, onNextCall(Constants.QueriedSuffix, Syntax.IdentifierName(backingField.Declaration.Variables.First().Identifier.ValueText)));
}
if (Property.Attributes.Any(ad => ad.Attributes.Any(a => a.Name.PlainName == typeof(FiresEventOnChange).Name))) {
newMembers.Add(subjectDeclaration(Constants.ChangedSuffix));
setStatements.Add(onNextCall(Constants.ChangedSuffix, Syntax.IdentifierName(Syntax.Token(SyntaxKind.ValueKeyword))));
}
newMembers.Add(Property.Update(Property.Attributes, Property.Modifiers, Property.Type, Property.ExplicitInterfaceSpecifierOpt, Property.Identifier,
Syntax.AccessorList(accessors: Syntax.List<AccessorDeclarationSyntax>(
Syntax.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, bodyOpt: Syntax.Block(statements: Syntax.List<StatementSyntax>(getStatements))),
Syntax.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, bodyOpt: Syntax.Block(statements: Syntax.List<StatementSyntax>(setStatements)))
))));
var typeDecl = Property.GetOwnerType();
var propertyIndex = typeDecl.Members.IndexOf(Property);
var newtypeDecl = Syntax.TypeDeclaration(typeDecl.Kind, typeDecl.Attributes, typeDecl.Modifiers, typeDecl.Keyword, typeDecl.Identifier, typeDecl.TypeParameterListOpt, typeDecl.BaseListOpt, typeDecl.ConstraintClauses, typeDecl.OpenBraceToken,
Syntax.List(typeDecl.Members.Take(propertyIndex).Concat(newMembers.AsEnumerable().Reverse()).Concat(typeDecl.Members.Skip(propertyIndex + 1))), typeDecl.CloseBraceToken, typeDecl.SemicolonTokenOpt).Format();
return EditFactory.CreateTreeTransformEdit(Document.Project.Solution, Document.GetSyntaxTree(), ((SyntaxNode)Document.GetSyntaxTree().Root).ReplaceNode(typeDecl, newtypeDecl));
}
}
#region Unimplemented ICodeIssueProvider members
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken) {
throw new NotImplementedException();
}
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxTrivia trivia, CancellationToken cancellationToken) {
throw new NotImplementedException();
}
#endregion
}
}