From 7a603f53ae79d07de621fefd210a6f296bed158c Mon Sep 17 00:00:00 2001 From: Cezary Piatek Date: Sat, 27 Apr 2019 09:30:21 +0200 Subject: [PATCH] #76: Refactoring: Extract implementors from MappingGeneratorRefactoring. Move MappingGeneratorRefactoring to the dedicated subfolder. --- .../MappingGenerator/MappingGeneratorTests.cs | 1 + .../Mapping/MappingGeneratorRefactoring.cs | 119 +++++++++ .../IMappingMethodImplementor.cs | 13 + .../IdentityMappingMethodImplementor.cs | 26 ++ ...iParameterMappingConstructorImplementor.cs | 22 ++ ...tiParameterPureMappingMethodImplementor.cs | 26 ++ ...eParameterMappingConstructorImplementor.cs | 23 ++ ...leParameterPureMappingMethodImplementor.cs | 26 ++ ...isObjectToOtherMappingMethodImplementor.cs | 27 ++ ...SecondParameterMappingMethodImplementor.cs | 28 +++ ...tMultiParameterMappingMethodImplementor.cs | 26 ++ ...SingleParameterMappingMethodImplementor.cs | 27 ++ .../MappingGeneratorRefactoring.cs | 237 ------------------ .../MappingGenerator/SymbolHelper.cs | 62 +---- .../MappingGenerator/SyntaxHelper.cs | 9 + 15 files changed, 374 insertions(+), 298 deletions(-) create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingGeneratorRefactoring.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IMappingMethodImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IdentityMappingMethodImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterMappingConstructorImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterPureMappingMethodImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterMappingConstructorImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterPureMappingMethodImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/ThisObjectToOtherMappingMethodImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateSecondParameterMappingMethodImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectMultiParameterMappingMethodImplementor.cs create mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectSingleParameterMappingMethodImplementor.cs delete mode 100644 MappingGenerator/MappingGenerator/MappingGenerator/MappingGeneratorRefactoring.cs diff --git a/MappingGenerator/MappingGenerator/MappingGenerator.Test/MappingGenerator/MappingGeneratorTests.cs b/MappingGenerator/MappingGenerator/MappingGenerator.Test/MappingGenerator/MappingGeneratorTests.cs index 154f8e6..10a0579 100644 --- a/MappingGenerator/MappingGenerator/MappingGenerator.Test/MappingGenerator/MappingGeneratorTests.cs +++ b/MappingGenerator/MappingGenerator/MappingGenerator.Test/MappingGenerator/MappingGeneratorTests.cs @@ -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; diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingGeneratorRefactoring.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingGeneratorRefactoring.cs new file mode 100644 index 0000000..768d828 --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingGeneratorRefactoring.cs @@ -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 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 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(); + } + + private readonly IReadOnlyList implementors = new List() + { + new IdentityMappingMethodImplementor(), + new SingleParameterPureMappingMethodImplementor(), + new MultiParameterPureMappingMethodImplementor(), + new UpdateSecondParameterMappingMethodImplementor(), + new UpdateThisObjectSingleParameterMappingMethodImplementor(), + new UpdateThisObjectMultiParameterMappingMethodImplementor(), + new SingleParameterMappingConstructorImplementor(), + new MultiParameterMappingConstructorImplementor(), + new ThisObjectToOtherMappingMethodImplementor() + }; + } +} diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IMappingMethodImplementor.cs new file mode 100644 index 0000000..1bd7c6e --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IMappingMethodImplementor.cs @@ -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 GenerateImplementation(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel); + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IdentityMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IdentityMappingMethodImplementor.cs new file mode 100644 index 0000000..6fe8a46 --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/IdentityMappingMethodImplementor.cs @@ -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 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) }; + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterMappingConstructorImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterMappingConstructorImplementor.cs new file mode 100644 index 0000000..3c004fb --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterMappingConstructorImplementor.cs @@ -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 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); + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterPureMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterPureMappingMethodImplementor.cs new file mode 100644 index 0000000..3344889 --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/MultiParameterPureMappingMethodImplementor.cs @@ -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 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) }; + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterMappingConstructorImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterMappingConstructorImplementor.cs new file mode 100644 index 0000000..37a799c --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterMappingConstructorImplementor.cs @@ -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 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); + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterPureMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterPureMappingMethodImplementor.cs new file mode 100644 index 0000000..7134c1e --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/SingleParameterPureMappingMethodImplementor.cs @@ -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 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) }; + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/ThisObjectToOtherMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/ThisObjectToOtherMappingMethodImplementor.cs new file mode 100644 index 0000000..2e45ee9 --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/ThisObjectToOtherMappingMethodImplementor.cs @@ -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 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) }; + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateSecondParameterMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateSecondParameterMappingMethodImplementor.cs new file mode 100644 index 0000000..03cbaca --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateSecondParameterMappingMethodImplementor.cs @@ -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 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)); + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectMultiParameterMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectMultiParameterMappingMethodImplementor.cs new file mode 100644 index 0000000..ca0c0ba --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectMultiParameterMappingMethodImplementor.cs @@ -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 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); + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectSingleParameterMappingMethodImplementor.cs b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectSingleParameterMappingMethodImplementor.cs new file mode 100644 index 0000000..8a98920 --- /dev/null +++ b/MappingGenerator/MappingGenerator/MappingGenerator/Features/Refactorings/Mapping/MappingImplementors/UpdateThisObjectSingleParameterMappingMethodImplementor.cs @@ -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 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); + } + } +} \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/MappingGeneratorRefactoring.cs b/MappingGenerator/MappingGenerator/MappingGenerator/MappingGeneratorRefactoring.cs deleted file mode 100644 index 7108a8c..0000000 --- a/MappingGenerator/MappingGenerator/MappingGenerator/MappingGeneratorRefactoring.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System.Composition; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -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 -{ - [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 MethodDeclarationSyntax methodDeclaration: - if (methodDeclaration.Parent.Kind() != SyntaxKind.InterfaceDeclaration ) - { - var semanticModel = await context.Document.GetSemanticModelAsync(); - var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration); - - if (IsCompleteMethodDeclarationSymbol(methodSymbol) == false) - { - return; - } - - if (IsMappingMethod(methodSymbol)) - { - context.RegisterRefactoring(CodeAction.Create(title: title, createChangedDocument: c => GenerateMappingMethodBody(context.Document, methodDeclaration, c), equivalenceKey: title)); - - } - } - break; - case ConstructorDeclarationSyntax constructorDeclaration: - if (IsMappingConstructor(constructorDeclaration)) - { - context.RegisterRefactoring(CodeAction.Create(title: title, createChangedDocument: c => GenerateMappingMethodBody(context.Document, constructorDeclaration, c), equivalenceKey: title)); - } - break; - case IdentifierNameSyntax _: - case ParameterListSyntax _: - await TryToRegisterRefactoring(context, node.Parent); - break; - - } - } - - private static bool IsCompleteMethodDeclarationSymbol(IMethodSymbol methodSymbol) - { - var allParameterHaveNames = methodSymbol.Parameters.All(x => string.IsNullOrWhiteSpace(x.Name) == false); - return allParameterHaveNames; - } - - private static bool IsMappingConstructor(ConstructorDeclarationSyntax constructorDeclaration) - { - return constructorDeclaration.ParameterList.Parameters.Count >= 1; - } - - private static bool IsMappingMethod(IMethodSymbol methodSymbol) - { - return SymbolHelper.IsPureMappingFunction(methodSymbol) || - SymbolHelper.IsMultiParameterPureFunction(methodSymbol) || - SymbolHelper.IsUpdateThisObjectFunction(methodSymbol) || - SymbolHelper.IsUpdateParameterFunction(methodSymbol) || - SymbolHelper.IsMultiParameterUpdateThisObjectFunction(methodSymbol) || - SymbolHelper.IsThisObjectToOtherConvert(methodSymbol); - } - - private async Task 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(AsStatement)).WithAdditionalAnnotations(Formatter.Annotation); - return await document.ReplaceNodes(methodSyntax, methodSyntax.WithOnlyBody(blockSyntax), cancellationToken); - } - - private StatementSyntax AsStatement(SyntaxNode node) - { - if (node is ExpressionSyntax expression) - return SyntaxFactory.ExpressionStatement(expression); - return (StatementSyntax)node; - } - - private static IEnumerable GenerateMappingCode(IMethodSymbol methodSymbol, SyntaxGenerator generator, SemanticModel semanticModel) - { - if (SymbolHelper.IsIdentityMappingFunction(methodSymbol)) - { - return GenerateIdentityMappingFunctionExpressions(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsPureMappingFunction(methodSymbol)) - { - return GenerateSingleParameterPureMappingFunctionExpressions(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsMultiParameterPureFunction(methodSymbol)) - { - return GenerateMultiParameterPureMappingFunctionExpressions(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsUpdateParameterFunction(methodSymbol)) - { - return GenerateUpdateSecondParameterFunctionExpressions(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsUpdateThisObjectFunction(methodSymbol)) - { - return GenerateSingleParameterUpdateThisObjectFunctionExpressions(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsMultiParameterUpdateThisObjectFunction(methodSymbol)) - { - return GenerateMultiParameterUpdateThisObjectFunctionExpressions(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsMappingConstructor(methodSymbol)) - { - return GenerateSingleParameterMappingConstructor(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsMultiParameterMappingConstructor(methodSymbol)) - { - return GenerateMultiParameterMappingConstructor(methodSymbol, generator, semanticModel); - } - - if (SymbolHelper.IsThisObjectToOtherConvert(methodSymbol)) - { - return GenerateThisObjectToOtherFunctionExpressions(methodSymbol, generator, semanticModel); - } - - return Enumerable.Empty(); - } - - private static IEnumerable GenerateThisObjectToOtherFunctionExpressions(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)}; - } - - private static IEnumerable GenerateMultiParameterMappingConstructor(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); - } - - private static IEnumerable GenerateSingleParameterMappingConstructor(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); - } - - private static IEnumerable GenerateMultiParameterUpdateThisObjectFunctionExpressions(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); - } - - private static IEnumerable GenerateSingleParameterUpdateThisObjectFunctionExpressions(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); - } - - private static IEnumerable GenerateUpdateSecondParameterFunctionExpressions(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)); - } - - private static IEnumerable GenerateMultiParameterPureMappingFunctionExpressions(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)}; - } - - private static IEnumerable GenerateSingleParameterPureMappingFunctionExpressions(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)}; - } - - private static IEnumerable GenerateIdentityMappingFunctionExpressions(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)}; - } - } -} diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/SymbolHelper.cs b/MappingGenerator/MappingGenerator/MappingGenerator/SymbolHelper.cs index d9aeec7..a75084a 100644 --- a/MappingGenerator/MappingGenerator/MappingGenerator/SymbolHelper.cs +++ b/MappingGenerator/MappingGenerator/MappingGenerator/SymbolHelper.cs @@ -8,58 +8,11 @@ namespace MappingGenerator { internal static class SymbolHelper { - public static bool IsUpdateParameterFunction(IMethodSymbol methodSymbol) - { - if (IsConstructor(methodSymbol)) - { - return false; - } - return methodSymbol.Parameters.Length == 2 && methodSymbol.ReturnsVoid; - } - - private static bool IsConstructor(IMethodSymbol methodSymbol) + public static bool IsConstructor(IMethodSymbol methodSymbol) { return methodSymbol.MethodKind == MethodKind.Constructor; } - public static bool IsUpdateThisObjectFunction(IMethodSymbol methodSymbol) - { - if (IsConstructor(methodSymbol)) - { - return false; - } - return methodSymbol.Parameters.Length == 1 && methodSymbol.ReturnsVoid; - } - - public static bool IsMultiParameterUpdateThisObjectFunction(IMethodSymbol methodSymbol) - { - if (IsConstructor(methodSymbol)) - { - return false; - } - return methodSymbol.Parameters.Length > 1 && methodSymbol.ReturnsVoid; - } - - public static bool IsPureMappingFunction(IMethodSymbol methodSymbol) - { - return methodSymbol.Parameters.Length == 1 && methodSymbol.ReturnsVoid == false; - } - - public static bool IsIdentityMappingFunction(IMethodSymbol methodSymbol) - { - return methodSymbol.Parameters.Length == 1 && methodSymbol.ReturnType.Equals(methodSymbol.Parameters[0].Type); - } - - public static bool IsMappingConstructor(IMethodSymbol methodSymbol) - { - return methodSymbol.Parameters.Length == 1 && methodSymbol.MethodKind == MethodKind.Constructor; - } - - public static bool IsMultiParameterMappingConstructor(IMethodSymbol methodSymbol) - { - return methodSymbol.Parameters.Length > 1 && methodSymbol.MethodKind == MethodKind.Constructor; - } - public static IEnumerable UnwrapGeneric(this ITypeSymbol typeSymbol) { if (typeSymbol.TypeKind == TypeKind.TypeParameter && typeSymbol is ITypeParameterSymbol namedType && namedType.Kind != SymbolKind.ErrorType) @@ -181,18 +134,5 @@ public static ITypeSymbol GetUnderlyingNullableType(ITypeSymbol type) { return ((INamedTypeSymbol) type).TypeArguments.First(); } - - public static bool IsMultiParameterPureFunction(IMethodSymbol methodSymbol) - { - return methodSymbol.Parameters.Length > 1 && methodSymbol.ReturnsVoid == false; - } - - public static bool IsThisObjectToOtherConvert(IMethodSymbol methodSymbol) - { - return methodSymbol.IsStatic == false && - methodSymbol.Parameters.Length == 0 && - methodSymbol.ReturnsVoid == false && - ObjectHelper.IsSimpleType(methodSymbol.ReturnType) == false; - } } } \ No newline at end of file diff --git a/MappingGenerator/MappingGenerator/MappingGenerator/SyntaxHelper.cs b/MappingGenerator/MappingGenerator/MappingGenerator/SyntaxHelper.cs index 444fe60..24457cc 100644 --- a/MappingGenerator/MappingGenerator/MappingGenerator/SyntaxHelper.cs +++ b/MappingGenerator/MappingGenerator/MappingGenerator/SyntaxHelper.cs @@ -1,4 +1,6 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace MappingGenerator { @@ -29,5 +31,12 @@ public static SyntaxNode FindNearestContainer(this SyntaxNode tokenParen return tokenParent.Parent == null ? null : FindNearestContainer(tokenParent.Parent); } + + public static StatementSyntax AsStatement(this SyntaxNode node) + { + if (node is ExpressionSyntax expression) + return SyntaxFactory.ExpressionStatement(expression); + return (StatementSyntax)node; + } } }