Example #1
0
        public static async Task <Document> AddParameterToConstructor(
            Document document,
            ClassDeclarationSyntax classDeclaration,
            AnalysedDeclaration analysedDeclaration, // must be either field variable or property
            IMethodSymbol constructorSymbol,
            ConstructorDeclarationSyntax constructorDeclaration,
            CancellationToken cancellationToken)
        {
            const int AppendPosition = int.MaxValue;

            var semanticModel = await document.GetSemanticModelAsync(cancellationToken);

            var classMemberAnalysis = new ClassMembersAnalysis(classDeclaration, semanticModel);
            var analyser            = new ConstructorPropertyRelationshipAnalyser(
                classMemberAnalysis.Fields.Select(f => f.Symbol).ToArray(),
                classMemberAnalysis.Properties.Select(f => f.Symbol).ToArray());
            var result = analyser.Analyze(semanticModel, constructorDeclaration);

            // Filter to consider only fields and properties that are assigned in constructor.
            var filteredClassMembers = classMemberAnalysis.WithFilteredProperties(
                (in FieldInfo fi) => result.GetResult(fi.Symbol) is AssignmentExpressionAnalyserResult,
                (in PropertyInfo pi) => result.GetResult(pi.Symbol) is AssignmentExpressionAnalyserResult);

            // Find closest declaration among the ones that are assigned in the constructor
            var(closestSymbol, isBeforeFoundSymbol) = filteredClassMembers.GetClosestFieldOrProperty(analysedDeclaration.Symbol);
            int constructorInsertPosition = AppendPosition;

            if (closestSymbol != null)
            {
                // There is another member that is assigned in constructor
                constructorInsertPosition = result.GetMatchingParameterIdx(constructorSymbol, closestSymbol);
                if (!isBeforeFoundSymbol)
                {
                    ++constructorInsertPosition;
                }
            }

            // TODO: resolve name clashes if parameter with a given name already exists?
            var addedParameter            = SyntaxHelpers.LowercaseIdentifierFirstLetter(analysedDeclaration.Identifier);
            var newConstructorDeclaration = constructorDeclaration.InsertParameter(
                SyntaxHelpers.Parameter(
                    analysedDeclaration.Type,
                    addedParameter),
                constructorInsertPosition);

            AssignmentExpressionSyntax closestSymbolAssignment = null;

            if (closestSymbol != null && result.GetResult(closestSymbol) is AssignmentExpressionAnalyserResult res)
            {
                closestSymbolAssignment = res.Assignment;
            }

            var fieldIdentifier     = SyntaxFactory.IdentifierName(analysedDeclaration.Identifier);
            var parameterIdentifier = SyntaxFactory.IdentifierName(addedParameter);
            var leftSide            = addedParameter.ValueText.Equals(analysedDeclaration.Identifier.ValueText, StringComparison.Ordinal) ?
                                      (ExpressionSyntax)ExpressionGenerationHelper.ThisMemberAccess(fieldIdentifier)
                : fieldIdentifier;
            var assignment = ExpressionGenerationHelper.SimpleAssignment(leftSide, parameterIdentifier);

            var statementToAdd = SyntaxFactory.ExpressionStatement(assignment);
            var body           = constructorDeclaration.Body;

            var closestStatement = closestSymbolAssignment?.Parent as ExpressionStatementSyntax;

            if (closestStatement == null)
            {
                newConstructorDeclaration = newConstructorDeclaration
                                            .AddBodyStatements(statementToAdd)
                                            .WithAdditionalAnnotations(Formatter.Annotation);
            }
            else if (isBeforeFoundSymbol)
            {
                var newBody = body.InsertBefore(closestStatement, statementToAdd);
                newConstructorDeclaration = newConstructorDeclaration.WithBody(
                    newBody).WithAdditionalAnnotations(Formatter.Annotation);
            }
            else
            {
                var newBody = body.InsertAfter(closestStatement, statementToAdd);
                newConstructorDeclaration = newConstructorDeclaration.WithBody(
                    newBody).WithAdditionalAnnotations(Formatter.Annotation);
            }

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

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

            var newRoot     = root.ReplaceNode(constructorDeclaration, newConstructorDeclaration);
            var newDocument = document.WithSyntaxRoot(newRoot);

            return(newDocument);
        }
Example #2
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);
        }