示例#1
0
        public static IFieldSymbol[] AsFieldArray(this AnalysedDeclaration declaration)
        {
            switch (declaration)
            {
            case FieldDeclaration fd: return(new IFieldSymbol[] { fd.Field });

            default: return(Array.Empty <IFieldSymbol>());
            }
        }
示例#2
0
        public static IPropertySymbol[] AsPropertyArray(this AnalysedDeclaration declaration)
        {
            switch (declaration)
            {
            case PropertyDeclaration pd: return(new IPropertySymbol[] { pd.Property });

            default: return(Array.Empty <IPropertySymbol>());
            }
        }
示例#3
0
        public static async Task <Document> RemoveParameter(
            Document document,
            ClassDeclarationSyntax classDeclaration,
            AnalysedDeclaration analysedDeclaration,
            ConstructorDeclarationSyntax constructorDeclaration,
            CancellationToken cancellationToken)
        {
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken);

            var analyser = new ConstructorPropertyRelationshipAnalyser(
                analysedDeclaration.AsFieldArray(),
                analysedDeclaration.AsPropertyArray());
            var result = analyser.Analyze(semanticModel, constructorDeclaration);

            var updatedConstructorDeclaration = constructorDeclaration;

            var                       analysisResult     = result.GetResult(analysedDeclaration.Symbol);
            ParameterSyntax           parameterToRemove  = null;
            ExpressionStatementSyntax assignmentToRemove = null;

            if (analysisResult is AssignmentExpressionAnalyserResult assignment)
            {
                // Remove constructor parameter
                var parameterSyntaxReference = assignment.AssignedParameter.DeclaringSyntaxReferences.FirstOrDefault();
                if (parameterSyntaxReference != null)
                {
                    parameterToRemove = await parameterSyntaxReference.GetSyntaxAsync() as ParameterSyntax;
                }

                // Remove assignment in constructor
                assignmentToRemove = assignment.Assignment.Parent as ExpressionStatementSyntax;
            }

            // Remove field / property from class.
            var rewriter = new RemoveDeclarationClassRewriter(constructorDeclaration, parameterToRemove, assignmentToRemove, analysedDeclaration);
            var updatedClassDeclaration = rewriter.Visit(classDeclaration);

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

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

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

            return(newDocument);
        }
示例#4
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);
        }
示例#5
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;
                }
            }
        }