internal static async Task ComputeRefactoringAsync(RefactoringContext context, ExpressionSyntax expression)
        {
            SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);

            foreach (Diagnostic diagnostic in semanticModel.GetCompilerDiagnostics(expression.Span, context.CancellationToken))
            {
                if (diagnostic.Id == CSharpErrorCodes.CannotImplicitlyConvertTypeExplicitConversionExists)
                {
                    if (context.Span.IsEmpty || diagnostic.Location.SourceSpan == expression.Span)
                    {
                        expression = expression
                                     .Ancestors()
                                     .FirstOrDefault(f => f.Span == diagnostic.Location.SourceSpan) as ExpressionSyntax;

                        if (expression != null &&
                            semanticModel.GetTypeSymbol(expression, context.CancellationToken)?.IsNullableOf(SpecialType.System_Boolean) == true)
                        {
                            if (semanticModel.GetTypeInfo(expression, context.CancellationToken).ConvertedType?.IsBoolean() == true ||
                                IsCondition(expression))
                            {
                                RegisterRefactoring(context, expression);
                            }
                        }
                    }
                }
                else if (diagnostic.Id == CSharpErrorCodes.OperatorCannotBeAppliedToOperands)
                {
                    if (context.Span.IsEmpty || diagnostic.Location.SourceSpan == expression.Span)
                    {
                        var binaryExpression = expression
                                               .Ancestors()
                                               .FirstOrDefault(f => f.Span == diagnostic.Location.SourceSpan) as BinaryExpressionSyntax;

                        if (binaryExpression != null)
                        {
                            ExpressionSyntax left = binaryExpression.Left;

                            if (left.Span.Contains(context.Span))
                            {
                                if (semanticModel.GetTypeSymbol(left, context.CancellationToken)?.IsNullableOf(SpecialType.System_Boolean) == true)
                                {
                                    RegisterRefactoring(context, left);
                                }
                            }
                            else
                            {
                                ExpressionSyntax right = binaryExpression.Right;

                                if (right.Span.Contains(context.Span) &&
                                    semanticModel.GetTypeSymbol(right, context.CancellationToken)?.IsNullableOf(SpecialType.System_Boolean) == true)
                                {
                                    RegisterRefactoring(context, right);
                                }
                            }
                        }
                    }
                }
            }
        }
        private static SyntaxNode CreateRootWithUsingFromArgument(SyntaxNode root, ExpressionSyntax childOfArgumentNode, string identifierName)
        {
            var arg = childOfArgumentNode.Parent as ArgumentSyntax;

            var variableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName(@"var"))
                                      .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(identifierName))
                                                                                          .WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.Token(SyntaxKind.EqualsToken), childOfArgumentNode))));


            var args    = arg.Parent as ArgumentListSyntax;
            var newArgs = args.ReplaceNode(arg, arg.WithExpression(SyntaxFactory.IdentifierName(identifierName)));

            StatementSyntax statement = childOfArgumentNode.FirstAncestorOfType <ExpressionStatementSyntax>();

            if (statement != null)
            {
                var exprStatement    = statement.ReplaceNode(args, newArgs);
                var newUsingStatment = CreateUsingStatement(exprStatement, SyntaxFactory.Block(exprStatement))
                                       .WithDeclaration(variableDeclaration);
                return(root.ReplaceNode(statement, newUsingStatment));
            }

            statement = (StatementSyntax)childOfArgumentNode.Ancestors().First(node => node is StatementSyntax);
            var newStatement = statement.ReplaceNode(args, newArgs);
            var statementsForUsing = new[] { newStatement }.Concat(GetChildStatementsAfter(statement));
            var usingBlock     = SyntaxFactory.Block(statementsForUsing);
            var usingStatement = CreateUsingStatement(newStatement, usingBlock)
                                 .WithDeclaration(variableDeclaration);
            var statementsToReplace = new List <StatementSyntax> {
                statement
            };

            statementsToReplace.AddRange(statementsForUsing.Skip(1));
            return(root.ReplaceNodes(statementsToReplace, (node, _) => node.Equals(statement) ? usingStatement : null));
        }
            private bool IsInsideConstructorOfTypeThatContainsSymbol(ExpressionSyntax node, IFieldSymbol fieldSymbol)
            {
                foreach (var currentNode in node.Ancestors())
                {
                    switch (currentNode.Kind())
                    {
                    case SyntaxKind.ConstructorDeclaration:
                    {
                        var constructorSymbol = _semanticModel.GetDeclaredSymbol(currentNode);
                        return(constructorSymbol.ContainingType == fieldSymbol.ContainingType && constructorSymbol.IsStatic == fieldSymbol.IsStatic);
                    }

                    case SyntaxKind.ParenthesizedLambdaExpression:
                    case SyntaxKind.SimpleLambdaExpression:
                    case SyntaxKind.AnonymousMethodExpression:
                    case SyntaxKind.MethodDeclaration:
                        return(false);

                    default:
                        continue;
                    }
                }

                return(false);
            }
        internal static MemberDeclarationSyntax GetContainingMethodOrPropertyOrIndexer(ExpressionSyntax expression)
        {
            foreach (SyntaxNode ancestor in expression.Ancestors())
            {
                switch (ancestor.Kind())
                {
                case SyntaxKind.MethodDeclaration:
                case SyntaxKind.PropertyDeclaration:
                case SyntaxKind.IndexerDeclaration:
                    return((MemberDeclarationSyntax)ancestor);

                case SyntaxKind.SimpleLambdaExpression:
                case SyntaxKind.ParenthesizedLambdaExpression:
                case SyntaxKind.AnonymousMethodExpression:
                case SyntaxKind.ConstructorDeclaration:
                case SyntaxKind.DestructorDeclaration:
                case SyntaxKind.EventDeclaration:
                case SyntaxKind.ConversionOperatorDeclaration:
                case SyntaxKind.OperatorDeclaration:
                case SyntaxKind.IncompleteMember:
                    return(null);
                }
            }

            return(null);
        }
        protected override async Task<Document> IntroduceLocalAsync(
            SemanticDocument document,
            ExpressionSyntax expression,
            bool allOccurrences,
            bool isConstant,
            CancellationToken cancellationToken)
        {
            var containerToGenerateInto = expression.Ancestors().FirstOrDefault(s =>
                s is BlockSyntax || s is ArrowExpressionClauseSyntax || s is LambdaExpressionSyntax);

            var newLocalNameToken = GenerateUniqueLocalName(
                document, expression, isConstant, containerToGenerateInto, cancellationToken);
            var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken);

            var modifiers = isConstant
                ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword))
                : default;

            var declarationStatement = SyntaxFactory.LocalDeclarationStatement(
                modifiers,
                SyntaxFactory.VariableDeclaration(
                    GetTypeSyntax(document, expression, cancellationToken),
                    SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(
                        newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()),
                        null,
                        SyntaxFactory.EqualsValueClause(expression.WithoutTrivia())))));

            // If we're inserting into a multi-line parent, then add a newline after the local-var
            // we're adding.  That way we don't end up having it and the starting statement be on
            // the same line (which will cause indentation to be computed incorrectly).
            var text = await document.Document.GetTextAsync(cancellationToken).ConfigureAwait(false);
            if (!text.AreOnSameLine(containerToGenerateInto.GetFirstToken(), containerToGenerateInto.GetLastToken()))
            {
                declarationStatement = declarationStatement.WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed);
            }

            switch (containerToGenerateInto)
            {
                case BlockSyntax block:
                    return await IntroduceLocalDeclarationIntoBlockAsync(
                        document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false);

                case ArrowExpressionClauseSyntax arrowExpression:
                    // this will be null for expression-bodied properties & indexer (not for individual getters & setters, those do have a symbol),
                    // both of which are a shorthand for the getter and always return a value
                    var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.Parent) as IMethodSymbol;
                    var createReturnStatement = !method?.ReturnsVoid ?? true;

                    return RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration(
                        document, arrowExpression, expression, newLocalName,
                        declarationStatement, allOccurrences, createReturnStatement, cancellationToken);

                case LambdaExpressionSyntax lambda:
                    return IntroduceLocalDeclarationIntoLambda(
                        document, lambda, expression, newLocalName, declarationStatement,
                        allOccurrences, cancellationToken);
            }

            throw new InvalidOperationException();
        }
        private static SelectQuerySyntax GetSelectQuery(ExpressionSyntax expression)
        {
            var selectQuery = expression.Ancestors().OfType <SelectQuerySyntax>().FirstOrDefault();

            if (selectQuery != null)
            {
                return(selectQuery);
            }

            var orderedQuery = expression.Ancestors().OfType <OrderedQuerySyntax>().FirstOrDefault();

            if (orderedQuery != null)
            {
                return(orderedQuery.GetAppliedSelectQuery());
            }

            return(null);
        }
        private static SyntaxNode GetNodeRootForAnalysis(ExpressionSyntax expression)
        {
            var parentNodeToSpeculate = expression
                                        .Ancestors(ascendOutOfTrivia: false)
                                        .FirstOrDefault(node =>
                                                        node.Kind() != SyntaxKind.Argument &&
                                                        node.Kind() != SyntaxKind.ArgumentList);

            return(parentNodeToSpeculate ?? expression);
        }
        private static IEnumerable <ICodeAction> GetOrderByFixes(SelectQuerySyntax selectQuery, ExpressionSyntax expression)
        {
            var orderByColumn = expression.Ancestors().OfType <OrderByColumnSyntax>().FirstOrDefault();

            if (orderByColumn == null)
            {
                return(Enumerable.Empty <ICodeAction>());
            }

            return(GetExpressionFixes(selectQuery, orderByColumn.ColumnSelector, expression));
        }
Example #9
0
        private static SyntaxList <StatementSyntax> InsertLocalVariableDeclarationBeforeCallSite(BlockSyntax block, VariableDeclarationSyntax varDecl, ExpressionSyntax callSite)
        {
            var stmts = block.Statements;

            var callSiteStatement = callSite.Ancestors().First(node => node.Parent.Kind() == SyntaxKind.Block);

            var i = stmts.IndexOf(stmt => stmt.IsEquivalentTo(callSiteStatement));

            var newStmts = stmts.Insert(i, SyntaxFactory.LocalDeclarationStatement(varDecl).WithNewLine());

            return(SyntaxFactory.List(newStmts.AsEnumerable()));
        }
        protected override async Task <Document> IntroduceLocalAsync(
            SemanticDocument document,
            ExpressionSyntax expression,
            bool allOccurrences,
            bool isConstant,
            CancellationToken cancellationToken)
        {
            var containerToGenerateInto = expression.Ancestors().FirstOrDefault(s =>
                                                                                s is BlockSyntax || s is ArrowExpressionClauseSyntax || s is LambdaExpressionSyntax);

            var newLocalNameToken = GenerateUniqueLocalName(
                document, expression, isConstant, containerToGenerateInto, cancellationToken);
            var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken);

            var modifiers = isConstant
                ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword))
                : default;

            var options = await document.Document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);

            var declarationStatement = SyntaxFactory.LocalDeclarationStatement(
                modifiers,
                SyntaxFactory.VariableDeclaration(
                    this.GetTypeSyntax(document, options, expression, isConstant, cancellationToken),
                    SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(
                                                             newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()),
                                                             null,
                                                             SyntaxFactory.EqualsValueClause(expression.WithoutTrivia())))));

            switch (containerToGenerateInto)
            {
            case BlockSyntax block:
                return(await IntroduceLocalDeclarationIntoBlockAsync(
                           document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false));

            case ArrowExpressionClauseSyntax arrowExpression:
                // this will be null for expression-bodied properties & indexer (not for individual getters & setters, those do have a symbol),
                // both of which are a shorthand for the getter and always return a value
                var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.Parent) as IMethodSymbol;
                var createReturnStatement = !method?.ReturnsVoid ?? true;

                return(RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration(
                           document, arrowExpression, expression, newLocalName,
                           declarationStatement, allOccurrences, createReturnStatement, cancellationToken));

            case LambdaExpressionSyntax lambda:
                return(IntroduceLocalDeclarationIntoLambda(
                           document, lambda, expression, newLocalName, declarationStatement,
                           allOccurrences, cancellationToken));
            }

            throw new InvalidOperationException();
        }
        private void RemoveLocal(ExpressionSyntax expression, DocumentEditor editor)
        {
            var variableDeclaration = expression.Ancestors().OfType <VariableDeclarationSyntax>().FirstOrDefault();

            if (variableDeclaration == null)
            {
                return;
            }

            if (variableDeclaration.Variables.Count > 1)
            {
                // Remove the appropriate variabledeclarator
                var declaratorToRemove = expression.Ancestors().OfType <VariableDeclaratorSyntax>().First();
                editor.RemoveNode(declaratorToRemove);
            }
            else
            {
                // Remove the entire variabledeclaration
                editor.RemoveNode(variableDeclaration.Ancestors().OfType <LocalDeclarationStatementSyntax>().First());
            }
        }
        private bool SkipFieldsFromItsOwnConstructor(TypeDeclarationWithSymbol type, ExpressionSyntax assignmentExpression, ISymbol assignmentSymbol)
        {
            var parentConstructor = assignmentExpression.Ancestors().OfType <ConstructorDeclarationSyntax>().FirstOrDefault();

            if (parentConstructor == null)
            {
                return(true);
            }

            return
                (assignmentSymbol.ContainingType != type.NamedTypeSymbol ||
                 assignmentSymbol.IsStatic != parentConstructor.Modifiers.Any(p => p.IsKind(SyntaxKind.StaticKeyword)));
        }
        /// <summary>
        ///     Replaces a certain expression for an identifier if the context is appropriate.
        /// </summary>
        /// <param name="expression">The expression (cast or binary as) to be replaced</param>
        /// <param name="newIdentifier">The new identifier that refers to the extracted variable</param>
        /// <param name="editor">The <see cref="DocumentEditor" /> used to edit the tree</param>
        /// <param name="requiresNullableValueAccess">
        ///     <code>true</code> if a <code>.Value</code> property access needs to be added to the new identifier.
        ///     This is needed in the case of a direct cast inside a larger invocation expression that needs to be replaced with an
        ///     identifier.
        ///     If this <code>.Value</code> wouldn't be added, it would create uncompilable code because you've changed the type
        ///     from int to int?.
        ///     Note that this should only happen in the case of a value type.
        /// </param>
        private void ReplaceIdentifier(ExpressionSyntax expression, SyntaxToken newIdentifier, DocumentEditor editor, bool requiresNullableValueAccess = false)
        {
            var newIdentifierName = SyntaxFactory.IdentifierName(newIdentifier);

            if (expression.Ancestors().OfType <InvocationExpressionSyntax>().Any())
            {
                if (requiresNullableValueAccess)
                {
                    var newAccess = SyntaxFactory.ParseExpression($"{newIdentifier.ValueText}.Value");
                    editor.ReplaceNode(expression, newAccess);
                }
                else
                {
                    editor.ReplaceNode(expression, newIdentifierName);
                }
                return;
            }

            if (!expression.Ancestors().OfType <VariableDeclaratorSyntax>().Any())
            {
                editor.ReplaceNode(expression, newIdentifierName);
            }
        }
Example #14
0
        private static bool IsInAsyncFunction(ExpressionSyntax expression)
        {
            foreach (var node in expression.Ancestors())
            {
                switch (node.Kind())
                {
                case SyntaxKind.ParenthesizedLambdaExpression:
                case SyntaxKind.SimpleLambdaExpression:
                case SyntaxKind.AnonymousMethodExpression:
                    return((node as AnonymousFunctionExpressionSyntax)?.AsyncKeyword.IsMissing == false);

                case SyntaxKind.MethodDeclaration:
                    return((node as MethodDeclarationSyntax)?.Modifiers.Any(SyntaxKind.AsyncKeyword) == true);

                default:
                    continue;
                }
            }

            return(false);
        }
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var document = context.Document;

            if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles)
            {
                return;
            }
            var span = context.Span;

            if (!span.IsEmpty)
            {
                return;
            }
            var cancellationToken = context.CancellationToken;

            if (cancellationToken.IsCancellationRequested)
            {
                return;
            }
            var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            if (model.IsFromGeneratedCode(cancellationToken))
            {
                return;
            }
            var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            var token = root.FindToken(span.Start);

            ExpressionSyntax identifier = token.Parent as IdentifierNameSyntax;

            if (identifier == null)
            {
                return;
            }

            // If identifier is a type name, this might be a static member access or similar, don't suggest null checks on it
            var identifierSymbol = model.GetSymbolInfo(identifier).Symbol;

            if ((identifierSymbol == null) || (identifierSymbol.IsType()))
            {
                return;
            }

            // Identifier might be part of a MemberAccessExpression and we need to check it for null as a whole
            identifier = GetOuterMemberAccessExpression(identifier) ?? identifier;

            if ((identifier.Parent is ExpressionSyntax) && ConditionContainsNullCheck((ExpressionSyntax)identifier.Parent, identifier))
            {
                return;
            }

            var identifierAncestors = identifier.Ancestors();

            // Don't surround Return statements with checks
            if (identifierAncestors.OfType <ReturnStatementSyntax>().Any())
            {
                return;
            }

            // If identifier is in a conditional ternary expression, skip refactoring is case of present null check in its condition
            var conditionalExprParent = identifierAncestors.OfType <TernaryConditionalExpressionSyntax>().FirstOrDefault();

            if ((conditionalExprParent != null) && ConditionContainsNullCheck(conditionalExprParent.Condition, identifier))
            {
                return;
            }

            // Check identifier type, don't suggest null checks for value types!
            var identifierType = model.GetTypeInfo(identifier).Type;

            if ((identifierType == null) || (identifierType.IsValueType && !identifierType.IsNullableType()))
            {
                return;
            }

            SyntaxNode statementToWrap = identifierAncestors.OfType <ExecutableStatementSyntax>().FirstOrDefault();

            if (statementToWrap == null)
            {
                return;
            }

            // No refactoring if statement is inside of a local variable declaration
            if (statementToWrap is LocalDeclarationStatementSyntax)
            {
                return;
            }

            bool       wrapWithSingleLineIfStatement = false;
            SyntaxNode newWrappedStatement           = null;

            var wrappedStatementAncestors = statementToWrap.Ancestors();

            if (wrappedStatementAncestors.OfType <SingleLineLambdaExpressionSyntax>().Any())
            {
                // Inside of a single-line lambda => wrap with single line If statement
                wrapWithSingleLineIfStatement = true;
            }

            // Check surrounding block
            var surroundingElseIfBlock = wrappedStatementAncestors.FirstOrDefault() as ElseIfBlockSyntax;

            if (surroundingElseIfBlock != null)
            {
                // Special handling for extension of Else If blocks
                if (StatementWithConditionContainsNullCheck(surroundingElseIfBlock, identifier))
                {
                    return;
                }
                statementToWrap     = surroundingElseIfBlock;
                newWrappedStatement = ExtendIfConditionWithNullCheck(surroundingElseIfBlock, identifier);
            }
            else
            {
                var surroundingStatement = wrappedStatementAncestors.OfType <ExecutableStatementSyntax>().FirstOrDefault();
                if (surroundingStatement != null)
                {
                    if (StatementWithConditionContainsNullCheck(surroundingStatement, identifier))
                    {
                        return;
                    }
                    if ((surroundingStatement is MultiLineIfBlockSyntax) || (surroundingStatement is SingleLineIfStatementSyntax))
                    {
                        statementToWrap     = surroundingStatement;
                        newWrappedStatement = ExtendIfConditionWithNullCheck(surroundingStatement, identifier);
                    }
                }
                else
                {
                    if (StatementWithConditionContainsNullCheck(statementToWrap, identifier))
                    {
                        return;
                    }
                    if ((statementToWrap is MultiLineIfBlockSyntax) || (statementToWrap is SingleLineIfStatementSyntax))
                    {
                        newWrappedStatement = ExtendIfConditionWithNullCheck(statementToWrap, identifier);
                    }
                }
            }

            if (newWrappedStatement == null)
            {
                if (wrapWithSingleLineIfStatement)
                {
                    newWrappedStatement = SyntaxFactory.SingleLineIfStatement(
                        SyntaxFactory.Token(SyntaxKind.IfKeyword),
                        CreateIsNotNothingBinaryExpression(identifier),
                        SyntaxFactory.Token(SyntaxKind.ThenKeyword),
                        SyntaxFactory.List <StatementSyntax>(new[] { ((StatementSyntax)statementToWrap).WithoutLeadingTrivia().WithoutTrailingTrivia() }),
                        null
                        ).WithLeadingTrivia(statementToWrap.GetLeadingTrivia()).WithTrailingTrivia(statementToWrap.GetTrailingTrivia()).WithAdditionalAnnotations(Formatter.Annotation);
                }
                else
                {
                    newWrappedStatement = SyntaxFactory.MultiLineIfBlock(
                        SyntaxFactory.IfStatement(
                            SyntaxFactory.Token(SyntaxKind.IfKeyword),
                            CreateIsNotNothingBinaryExpression(identifier),
                            SyntaxFactory.Token(SyntaxKind.ThenKeyword)),
                        SyntaxFactory.List <StatementSyntax>(new[] { ((StatementSyntax)statementToWrap).WithoutLeadingTrivia().WithoutTrailingTrivia() }),
                        SyntaxFactory.List <ElseIfBlockSyntax>(),
                        null
                        ).WithLeadingTrivia(statementToWrap.GetLeadingTrivia()).WithTrailingTrivia(statementToWrap.GetTrailingTrivia()).WithAdditionalAnnotations(Formatter.Annotation);
                }
            }

            context.RegisterRefactoring(CodeActionFactory.Create(token.Span, DiagnosticSeverity.Info, GettextCatalog.GetString("Add check for Nothing"), t2 =>
            {
                var newRoot = root.ReplaceNode <SyntaxNode>(statementToWrap, newWrappedStatement);
                return(Task.FromResult(document.WithSyntaxRoot(newRoot)));
            }));
        }
        private static SyntaxNode CreateRootWithUsingFromArgument(SyntaxNode root, ExpressionSyntax childOfArgumentNode, string identifierName)
        {
            var arg = childOfArgumentNode.Parent as ArgumentSyntax;

            var variableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName(@"var"))
                .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(identifierName))
                .WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.Token(SyntaxKind.EqualsToken), childOfArgumentNode))));


            var args = arg.Parent as ArgumentListSyntax;
            var newArgs = args.ReplaceNode(arg, arg.WithExpression(SyntaxFactory.IdentifierName(identifierName)));

            StatementSyntax statement = childOfArgumentNode.FirstAncestorOfType<ExpressionStatementSyntax>();
            if (statement != null)
            {
                var exprStatement = statement.ReplaceNode(args, newArgs);
                var newUsingStatment = CreateUsingStatement(exprStatement, SyntaxFactory.Block(exprStatement))
                    .WithDeclaration(variableDeclaration);
                return root.ReplaceNode(statement, newUsingStatment);
            }

            statement = (StatementSyntax)childOfArgumentNode.Ancestors().First(node => node is StatementSyntax);
            var newStatement = statement.ReplaceNode(args, newArgs);
            var statementsForUsing = new[] { newStatement }.Concat(GetChildStatementsAfter(statement));
            var usingBlock = SyntaxFactory.Block(statementsForUsing);
            var usingStatement = CreateUsingStatement(newStatement, usingBlock)
                .WithDeclaration(variableDeclaration);
            var statementsToReplace = new List<StatementSyntax> { statement };
            statementsToReplace.AddRange(statementsForUsing.Skip(1));
            return root.ReplaceNodes(statementsToReplace, (node, _) => node.Equals(statement) ? usingStatement : null);
        }
Example #17
0
            // To convert a null-check to pattern-matching, we should make sure of a few things:
            //
            //      (1) The pattern variable may not be used before the point of declaration.
            //
            //          {
            //              var use = t;
            //              if (x is T t) {}
            //          }
            //
            //      (2) The pattern variable may not be used outside of the new scope which
            //          is determined by the parent statement.
            //
            //          {
            //              if (x is T t) {}
            //          }
            //
            //          var use = t;
            //
            //      (3) The pattern variable may not be used before assignment in opposite
            //          branches, if any.
            //
            //          {
            //              if (x is T t) {}
            //              var use = t;
            //          }
            //
            // We walk up the tree from the point of null-check and see if any of the above is violated.
            private bool CanSafelyConvertToPatternMatching()
            {
                // Keep track of whether the pattern variable is definitely assigned when false/true.
                // We start by the null-check itself, if it's compared with '==', the pattern variable
                // will be definitely assigned when false, because we wrap the is-operator in a !-operator.
                var defAssignedWhenTrue = _comparison.IsKind(SyntaxKind.NotEqualsExpression, SyntaxKind.IsExpression);

                foreach (var current in _comparison.Ancestors())
                {
                    // Checking for any conditional statement or expression that could possibly
                    // affect or determine the state of definite-assignment of the pattern variable.
                    switch (current.Kind())
                    {
                    case SyntaxKind.LogicalAndExpression when !defAssignedWhenTrue:
                    case SyntaxKind.LogicalOrExpression when defAssignedWhenTrue:
                        // Since the pattern variable is only definitely assigned if the pattern
                        // succeeded, in the following cases it would not be safe to use pattern-matching.
                        // For example:
                        //
                        //      if ((x = o as string) == null && SomeExpression)
                        //      if ((x = o as string) != null || SomeExpression)
                        //
                        // Here, x would never be definitely assigned if pattern-matching were used.
                        return(false);

                    case SyntaxKind.LogicalAndExpression:
                    case SyntaxKind.LogicalOrExpression:

                    // Parentheses and cast expressions do not contribute to the flow analysis.
                    case SyntaxKind.ParenthesizedExpression:
                    case SyntaxKind.CastExpression:

                    // Skip over declaration parts to get to the parenting statement
                    // which might be a for-statement or a local declaration statement.
                    case SyntaxKind.EqualsValueClause:
                    case SyntaxKind.VariableDeclarator:
                    case SyntaxKind.VariableDeclaration:
                        continue;

                    case SyntaxKind.LogicalNotExpression:
                        // The !-operator negates the definitive assignment state.
                        defAssignedWhenTrue = !defAssignedWhenTrue;
                        continue;

                    case SyntaxKind.ConditionalExpression:
                        var conditionalExpression = (ConditionalExpressionSyntax)current;
                        if (LocalFlowsIn(defAssignedWhenTrue
                                    ? conditionalExpression.WhenFalse
                                    : conditionalExpression.WhenTrue))
                        {
                            // In a conditional expression, the pattern variable
                            // would not be definitely assigned in the opposite branch.
                            return(false);
                        }

                        return(CheckExpression(conditionalExpression));

                    case SyntaxKind.ForStatement:
                        var forStatement = (ForStatementSyntax)current;
                        if (forStatement.Condition is null || !forStatement.Condition.Span.Contains(_comparison.Span))
                        {
                            // In a for-statement, only the condition expression
                            // can make this definitely assigned in the loop body.
                            return(false);
                        }

                        return(CheckLoop(forStatement, forStatement.Statement, defAssignedWhenTrue));

                    case SyntaxKind.WhileStatement:
                        var whileStatement = (WhileStatementSyntax)current;
                        return(CheckLoop(whileStatement, whileStatement.Statement, defAssignedWhenTrue));

                    case SyntaxKind.IfStatement:
                        var ifStatement       = (IfStatementSyntax)current;
                        var oppositeStatement = defAssignedWhenTrue
                                ? ifStatement.Else?.Statement
                                : ifStatement.Statement;

                        if (oppositeStatement != null)
                        {
                            var dataFlow = _semanticModel.AnalyzeRequiredDataFlow(oppositeStatement);
                            if (dataFlow.DataFlowsIn.Contains(_localSymbol))
                            {
                                // Access before assignment is not safe in the opposite branch
                                // as the variable is not definitely assigned at this point.
                                // For example:
                                //
                                //    if (o is string x) { }
                                //    else { Use(x); }
                                //
                                return(false);
                            }

                            if (dataFlow.AlwaysAssigned.Contains(_localSymbol))
                            {
                                // If the variable is always assigned here, we don't need to check
                                // subsequent statements as it's definitely assigned afterwards.
                                // For example:
                                //
                                //     if (o is string x) { }
                                //     else { x = null; }
                                //
                                return(true);
                            }
                        }

                        if (!defAssignedWhenTrue &&
                            !_semanticModel.AnalyzeRequiredControlFlow(ifStatement.Statement).EndPointIsReachable)
                        {
                            // Access before assignment here is only valid if we have a negative
                            // pattern-matching in an if-statement with an unreachable endpoint.
                            // For example:
                            //
                            //      if (!(o is string x)) {
                            //        return;
                            //      }
                            //
                            //      // The 'return' statement above ensures x is definitely assigned here
                            //      Console.WriteLine(x);
                            //
                            return(true);
                        }

                        return(CheckStatement(ifStatement));
                    }

                    switch (current)
                    {
                    case ExpressionSyntax expression:
                        // If we reached here, it means we have a sub-expression that
                        // does not guarantee definite assignment. We should make sure that
                        // the pattern variable is not used outside of the expression boundaries.
                        return(CheckExpression(expression));

                    case StatementSyntax statement:
                        // If we reached here, it means that the null-check is appeared in
                        // a statement. In that case, the variable would be actually in the
                        // scope in subsequent statements, but not definitely assigned.
                        // Therefore, we should ensure that there is no use before assignment.
                        return(CheckStatement(statement));
                    }

                    // Bail out for error cases and unhandled cases.
                    break;
                }

                return(false);
            }
 private static SyntaxNode GetNodeRootForAnalysis(ExpressionSyntax expression)
 {
     var parentNodeToSpeculate = expression
         .Ancestors(ascendOutOfTrivia: false)
         .FirstOrDefault(node =>
         node.Kind() != SyntaxKind.Argument &&
         node.Kind() != SyntaxKind.ArgumentList);
     return parentNodeToSpeculate ?? expression;
 }
Example #19
0
        public CodeRefactoring GetRefactoring(IDocument document, TextSpan textSpan, CancellationToken cancellationToken)
        {
            SyntaxNode root = (SyntaxNode)document.GetSyntaxRoot(cancellationToken);

            // Get nodes for highlighted code (only nodes of expression type can be extracted to method)
            IEnumerable <ExpressionSyntax> selectedNodes = root.DescendantNodes(textSpan).OfType <ExpressionSyntax>();

            // Note: This does not work always.
            // Consider int a = 1 + 2 + 3; and extracting 2 + 3
            // There is no such expression like 2 + 3, rather there is ((1 + 2) + 3)

            // Select the node that spans the selected text most
            ExpressionSyntax selectedNode = null;
            int bestSpan = -1;

            foreach (var node in selectedNodes)
            {
                if (textSpan.Contains(node.Span))
                {
                    int spanWidth = node.Span.End - node.Span.Start;
                    if (spanWidth > bestSpan)
                    {
                        bestSpan     = spanWidth;
                        selectedNode = node;
                    }
                }
            }

            // If no expression fits in selection, raise no refactoring
            if (selectedNode == null)
            {
                return(null);
            }

            // Verify is the expression within statement. IdentifierName within method's parameter list is also expression, but can not be extracted.
            if (selectedNode.FirstAncestorOrSelf <StatementSyntax>() == null)
            {
                return(null);
            }

            // (1) Only top most member access expression can be extracted
            if (selectedNode.Ancestors().OfType <MemberAccessExpressionSyntax>().Any(n => n.Name.DescendantNodesAndSelf().Contains(selectedNode)))
            {
                return(null);
            }

            // (2) Selected node must not be (note `Equals', not `Contains') an expression of method invocation
            if (selectedNode.Ancestors().OfType <InvocationExpressionSyntax>().Any(n => n.Expression.Equals(selectedNode)))
            {
                return(null);
            }

            // (3) Left hand side of assignment (=, +=, -=, etc.) cannot be extracted
            if (selectedNode.Ancestors().OfType <BinaryExpressionSyntax>().Any(n => IsComplexAssignment(n) && n.Left.Equals(selectedNode)))
            {
                return(null);
            }

            return(new CodeRefactoring(
                       new[] { new ExtractMethodAction(document, selectedNode) }
                       , selectedNode.Span));
        }
        private static bool SkipFieldsFromItsOwnConstructor(TypeDeclarationWithSymbol type, ExpressionSyntax assignmentExpression, ISymbol assignmentSymbol)
        {
            var parentConstructor = assignmentExpression.Ancestors().OfType<ConstructorDeclarationSyntax>().FirstOrDefault();

            if (parentConstructor == null)
                return true;

            return
                assignmentSymbol.ContainingType != type.NamedTypeSymbol ||
                assignmentSymbol.IsStatic != parentConstructor.Modifiers.Any(p => p.IsKind(SyntaxKind.StaticKeyword));
        }
Example #21
0
 private static bool IsInsideIfDirective(ExpressionSyntax node)
 {
     return(node.Ancestors().Any(a => a is ElifDirectiveTriviaSyntax || a is IfDirectiveTriviaSyntax));
 }
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var document = context.Document;
            if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles)
                return;
            var span = context.Span;
            if (!span.IsEmpty)
                return;
            var cancellationToken = context.CancellationToken;
            if (cancellationToken.IsCancellationRequested)
                return;
            var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            if (model.IsFromGeneratedCode(cancellationToken))
                return;
            var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
            var token = root.FindToken(span.Start);

            ExpressionSyntax identifier = token.Parent as IdentifierNameSyntax;
            if (identifier == null)
                return;

            // If identifier is a type name, this might be a static member access or similar, don't suggest null checks on it
            var identifierSymbol = model.GetSymbolInfo(identifier).Symbol;
            if ((identifierSymbol == null) || (identifierSymbol.IsType()))
                return;

            // Identifier might be part of a MemberAccessExpression and we need to check it for null as a whole
            identifier = GetOuterMemberAccessExpression(identifier) ?? identifier;

            if ((identifier.Parent is ExpressionSyntax) && ConditionContainsNullCheck((ExpressionSyntax)identifier.Parent, identifier))
                return;

            var identifierAncestors = identifier.Ancestors();

            // Don't surround return statements with checks
            if (identifierAncestors.OfType<ReturnStatementSyntax>().Any())
                return;

            // If identifier is in a conditional ternary expression, skip refactoring is case of present null check in its condition
            var conditionalExprParent = identifierAncestors.OfType<ConditionalExpressionSyntax>().FirstOrDefault();
            if ((conditionalExprParent != null) && ConditionContainsNullCheck(conditionalExprParent.Condition, identifier))
                return;

            // Check identifier type, don't suggest null checks for value types!
            var identifierType = model.GetTypeInfo(identifier).Type;
            if ((identifierType == null) || (identifierType.IsValueType && !identifierType.IsNullableType()))
                return;

            var statementToWrap = identifierAncestors.OfType<StatementSyntax>().FirstOrDefault();
            if (statementToWrap == null)
                return;

            // No refactoring if statement is inside of a local variable declaration
            if ((statementToWrap is BlockSyntax) || (statementToWrap is LocalDeclarationStatementSyntax))
                return;

            SyntaxNode newWrappedStatement = null;

            // Check surrounding block
            var surroundingStatement = statementToWrap.Ancestors().OfType<StatementSyntax>().FirstOrDefault();
            if (surroundingStatement is BlockSyntax)
                surroundingStatement = surroundingStatement.Parent as StatementSyntax;
            if (surroundingStatement != null)
            {
                if (StatementWithConditionContainsNullCheck(surroundingStatement, identifier))
                    return;

                if (surroundingStatement is IfStatementSyntax surroundingIfStatement
                    && surroundingIfStatement.Else == null)
                {
                    statementToWrap = surroundingIfStatement;
                }
            }

            if (statementToWrap is IfStatementSyntax)
            {
                newWrappedStatement = ExtendIfConditionWithNullCheck((IfStatementSyntax)statementToWrap, identifier);
            }
            else
            {
                newWrappedStatement = SyntaxFactory.IfStatement(
                        SyntaxFactory.BinaryExpression(SyntaxKind.NotEqualsExpression, identifier, SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)),
                        SyntaxFactory.Block(statementToWrap).WithLeadingTrivia(statementToWrap.GetLeadingTrivia()).WithTrailingTrivia(statementToWrap.GetTrailingTrivia())
                    ).WithAdditionalAnnotations(Formatter.Annotation);
            }

            context.RegisterRefactoring(CodeActionFactory.Create(token.Span, DiagnosticSeverity.Info, GettextCatalog.GetString("Add null check"), t2 =>
            {
                var newRoot = root.ReplaceNode(statementToWrap, newWrappedStatement);
                return Task.FromResult(document.WithSyntaxRoot(newRoot));
            }));
        }
Example #23
0
        public CodeRefactoring GetRefactoring(IDocument document, TextSpan textSpan, CancellationToken cancellationToken)
        {
            SyntaxNode     root  = (SyntaxNode)document.GetSyntaxRoot(cancellationToken);
            ISemanticModel model = document.GetSemanticModel();

            // Get nodes for highlighted code (only nodes of expression type can be extracted)
            IEnumerable <ExpressionSyntax> selectedNodes = root.DescendantNodes(textSpan).OfType <ExpressionSyntax>();

            // Select the node that spans the selected text most
            ExpressionSyntax selectedNode = null;
            int bestSpan = -1;

            foreach (var node in selectedNodes)
            {
                if (textSpan.Contains(node.Span))
                {
                    int spanWidth = node.Span.End - node.Span.Start;
                    if (spanWidth > bestSpan)
                    {
                        bestSpan     = spanWidth;
                        selectedNode = node;
                    }
                }
            }

            if (selectedNode == null)
            {
                return(null);
            }

            // Verify is the expression within statement. IdentifierName within method's parameter list is also expression, but can not be converted to variable.
            if (selectedNode.FirstAncestorOrSelf <StatementSyntax>() == null)
            {
                return(null);
            }

            // Special cases:
            // (1) a.b.c.d -> b.c is expression, but cannot be extracted. a.b.c can be extracted
            // (2) foo() -> foo is expression, but cannot be extracted (the name of method, not invocation!)
            // (3) a = b -> a is expression, but cannot be extracted
            // (4) foo() where foo() returns void -> void method cannot be extracted

            // (1) Only top most member access expression can be extracted
            // a.b.c -> a, a.b, and a.b.c can be extracted
            if (selectedNode.Ancestors().OfType <MemberAccessExpressionSyntax>().Any(n => n.Name.DescendantNodesAndSelf().Contains(selectedNode)))
            {
                return(null);
            }

            // (2) Selected node must not be (note `Equals', not `Contains') an expression of method invocation
            if (selectedNode.Ancestors().OfType <InvocationExpressionSyntax>().Any(n => n.Expression.Equals(selectedNode)))
            {
                return(null);
            }

            // (3) Left hand side of assignment (=, +=, -=, etc.) cannot be extracted
            if (selectedNode.Ancestors().OfType <BinaryExpressionSyntax>().Any(n => IsComplexAssignment(n) && n.Left.Equals(selectedNode)))
            {
                return(null);
            }

            // (4) void method cannot be extracted
            ITypeSymbol typeSymbol = model.GetTypeInfo(selectedNode, cancellationToken).Type;

            if (typeSymbol == null || typeSymbol.SpecialType == SpecialType.System_Void)
            {
                return(null);
            }

            return(new CodeRefactoring(
                       new[] { new IntroduceLocalAction(document, selectedNode) }
                       , selectedNode.Span));
        }
        private static bool IsInAsyncFunction(ExpressionSyntax expression)
        {
            foreach (var node in expression.Ancestors())
            {
                switch (node.Kind())
                {
                    case SyntaxKind.ParenthesizedLambdaExpression:
                    case SyntaxKind.SimpleLambdaExpression:
                    case SyntaxKind.AnonymousMethodExpression:
                        return (node as AnonymousFunctionExpressionSyntax)?.AsyncKeyword.IsMissing == false;
                    case SyntaxKind.MethodDeclaration:
                        return (node as MethodDeclarationSyntax)?.Modifiers.Any(SyntaxKind.AsyncKeyword) == true;
                    default:
                        continue;
                }
            }

            return false;
        }
 private bool IsSurroundedByInvocation(ExpressionSyntax expression)
 {
     return(expression.Ancestors().OfType <InvocationExpressionSyntax>().Any());
 }
        public CodeActionEdit GetEdit(CancellationToken cancellationToken)
        {
            SyntaxNode     root  = (SyntaxNode)this.document.GetSyntaxRoot(cancellationToken);
            ISemanticModel model = this.document.GetSemanticModel(cancellationToken);

            // New method's name
            const string methodName = "NewMethod";

            // Get the resultant type of expression being extracted into method
            TypeSymbol typeSymbol = model.GetTypeInfo(this.expression, cancellationToken).Type as TypeSymbol;

            if (typeSymbol == null)
            {
                return(null);
            }

            // Get the container that the new method will be stored in
            // TypeDeclarationSyntax refers to classes, structs and interfaces
            TypeDeclarationSyntax containingType = this.expression.FirstAncestorOrSelf <TypeDeclarationSyntax>();

            if (containingType == null)
            {
                return(null);
            }

            TypeSyntax returnType = Syntax.ParseTypeName(typeSymbol.ToMinimalDisplayString(containingType.GetLocation(), model));

            // Create new method's body
            StatementSyntax methodStatement = null;

            if (typeSymbol.SpecialType == SpecialType.System_Void)
            {
                // If return type is void (foo();), no value is returned at all
                methodStatement = Syntax.ExpressionStatement(this.expression);
            }
            else
            {
                // Create return statement
                methodStatement = Syntax.ReturnStatement(this.expression);
            }

            BlockSyntax body = Syntax.Block(Syntax.List <StatementSyntax>(methodStatement));

            // Create method declaration
            MethodDeclarationSyntax methodDeclaration = Syntax.MethodDeclaration(returnType, methodName).WithBody(body);

            SeparatedSyntaxList <ParameterSyntax> parametersList = Syntax.SeparatedList <ParameterSyntax>();
            SeparatedSyntaxList <ArgumentSyntax>  argumentsList  = Syntax.SeparatedList <ArgumentSyntax>();

            // Perform data flow analysis within expression
            var analysis = model.AnalyzeExpressionDataFlow(this.expression);

            // Analyze when to use `ref' and when to use `out' (`out' variable cannot be read before assignment in method, but variable cannot be passed as `ref' if is unassigned before expression in old place)
            // If variable is read in expression it is guaranteed that is has already value assigned.
            // Also, `out' variable can be written to before used as `out' argument.
            //
            // Conclusion:
            // Use `out' if variable is first written to (no matter if already has some value).
            // Otherwise, use `ref' (because variable is first read from and already has value).
            //
            // Consider:
            // void foo(out int i) { return (i=3) + i; }, can be used for both `int i;' and `int i = 1;', but not in `i + (i=3)'
            // void foo(ref int i) { return i + (i=3); }, can be used for `int i = 1;', but not in `int i;' and `(i=3) + i'
            //
            // Also note:
            // The first occurence of identifier can be used in `foo(out identifier)' context, it is not write to operation, but MUST remain as `out' or `ref'

            // Out/Ref parameters
            foreach (var variable in analysis.WrittenInside)
            {
                // Data flow analysis is applicable only for local variables (`this.a', or even `a' if it is field, both are flattened to `this' variable)
                // And `this' is of Parameter kind
                if (variable.Kind == CommonSymbolKind.Parameter)
                {
                    // If this is `this', do not pass as argument, can be referenced directly from method scope
                    if (((ParameterSymbol)variable).IsThis)
                    {
                        continue;
                    }
                }

                // Find first identifier expression that refers to analyzed symbol
                ExpressionSyntax symbolExpression = this.expression.DescendantNodesAndSelf()
                                                    .OfType <IdentifierNameSyntax>()
                                                    .Where(n => variable.Equals(model.GetSymbolInfo(n, cancellationToken).Symbol))
                                                    .FirstOrDefault();

                if (symbolExpression == null)
                {
                    // Should not happen at all
                    continue;
                }

                SyntaxToken refOrOut = Syntax.Token(SyntaxKind.RefKeyword);

                // The symbolExpression is first reference to variable (due to preorder search in DescendantNodes)
                // If it is assignment, then variable should be passed as `out'
                if (symbolExpression.Ancestors().OfType <BinaryExpressionSyntax>().Any(n => IsComplexAssignment(n) && n.Left.DescendantNodesAndSelf().Contains(symbolExpression)))
                {
                    refOrOut = Syntax.Token(SyntaxKind.OutKeyword);
                }

                // If expression is used as argument (`ref sth', or `out sth'), the keyword must remain.
                ArgumentSyntax expressionAsArgument = symbolExpression.FirstAncestorOrSelf <ArgumentSyntax>();
                if (expressionAsArgument != null)
                {
                    refOrOut = expressionAsArgument.RefOrOutKeyword;
                }

                ArgumentSyntax argument = Syntax.Argument(symbolExpression)
                                          .WithRefOrOutKeyword(refOrOut);

                argumentsList = argumentsList.Add(argument);

                // Get type of new parameter
                TypeSymbol parameterType = null;

                switch (variable.Kind)
                {
                case CommonSymbolKind.Local:
                {
                    LocalSymbol localSymbol = (LocalSymbol)variable;
                    parameterType = localSymbol.Type;
                }
                break;

                case CommonSymbolKind.Parameter:
                {
                    ParameterSymbol parameterSymbol = (ParameterSymbol)variable;
                    parameterType = parameterSymbol.Type;
                }
                break;
                }

                if (parameterType == null)
                {
                    // It can be Range type variable, used in Linq
                    continue;
                }

                // Parse type name
                TypeSyntax parameterTypeSyntax = Syntax.ParseTypeName(parameterType.ToMinimalDisplayString(containingType.GetLocation(), model));

                ParameterSyntax parameter = Syntax.Parameter(Syntax.Identifier(variable.Name))
                                            .WithType(parameterTypeSyntax)
                                            .WithModifiers(Syntax.TokenList(refOrOut));

                parametersList = parametersList.Add(parameter);
            }

            // In parameters
            foreach (var variable in analysis.ReadInside)
            {
                // Do not pass variable as `in', if it is already `out' variable
                if (analysis.WrittenInside.Contains(variable))
                {
                    continue;
                }

                // Data flow analysis is applicable only for local variables (`this.a', or even `a' if it is field, both are flattened to `this' variable)
                // And `this' is of Parameter kind
                if (variable.Kind == CommonSymbolKind.Parameter)
                {
                    // If this is `this', do not pass as argument, can be referenced directly from method scope
                    if (((ParameterSymbol)variable).IsThis)
                    {
                        continue;
                    }
                }

                // Find first identifier expression that refers to analyzed symbol
                ExpressionSyntax symbolExpression = this.expression.DescendantNodesAndSelf()
                                                    .OfType <IdentifierNameSyntax>()
                                                    .Where(n => variable.Equals(model.GetSymbolInfo(n, cancellationToken).Symbol))
                                                    .FirstOrDefault();

                if (symbolExpression == null)
                {
                    // Should not happen at all
                    continue;
                }

                // Create argument to be passed to method
                ArgumentSyntax argument = Syntax.Argument(symbolExpression);

                argumentsList = argumentsList.Add(argument);

                // Get type of new parameter
                TypeSymbol parameterType = null;

                switch (variable.Kind)
                {
                case CommonSymbolKind.Local:
                {
                    LocalSymbol localSymbol = (LocalSymbol)variable;
                    parameterType = localSymbol.Type;
                }
                break;

                case CommonSymbolKind.Parameter:
                {
                    ParameterSymbol parameterSymbol = (ParameterSymbol)variable;
                    parameterType = parameterSymbol.Type;
                }
                break;
                }

                if (parameterType == null)
                {
                    // It can be Range type variable, used in Linq
                    continue;
                }

                // Parse type name
                TypeSyntax parameterTypeSyntax = Syntax.ParseTypeName(parameterType.ToMinimalDisplayString(containingType.GetLocation(), model));

                ParameterSyntax parameter = Syntax.Parameter(Syntax.Identifier(variable.Name))
                                            .WithType(parameterTypeSyntax);

                parametersList = parametersList.Add(parameter);
            }

            // Add parameter list to method declaration
            methodDeclaration = methodDeclaration.WithParameterList(Syntax.ParameterList(parametersList))
                                .WithLeadingTrivia(Syntax.ElasticCarriageReturnLineFeed)
                                .WithAdditionalAnnotations(CodeAnnotations.Formatting);

            // If the expression is within static method, the extracted method should be static as well
            MemberDeclarationSyntax memberDeclaration = this.expression.FirstAncestorOrSelf <MemberDeclarationSyntax>();

            if (memberDeclaration == null)
            {
                return(null);
            }

            ISymbol memberSymbol = model.GetDeclaredSymbol(memberDeclaration);

            if (memberSymbol.IsStatic)
            {
                methodDeclaration = methodDeclaration.WithModifiers(methodDeclaration.Modifiers.Add(Syntax.Token(SyntaxKind.StaticKeyword)));
            }

            // Format arguments' list
            ArgumentListSyntax argumentListSyntax = Syntax.ArgumentList(argumentsList)
                                                    .WithAdditionalAnnotations(CodeAnnotations.Formatting);

            // Replace selected expression with new method's invocation
            InvocationExpressionSyntax methodInvocation = Syntax.InvocationExpression(Syntax.IdentifierName(methodName))
                                                          .WithArgumentList(argumentListSyntax)
                                                          .WithLeadingTrivia(this.expression.GetLeadingTrivia())
                                                          .WithTrailingTrivia(this.expression.GetTrailingTrivia());

            // If containing method is a template, type parameters should be forwarded to new method as well,
            // together with type constraints.
            // Note: I am interested only in type parameters from MethodDeclaration. Other method-like syntax (lambdas, anonymous methods, indexers, operators) don't specify type parameters.
            // Additionally, type parameters of containing type are visible to new method, becuase it is also a member of this class
            MethodDeclarationSyntax containingMethodDeclaration = this.expression.FirstAncestorOrSelf <MethodDeclarationSyntax>();

            if (containingMethodDeclaration != null && containingMethodDeclaration.TypeParameterList != null)
            {
                methodDeclaration = methodDeclaration.WithTypeParameterList(containingMethodDeclaration.TypeParameterList)
                                    .WithConstraintClauses(containingMethodDeclaration.ConstraintClauses);

                SeparatedSyntaxList <TypeSyntax> typeArguments = Syntax.SeparatedList <TypeSyntax>();
                foreach (TypeParameterSyntax templateArg in containingMethodDeclaration.TypeParameterList.Parameters)
                {
                    typeArguments = typeArguments.Add(Syntax.ParseTypeName(templateArg.Identifier.ValueText));
                }

                TypeArgumentListSyntax typeArgumentList = Syntax.TypeArgumentList(typeArguments);
                GenericNameSyntax      genericName      = Syntax.GenericName(Syntax.Identifier(methodName), typeArgumentList)
                                                          .WithAdditionalAnnotations(CodeAnnotations.Formatting);
                methodInvocation = methodInvocation.WithExpression(genericName);
            }

            TypeDeclarationSyntax newContainingType = containingType.ReplaceNode(this.expression, methodInvocation);

            // Insert method declaration to containing type
            if (containingType.Kind == SyntaxKind.ClassDeclaration)
            {
                ClassDeclarationSyntax classDeclaration = (ClassDeclarationSyntax)newContainingType;
                newContainingType = classDeclaration.AddMembers(methodDeclaration);
            }
            else if (containingType.Kind == SyntaxKind.StructDeclaration)
            {
                StructDeclarationSyntax structDeclaration = (StructDeclarationSyntax)newContainingType;
                newContainingType = structDeclaration.AddMembers(methodDeclaration);
            }
            else
            {
                return(null);
            }

            SyntaxNode newRoot = root.ReplaceNode(containingType, newContainingType);

            return(new CodeActionEdit(document.UpdateSyntaxRoot(newRoot)));
        }
Example #27
0
 private static bool IsSurroundedByInvocation(ExpressionSyntax expression) => expression.Ancestors().OfType <InvocationExpressionSyntax>().Any();