Skip to content
This repository has been archived by the owner on Jan 16, 2022. It is now read-only.

Commit

Permalink
#76: Refactoring: Extract implementors from MappingGeneratorRefactori…
Browse files Browse the repository at this point in the history
…ng. Move MappingGeneratorRefactoring to the dedicated subfolder.
  • Loading branch information
cezarypiatek committed Apr 29, 2019
1 parent 83d8d90 commit 7a603f5
Show file tree
Hide file tree
Showing 15 changed files with 374 additions and 298 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using MappingGenerator.Features.Refactorings.Mapping;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeRefactorings;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MappingGenerator.Features.Refactorings.Mapping.MappingImplementors;
using MappingGenerator.MethodHelpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;

namespace MappingGenerator.Features.Refactorings.Mapping
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(MappingGeneratorRefactoring)), Shared]
public class MappingGeneratorRefactoring : CodeRefactoringProvider
{
private const string title = "Generate mapping code";

public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span);

await TryToRegisterRefactoring(context, node);
}

private async Task TryToRegisterRefactoring(CodeRefactoringContext context, SyntaxNode node)
{
switch (node)
{
case BaseMethodDeclarationSyntax methodDeclaration when IsMappingMethodCandidate(methodDeclaration):
if (methodDeclaration.Parent.Kind() != SyntaxKind.InterfaceDeclaration )
{
var semanticModel = await context.Document.GetSemanticModelAsync();
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration);

if (IsCompleteMethodDeclarationSymbol(methodSymbol) == false)
{
return;
}

if (CanProvideMappingImplementationFor(methodSymbol))
{
var generateMappingAction = CodeAction.Create(title: title, createChangedDocument: async (c) => await GenerateMappingMethodBody(context.Document, methodDeclaration, c), equivalenceKey: title);
context.RegisterRefactoring(generateMappingAction);
}
}
break;
case IdentifierNameSyntax _:
case ParameterListSyntax _:
await TryToRegisterRefactoring(context, node.Parent);
break;
}
}

private static bool IsMappingMethodCandidate(BaseMethodDeclarationSyntax methodDeclaration)
{
if (methodDeclaration is MethodDeclarationSyntax)
{
return true;
}

if (methodDeclaration is ConstructorDeclarationSyntax constructorDeclaration && constructorDeclaration.ParameterList.Parameters.Count >= 1)
{
return true;
}

return false;
}

private static bool IsCompleteMethodDeclarationSymbol(IMethodSymbol methodSymbol)
{
var allParametersHaveNames = methodSymbol.Parameters.All(x => string.IsNullOrWhiteSpace(x.Name) == false);
return allParametersHaveNames;
}

private bool CanProvideMappingImplementationFor(IMethodSymbol methodSymbol)
{
return this.implementors.Any(x => x.CanImplement(methodSymbol));
}

private async Task<Document> GenerateMappingMethodBody(Document document, BaseMethodDeclarationSyntax methodSyntax, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var methodSymbol = semanticModel.GetDeclaredSymbol(methodSyntax);
var generator = SyntaxGenerator.GetGenerator(document);
var mappingExpressions = GenerateMappingCode(methodSymbol, generator, semanticModel);
var blockSyntax = SyntaxFactory.Block(mappingExpressions.Select(e => e.AsStatement())).WithAdditionalAnnotations(Formatter.Annotation);
return await document.ReplaceNodes(methodSyntax, methodSyntax.WithOnlyBody(blockSyntax), cancellationToken);
}

private IEnumerable<SyntaxNode> GenerateMappingCode(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var matchedImplementor = implementors.FirstOrDefault(x => x.CanImplement(methodSymbol));
if (matchedImplementor != null)
{
return matchedImplementor.GenerateImplementation(methodSymbol, generator, semanticModel);
}
return Enumerable.Empty<SyntaxNode>();
}

private readonly IReadOnlyList<IMappingMethodImplementor> implementors = new List<IMappingMethodImplementor>()
{
new IdentityMappingMethodImplementor(),
new SingleParameterPureMappingMethodImplementor(),
new MultiParameterPureMappingMethodImplementor(),
new UpdateSecondParameterMappingMethodImplementor(),
new UpdateThisObjectSingleParameterMappingMethodImplementor(),
new UpdateThisObjectMultiParameterMappingMethodImplementor(),
new SingleParameterMappingConstructorImplementor(),
new MultiParameterMappingConstructorImplementor(),
new ThisObjectToOtherMappingMethodImplementor()
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
public interface IMappingMethodImplementor
{
bool CanImplement(IMethodSymbol methodSymbol);

IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
internal class IdentityMappingMethodImplementor: IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
return methodSymbol.Parameters.Length == 1 && methodSymbol.ReturnType.Equals(methodSymbol.Parameters[0].Type);
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var cloneMappingEngine = new CloneMappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var source = methodSymbol.Parameters[0];
var targetType = methodSymbol.ReturnType;
var newExpression = cloneMappingEngine.MapExpression((ExpressionSyntax)generator.IdentifierName(source.Name),
source.Type, targetType);
return new[] { generator.ReturnStatement(newExpression).WithAdditionalAnnotations(Formatter.Annotation) };
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
class MultiParameterMappingConstructorImplementor:IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
return methodSymbol.Parameters.Length > 1 && SymbolHelper.IsConstructor(methodSymbol);
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var sourceFinder = new LocalScopeMappingSourceFinder(semanticModel, methodSymbol.Parameters);
var targets = ObjectHelper.GetFieldsThaCanBeSetFromConstructor(methodSymbol.ContainingType);
return mappingEngine.MapUsingSimpleAssignment(generator, targets, sourceFinder);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
class MultiParameterPureMappingMethodImplementor: IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
return methodSymbol.Parameters.Length > 1 && methodSymbol.ReturnsVoid == false;
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var targetType = methodSymbol.ReturnType;
var sourceFinder = new LocalScopeMappingSourceFinder(semanticModel, methodSymbol);
var newExpression = mappingEngine.AddInitializerWithMapping(
(ObjectCreationExpressionSyntax)generator.ObjectCreationExpression(targetType), sourceFinder, targetType);
return new[] { generator.ReturnStatement(newExpression).WithAdditionalAnnotations(Formatter.Annotation) };
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
class SingleParameterMappingConstructorImplementor:IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
return methodSymbol.Parameters.Length == 1 && SymbolHelper.IsConstructor(methodSymbol);
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var source = methodSymbol.Parameters[0];
var sourceFinder = new ObjectMembersMappingSourceFinder(source.Type, generator.IdentifierName(source.Name), generator);
var targets = ObjectHelper.GetFieldsThaCanBeSetFromConstructor(methodSymbol.ContainingType);
return mappingEngine.MapUsingSimpleAssignment(generator, targets, sourceFinder);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
internal class SingleParameterPureMappingMethodImplementor: IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
return methodSymbol.Parameters.Length == 1 && methodSymbol.ReturnsVoid == false;
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var source = methodSymbol.Parameters[0];
var targetType = methodSymbol.ReturnType;
var newExpression = mappingEngine.MapExpression((ExpressionSyntax)generator.IdentifierName(source.Name),
source.Type, targetType);
return new[] { generator.ReturnStatement(newExpression).WithAdditionalAnnotations(Formatter.Annotation) };
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
class ThisObjectToOtherMappingMethodImplementor:IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
return methodSymbol.IsStatic == false &&
methodSymbol.Parameters.Length == 0 &&
methodSymbol.ReturnsVoid == false &&
ObjectHelper.IsSimpleType(methodSymbol.ReturnType) == false;
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var targetType = methodSymbol.ReturnType;
var newExpression = mappingEngine.MapExpression((ExpressionSyntax)generator.ThisExpression(), methodSymbol.ContainingType, targetType);
return new[] { generator.ReturnStatement(newExpression).WithAdditionalAnnotations(Formatter.Annotation) };
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
class UpdateSecondParameterMappingMethodImplementor:IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
if (SymbolHelper.IsConstructor(methodSymbol))
{
return false;
}
return methodSymbol.Parameters.Length == 2 && methodSymbol.ReturnsVoid;
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var source = methodSymbol.Parameters[0];
var target = methodSymbol.Parameters[1];
var targets = ObjectHelper.GetFieldsThaCanBeSetPublicly(target.Type, methodSymbol.ContainingAssembly);
var sourceFinder = new ObjectMembersMappingSourceFinder(source.Type, generator.IdentifierName(source.Name), generator);
return mappingEngine.MapUsingSimpleAssignment(generator, targets, sourceFinder, globalTargetAccessor: generator.IdentifierName(target.Name));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
class UpdateThisObjectMultiParameterMappingMethodImplementor:IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
if (SymbolHelper.IsConstructor(methodSymbol))
{
return false;
}
return methodSymbol.Parameters.Length > 1 && methodSymbol.ReturnsVoid;
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var sourceFinder = new LocalScopeMappingSourceFinder(semanticModel, methodSymbol.Parameters);
var targets = ObjectHelper.GetFieldsThaCanBeSetPrivately(methodSymbol.ContainingType);
return mappingEngine.MapUsingSimpleAssignment(generator, targets, sourceFinder);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;

namespace MappingGenerator.Features.Refactorings.Mapping.MappingImplementors
{
class UpdateThisObjectSingleParameterMappingMethodImplementor:IMappingMethodImplementor
{
public bool CanImplement(IMethodSymbol methodSymbol)
{
if (SymbolHelper.IsConstructor(methodSymbol))
{
return false;
}
return methodSymbol.Parameters.Length == 1 && methodSymbol.ReturnsVoid;
}

public IEnumerable<SyntaxNode> GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel)
{
var mappingEngine = new MappingEngine(semanticModel, generator, methodSymbol.ContainingAssembly);
var source = methodSymbol.Parameters[0];
var sourceFinder = new ObjectMembersMappingSourceFinder(source.Type, generator.IdentifierName(source.Name), generator);
var targets = ObjectHelper.GetFieldsThaCanBeSetPrivately(methodSymbol.ContainingType);
return mappingEngine.MapUsingSimpleAssignment(generator, targets, sourceFinder);
}
}
}
Loading

0 comments on commit 7a603f5

Please sign in to comment.