Example #1
0
        public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>();

            if (document == null || classDeclarationSyntax == null)
            {
                return;
            }

            if (!ClassDeclarationSyntaxAnalysis.IsRecordLike(classDeclarationSyntax))
            {
                return;
            }

            var properties = ClassDeclarationSyntaxAnalysis.GetPropertyDeclarations(classDeclarationSyntax).ToList();

            if (properties.Count == 0 ||
                properties.Any(PropertyDeclarationSyntaxExtensions.IsStatic))
            {
                return;
            }

            context.RegisterRefactoring(
                new DelegateCodeAction(
                    "Generate to string from parameters",
                    (c) => GenerateToString(document, classDeclarationSyntax, properties, c)));

            return;
        }
Example #2
0
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, classDeclarationSyntax) = await context.GetClassIfOnClassIdentifier();

            if (classDeclarationSyntax == null)
            {
                return;
            }

            if (!ClassDeclarationSyntaxAnalysis.CanBeDiscriminatedUnionBaseType(classDeclarationSyntax))
            {
                return;
            }

            if (classDeclarationSyntax.Parent == null ||
                (!classDeclarationSyntax.Parent.IsKind(SyntaxKind.NamespaceDeclaration) &&
                 !classDeclarationSyntax.Parent.IsKind(SyntaxKind.CompilationUnit)))
            {
                return;
            }

            var duMembers = GetCandidateMethods(classDeclarationSyntax);

            if (duMembers.Count == 0)
            {
                return;
            }

            context.RegisterRefactoring(
                new DelegateCodeAction(
                    "Generate discriminated union from static methods",
                    (c) => GenerateDiscriminatedUnion(document, classDeclarationSyntax, c)));
        }
Example #3
0
        private static async Task <Document> GenerateToString(
            Document document,
            ClassDeclarationSyntax classDeclarationSyntax,
            IList <PropertyDeclarationSyntax> properties,
            CancellationToken cancellationToken)
        {
            InvocationExpressionSyntax GenerateNameOfCall(SyntaxToken identifier) =>
            ExpressionGenerationHelper.Invocation(
                "nameof",
                SyntaxHelpers.ArgumentFromIdentifier(identifier));

            var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

            if (properties.Count == 0)
            {
                return(document);
            }

            var firstEl = Enumerable.Repeat(
                (InterpolatedStringContentSyntax)SyntaxFactory.Interpolation(
                    GenerateNameOfCall(classDeclarationSyntax.Identifier)),
                1);
            var interpolatedStringExpression = InterpolatedStringGenerationHelper.InterpolatedString(
                firstEl.Concat(
                    properties.SelectMany(p =>
                                          new InterpolatedStringContentSyntax[]
            {
                InterpolatedStringGenerationHelper.Text($" "),
                SyntaxFactory.Interpolation(GenerateNameOfCall(p.Identifier)),
                InterpolatedStringGenerationHelper.Text($"="),
                SyntaxFactory.Interpolation(SyntaxFactory.IdentifierName(p.Identifier.WithoutTrivia()))
            })));

            var methodExpression = MethodGenerationHelper.Builder(ToStringMethodName)
                                   .Modifiers(Modifiers.Public, Modifiers.Override)
                                   .ReturnType(Types.String)
                                   .ArrowBody(ExpressionGenerationHelper.Arrow(interpolatedStringExpression))
                                   .Build();

            var previousToString = ClassDeclarationSyntaxAnalysis.GetMembers <MethodDeclarationSyntax>(
                classDeclarationSyntax)
                                   .FirstOrDefault(m => m.Identifier.ValueText.Equals(ToStringMethodName));

            var newClassDeclaration =
                previousToString != null?
                classDeclarationSyntax.ReplaceNode(previousToString, methodExpression)
                    : classDeclarationSyntax.AddMembers(methodExpression);

            var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            var newRoot     = root.ReplaceNode(classDeclarationSyntax, newClassDeclaration);
            var newDocument = document.WithSyntaxRoot(newRoot);

            return(newDocument);
        }
Example #4
0
        public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>();

            if (document == null || classDeclarationSyntax == null)
            {
                return;
            }

            if (!ClassDeclarationSyntaxAnalysis.IsRecordLike(classDeclarationSyntax))
            {
                return;
            }

            var(atMostOneConstructor, nonTrivialConstructorCandidate) =
                ClassDeclarationSyntaxAnalysis.HasAtMostOneNoneTrivialConstructor(classDeclarationSyntax);

            if (!atMostOneConstructor || nonTrivialConstructorCandidate == null)
            {
                return;
            }

            var properties = ClassDeclarationSyntaxAnalysis.GetPropertyDeclarations(classDeclarationSyntax).ToList();

            if (properties.Count == 0 ||
                properties.Any(PropertyDeclarationSyntaxExtensions.IsStatic))
            {
                return;
            }

            var cancellationToken = context.CancellationToken;
            var semanticModel     = await document.GetSemanticModelAsync(cancellationToken);

            var constructorSymbol = semanticModel.GetDeclaredSymbol(nonTrivialConstructorCandidate);
            var propertySymbols   = properties.Select(p => semanticModel.GetDeclaredSymbol(p, cancellationToken)).ToArray();

            var analyser = new ConstructorPropertyRelationshipAnalyser(
                Array.Empty <IFieldSymbol>(),
                propertySymbols);

            var result = analyser.Analyze(semanticModel, nonTrivialConstructorCandidate);

            var(idxMappings, isExhaustive) = result.GetIndexMapping(propertySymbols, constructorSymbol);
            if (!isExhaustive)
            {
                return;
            }

            context.RegisterRefactoring(
                new DelegateCodeAction(
                    "Generate with methods for parameters",
                    (c) => GenerateWithMethods(document, classDeclarationSyntax, properties, idxMappings, c)));

            return;
        }
Example #5
0
        private static async Task <Document> GenerateCreateMethod(
            Document document,
            ConstructorDeclarationSyntax constructor,
            CancellationToken cancellationToken)
        {
            var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

            var classDeclaration = constructor
                                   .Parent.FirstAncestorOrSelf <ClassDeclarationSyntax>();

            if (classDeclaration == null)
            {
                return(document);
            }

            var classType = SyntaxFactory.IdentifierName(
                classDeclaration.Identifier.WithoutTrivia());

            var createObjectExpression =
                ExpressionGenerationHelper.CreateObject(
                    classType,
                    constructor.ParameterList.ToArgumentArray());

            var createMethodExpression = MethodGenerationHelper.Builder(CreateMethodName)
                                         .Modifiers(Modifiers.Public, Modifiers.Static)
                                         .Parameters(constructor.ParameterList.Parameters.ToArray())
                                         .ReturnType(SyntaxFactory.IdentifierName(classDeclaration.Identifier.WithoutTrivia()))
                                         .ArrowBody(ExpressionGenerationHelper.Arrow(createObjectExpression))
                                         .Build();

            // TODO: validate argument types as well ?
            var previousCreate =
                ClassDeclarationSyntaxAnalysis.GetMembers <MethodDeclarationSyntax>(classDeclaration)
                .FirstOrDefault(m =>
                                m.Identifier.ValueText.Equals(CreateMethodName) &&
                                m.ParameterList.Parameters.Count == createMethodExpression.ParameterList.Parameters.Count);

            // TODO: align with "with" method generation
            var newClassDeclaration = classDeclaration;

            if (previousCreate == null)
            {
                createMethodExpression = createMethodExpression
                                         .NormalizeWhitespace(elasticTrivia: false)
                                         .WithLeadingTrivia(
                    Settings.EndOfLine,
                    SyntaxFactory.ElasticSpace)
                                         .WithTrailingTrivia(Settings.EndOfLine);
                newClassDeclaration = newClassDeclaration.InsertNodesAfter(constructor, new[] { createMethodExpression });
            }
            else
            {
                createMethodExpression = createMethodExpression
                                         .NormalizeWhitespace(elasticTrivia: false)
                                         .WithTriviaFrom(previousCreate);

                newClassDeclaration = newClassDeclaration.ReplaceNode(previousCreate, createMethodExpression);
            }

            var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            var newRoot     = root.ReplaceNode(classDeclaration, newClassDeclaration);
            var newDocument = document.WithSyntaxRoot(newRoot);

            return(newDocument);
        }
Example #6
0
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>();

            if (document == null || classDeclarationSyntax == null)
            {
                return;
            }

            if (ClassDeclarationSyntaxAnalysis.IsPartial(classDeclarationSyntax) ||
                ClassDeclarationSyntaxAnalysis.IsStatic(classDeclarationSyntax))
            {
                return;
            }

            var(atMostOneNonTrivialConstructor, nonTrivialConstructorCandidate) =
                ClassDeclarationSyntaxAnalysis.HasAtMostOneNoneTrivialConstructor(classDeclarationSyntax);

            if (!atMostOneNonTrivialConstructor)
            {
                return;
            }

            var(_, propertyDeclaration) = await context.FindSyntaxForCurrentSpan <PropertyDeclarationSyntax>();

            var(_, fieldDeclaration) = await context.FindSyntaxForCurrentSpan <FieldDeclarationSyntax>();

            // TODO: Skip properties like string Prop => "something";
            if ((fieldDeclaration == null && propertyDeclaration == null) ||
                (fieldDeclaration != null && fieldDeclaration.IsStatic()) ||
                (propertyDeclaration != null && propertyDeclaration.IsStatic()))
            {
                return;
            }

            var(_, fieldVariableDeclaration) = await context.FindVariableDeclaratorForCurrentSpan();

            if (fieldDeclaration != null && fieldVariableDeclaration == null)
            {
                return;
            }

            switch (nonTrivialConstructorCandidate)
            {
            case ConstructorDeclarationSyntax candidate:
            {
                var cancellationToken = context.CancellationToken;
                var semanticModel     = await document.GetSemanticModelAsync(cancellationToken);

                HandleCandidate(semanticModel, candidate, propertyDeclaration, fieldDeclaration, fieldVariableDeclaration);
            }
            break;

            case null:
            {
                var trivialConstructor = ClassDeclarationSyntaxAnalysis.GetConstructors(classDeclarationSyntax)?.FirstOrDefault();
                if (trivialConstructor != null)
                {
                    var cancellationToken = context.CancellationToken;
                    var semanticModel     = await document.GetSemanticModelAsync(cancellationToken);

                    HandleCandidate(semanticModel, trivialConstructor, propertyDeclaration, fieldDeclaration, fieldVariableDeclaration);
                }
                else
                {
                    // TODO: register add constructor refactoring
                }
            }

            break;
            }

            void HandleCandidate(
                SemanticModel model,
                ConstructorDeclarationSyntax candidate,
                PropertyDeclarationSyntax pDeclaration,
                FieldDeclarationSyntax fDeclaration,
                VariableDeclaratorSyntax variableDeclarator)
            {
                bool    isProp            = pDeclaration != null;
                var     constructorSymbol = model.GetDeclaredSymbol(candidate) as IMethodSymbol;
                ISymbol memberSymbol      = isProp ?
                                            model.GetDeclaredSymbol(pDeclaration)
                    :  model.GetDeclaredSymbol(variableDeclarator);

                if (constructorSymbol == null || memberSymbol == null)
                {
                    return;
                }

                var analyser = new ConstructorPropertyRelationshipAnalyser(
                    isProp ? Array.Empty <IFieldSymbol>() : new[] { memberSymbol as IFieldSymbol },
                    isProp ? new[] { memberSymbol as IPropertySymbol } : Array.Empty <IPropertySymbol>());

                var result             = analyser.Analyze(model, candidate);
                var assignmentResult   = result.GetResult(memberSymbol);
                AnalysedDeclaration ad = isProp ?
                                         (AnalysedDeclaration) new PropertyDeclaration(memberSymbol as IPropertySymbol, pDeclaration)
                    : new FieldDeclaration(memberSymbol as IFieldSymbol, fDeclaration, variableDeclarator);

                switch (assignmentResult)
                {
                case AssignmentExpressionAnalyserResult r:
                    // register remove as assignment exists
                    context.RegisterRefactoring(
                        new DelegateCodeAction(
                            $"Remove {ad.Identifier.ValueText}",
                            (c) => RefactoringActions.RemoveParameter(
                                document,
                                classDeclarationSyntax,
                                ad,
                                candidate,
                                c)));
                    break;

                case EmptyAssignmentAnalyserResult _:
                    // register add to constructor
                    context.RegisterRefactoring(
                        new DelegateCodeAction(
                            $"Add {ad.Identifier.ValueText} to constructor",
                            (c) => RefactoringActions.AddParameterToConstructor(
                                document,
                                classDeclarationSyntax,
                                ad,
                                constructorSymbol,
                                candidate,
                                c)));
                    break;

                case MultipleAssignments _:
                case ParsingError _:
                default:
                    // Something went wrong so it might be better to do nothing.
                    break;
                }
            }
        }
Example #7
0
        private static async Task <Document> GenerateWithMethods(
            Document document,
            ClassDeclarationSyntax classDeclarationSyntax,
            IList <PropertyDeclarationSyntax> properties,
            int[] propertyToParameterIdx,
            CancellationToken cancellationToken)
        {
            int MappedIdx(int i)
            {
                int mappedIdx = propertyToParameterIdx[i];

                if (mappedIdx == -1)
                {
                    throw new ArgumentException($"{nameof(propertyToParameterIdx)} contains invalid mapping");
                }
                return(mappedIdx);
            }

            ArgumentSyntax[] ReorderArgumentsToMapping(IReadOnlyList <ArgumentSyntax> arguments)
            {
                var result = new ArgumentSyntax[arguments.Count];

                for (int i = 0; i < arguments.Count; ++i)
                {
                    result[MappedIdx(i)] = arguments[i];
                }

                return(result);
            }

            T ExecuteWithTempArg <T>(ArgumentSyntax[] args, int argIdx, ArgumentSyntax tempArg, Func <ArgumentSyntax[], T> operation)
            {
                var oldArg = args[argIdx];

                args[argIdx] = tempArg;

                var result = operation(args);

                args[argIdx] = oldArg;
                return(result);
            }

            if (properties.Count != propertyToParameterIdx.Length)
            {
                throw new ArgumentException("properties.Count != propertyToParameterIdx.Length");
            }

            var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

            if (properties.Count == 0)
            {
                return(document);
            }

            var classType           = SyntaxFactory.IdentifierName(classDeclarationSyntax.Identifier.WithoutTrivia());
            var newClassDeclaration = classDeclarationSyntax;

            var argumentsArray = properties.Select(p => p.ToArgument()).ToArray();

            argumentsArray = ReorderArgumentsToMapping(argumentsArray);

            for (int i = 0; i < properties.Count; ++i)
            {
                var p          = properties[i];
                var id         = p.Identifier.WithoutTrivia();
                var lcId       = SyntaxHelpers.LowercaseIdentifierFirstLetter(id);
                var methodName = WithRefactoringUtils.MethodName(p.Identifier);
                var arg        = SyntaxHelpers.ArgumentFromIdentifier(lcId);

                var objectCreation = ExecuteWithTempArg(argumentsArray, MappedIdx(i), arg, args => ExpressionGenerationHelper.CreateObject(classType, args));

                var withMethodExpression = MethodGenerationHelper.Builder(methodName)
                                           .Modifiers(Modifiers.Public)
                                           .Parameters(SyntaxHelpers.Parameter(p.Type, lcId))
                                           .ReturnType(SyntaxFactory.IdentifierName(classDeclarationSyntax.Identifier.WithoutTrivia()))
                                           .ArrowBody(ExpressionGenerationHelper.Arrow(objectCreation))
                                           .Build();

                var previousWith = ClassDeclarationSyntaxAnalysis.GetMembers <MethodDeclarationSyntax>(
                    newClassDeclaration)
                                   .FirstOrDefault(m => m.Identifier.ValueText.Equals(methodName));

                if (previousWith == null)
                {
                    // TODO: Group with members next to each other rather than adding at the end
                    withMethodExpression = withMethodExpression
                                           .NormalizeWhitespace(elasticTrivia: false)
                                           .WithLeadingTrivia(
                        Settings.EndOfLine,
                        SyntaxFactory.ElasticSpace)
                                           .WithTrailingTrivia(Settings.EndOfLine);

                    newClassDeclaration = newClassDeclaration.AddMembers(withMethodExpression);
                }
                else
                {
                    withMethodExpression = withMethodExpression
                                           .NormalizeWhitespace(elasticTrivia: false)
                                           .WithTriviaFrom(previousWith);

                    newClassDeclaration = newClassDeclaration.ReplaceNode(previousWith, withMethodExpression);
                }
            }

            var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            var newRoot     = root.ReplaceNode(classDeclarationSyntax, newClassDeclaration);
            var newDocument = document.WithSyntaxRoot(newRoot);

            return(newDocument);
        }