Пример #1
0
        protected async Task TestNotUnderselectedAsync <TNode>(string text) where TNode : SyntaxNode
        {
            text = GetSelectionAndResultSpans(text, out var selection, out var result);
            var resultNode = await GetNodeForSelectionAsync <TNode>(text, selection, Functions <TNode> .True).ConfigureAwait(false);

            Assert.Equal(result, resultNode.Span);
            Assert.False(CodeRefactoringHelpers.IsNodeUnderselected(resultNode, selection));
        }
Пример #2
0
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, span, cancellationToken) = context;

            var expression = (SyntaxNode?)await context.TryGetRelevantNodeAsync <TBinaryExpressionSyntax>().ConfigureAwait(false);

            var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>();
            var syntaxKinds = document.GetRequiredLanguageService <ISyntaxKindsService>();

            if (expression == null ||
                (!syntaxFacts.IsLogicalAndExpression(expression) &&
                 !syntaxFacts.IsLogicalOrExpression(expression)))
            {
                return;
            }

            if (span.IsEmpty)
            {
                // Walk up to the topmost binary of the same type.  When converting || to && (or vice versa)
                // we want to grab the entire set.  i.e.  `!a && !b && !c` should become `!(a || b || c)` not
                // `!(a || b) && !c`
                while (expression.Parent?.RawKind == expression.RawKind)
                {
                    expression = expression.Parent;
                }
            }
            else
            {
                // When selection is non-empty -> allow only top-level full selections.
                // Currently the refactoring can't handle invert of arbitrary nodes but only whole subtrees
                // and allowing it just for selection of those nodes that - by chance - form a full subtree
                // would produce only confusion.
                if (CodeRefactoringHelpers.IsNodeUnderselected(expression, span) ||
                    syntaxFacts.IsLogicalAndExpression(expression.Parent) || syntaxFacts.IsLogicalOrExpression(expression.Parent))
                {
                    return;
                }
            }

            context.RegisterRefactoring(
                new MyCodeAction(
                    GetTitle(syntaxKinds, expression.RawKind),
                    c => InvertLogicalAsync(document, expression, c)),
                expression.Span);
        }
            private async Task <bool> TryInitializeAsync(
                SemanticDocument document,
                TextSpan textSpan,
                CancellationToken cancellationToken)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(false);
                }

                Expression = await document.Document.TryGetRelevantNodeAsync <TExpressionSyntax>(textSpan, cancellationToken).ConfigureAwait(false);

                if (Expression == null || CodeRefactoringHelpers.IsNodeUnderselected(Expression, textSpan))
                {
                    return(false);
                }

                var expressionType = Document.SemanticModel.GetTypeInfo(Expression, cancellationToken).Type;

                if (expressionType is IErrorTypeSymbol)
                {
                    return(false);
                }

                var containingType = Expression.AncestorsAndSelf()
                                     .Select(n => Document.SemanticModel.GetDeclaredSymbol(n, cancellationToken))
                                     .OfType <INamedTypeSymbol>()
                                     .FirstOrDefault();

                containingType ??= Document.SemanticModel.Compilation.ScriptClass;

                if (containingType == null || containingType.TypeKind == TypeKind.Interface)
                {
                    return(false);
                }

                if (!CanIntroduceVariable(textSpan.IsEmpty, cancellationToken))
                {
                    return(false);
                }

                IsConstant = IsExpressionConstant(Document, Expression, _service, cancellationToken);

                // Note: the ordering of these clauses are important.  They go, generally, from
                // innermost to outermost order.
                if (IsInQueryContext(cancellationToken))
                {
                    if (CanGenerateInto <TQueryExpressionSyntax>(cancellationToken))
                    {
                        InQueryContext = true;
                        return(true);
                    }

                    return(false);
                }

                if (IsInConstructorInitializerContext(cancellationToken))
                {
                    if (CanGenerateInto <TTypeDeclarationSyntax>(cancellationToken))
                    {
                        InConstructorInitializerContext = true;
                        return(true);
                    }

                    return(false);
                }

                var enclosingBlocks = _service.GetContainingExecutableBlocks(Expression);

                if (enclosingBlocks.Any())
                {
                    // If we're inside a block, then don't even try the other options (like field,
                    // constructor initializer, etc.).  This is desirable behavior.  If we're in a
                    // block in a field, then we're in a lambda, and we want to offer to generate
                    // a local, and not a field.
                    if (IsInBlockContext(cancellationToken))
                    {
                        InBlockContext = true;
                        return(true);
                    }

                    return(false);
                }

                /* NOTE: All checks from this point forward are intentionally ordered to be AFTER the check for Block Context. */

                // If we are inside a block within an Expression bodied member we should generate inside the block,
                // instead of rewriting a concise expression bodied member to its equivalent that has a body with a block.
                if (_service.IsInExpressionBodiedMember(Expression))
                {
                    if (CanGenerateInto <TTypeDeclarationSyntax>(cancellationToken))
                    {
                        InExpressionBodiedMemberContext = true;
                        return(true);
                    }

                    return(false);
                }

                if (_service.IsInAutoPropertyInitializer(Expression))
                {
                    if (CanGenerateInto <TTypeDeclarationSyntax>(cancellationToken))
                    {
                        InAutoPropertyInitializerContext = true;
                        return(true);
                    }

                    return(false);
                }

                if (CanGenerateInto <TTypeDeclarationSyntax>(cancellationToken))
                {
                    if (IsInParameterContext(cancellationToken))
                    {
                        InParameterContext = true;
                        return(true);
                    }
                    else if (IsInFieldContext(cancellationToken))
                    {
                        InFieldContext = true;
                        return(true);
                    }
                    else if (IsInAttributeContext())
                    {
                        InAttributeContext = true;
                        return(true);
                    }
                }

                return(false);
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, textSpan, cancellationToken) = context;
            var possibleExpressions = await context.GetRelevantNodesAsync <TExpressionSyntax>().ConfigureAwait(false);

            var syntaxFacts   = document.GetLanguageService <ISyntaxFactsService>();
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            // let's take the largest (last) StringConcat we can given current textSpan
            var top = possibleExpressions
                      .Where(expr => IsStringConcat(syntaxFacts, expr, semanticModel, cancellationToken))
                      .LastOrDefault();

            if (top == null)
            {
                return;
            }

            // Currently we can concatenate only full subtrees. Therefore we can't support arbitrary selection. We could
            // theoretically support selecting the selections that correspond to full sub-trees (e.g. prefixes of
            // correct length but from UX point of view that it would feel arbitrary).
            // Thus, we only support selection that takes the whole topmost expression. It breaks some leniency around under-selection
            // but it's the best solution so far.
            if (CodeRefactoringHelpers.IsNodeUnderselected(top, textSpan) ||
                IsStringConcat(syntaxFacts, top.Parent, semanticModel, cancellationToken))
            {
                return;
            }

            // Now walk down the concatenation collecting all the pieces that we are
            // concatenating.
            var pieces = new List <SyntaxNode>();

            CollectPiecesDown(syntaxFacts, pieces, top, semanticModel, cancellationToken);

            var stringLiterals = pieces
                                 .Where(x => syntaxFacts.IsStringLiteralExpression(x) || syntaxFacts.IsCharacterLiteralExpression(x))
                                 .ToImmutableArray();

            // If the entire expression is just concatenated strings, then don't offer to
            // make an interpolated string.  The user likely manually split this for
            // readability.
            if (stringLiterals.Length == pieces.Count)
            {
                return;
            }

            var isVerbatimStringLiteral = false;

            if (stringLiterals.Length > 0)
            {
                // Make sure that all the string tokens we're concatenating are the same type
                // of string literal.  i.e. if we have an expression like: @" "" " + " \r\n "
                // then we don't merge this.  We don't want to be munging different types of
                // escape sequences in these strings, so we only support combining the string
                // tokens if they're all the same type.
                var firstStringToken = stringLiterals[0].GetFirstToken();
                isVerbatimStringLiteral = syntaxFacts.IsVerbatimStringLiteral(firstStringToken);
                if (stringLiterals.Any(
                        lit => isVerbatimStringLiteral != syntaxFacts.IsVerbatimStringLiteral(lit.GetFirstToken())))
                {
                    return;
                }
            }

            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var interpolatedString = CreateInterpolatedString(document, isVerbatimStringLiteral, pieces);

            context.RegisterRefactoring(
                new MyCodeAction(
                    _ => UpdateDocumentAsync(document, root, top, interpolatedString)),
                top.Span);
        }
        public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, textSpan, cancellationToken) = context;
            if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles)
            {
                return;
            }

            var expression = await document.TryGetRelevantNodeAsync <TExpressionSyntax>(textSpan, cancellationToken).ConfigureAwait(false);

            if (expression == null || CodeRefactoringHelpers.IsNodeUnderselected(expression, textSpan))
            {
                return;
            }

            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var expressionType = semanticModel.GetTypeInfo(expression, cancellationToken).Type;

            if (expressionType is null or IErrorTypeSymbol)
            {
                return;
            }

            var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>();

            // Need to special case for expressions that are contained within a parameter
            // because it is technically "contained" within a method, but an expression in a parameter does not make
            // sense to introduce.
            var parameterNode = expression.FirstAncestorOrSelf <SyntaxNode>(node => syntaxFacts.IsParameter(node));

            if (parameterNode is not null)
            {
                return;
            }

            var generator        = SyntaxGenerator.GetGenerator(document);
            var containingMethod = expression.FirstAncestorOrSelf <SyntaxNode>(node => generator.GetParameterListNode(node) is not null);

            if (containingMethod is null)
            {
                return;
            }

            var containingSymbol = semanticModel.GetDeclaredSymbol(containingMethod, cancellationToken);

            if (containingSymbol is not IMethodSymbol methodSymbol)
            {
                return;
            }

            // Code actions for trampoline and overloads will not be offered if the method is a constructor.
            // Code actions for overloads will not be offered if the method if the method is a local function.
            var methodKind = methodSymbol.MethodKind;

            if (methodKind is not(MethodKind.Ordinary or MethodKind.LocalFunction or MethodKind.Constructor))
            {
                return;
            }

            if (IsDestructor(methodSymbol))
            {
                return;
            }

            var actions = await GetActionsAsync(document, expression, methodSymbol, containingMethod, cancellationToken).ConfigureAwait(false);

            if (actions is null)
            {
                return;
            }

            var singleLineExpression = syntaxFacts.ConvertToSingleLine(expression);
            var nodeString           = singleLineExpression.ToString();

            context.RegisterRefactoring(new CodeActionWithNestedActions(
                                            string.Format(FeaturesResources.Introduce_parameter_for_0, nodeString), actions.Value.actions, isInlinable: false), textSpan);
            context.RegisterRefactoring(new CodeActionWithNestedActions(
                                            string.Format(FeaturesResources.Introduce_parameter_for_all_occurrences_of_0, nodeString), actions.Value.actionsAllOccurrences, isInlinable: false), textSpan);
        }
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var(document, textSpan, cancellationToken) = context;
            var possibleExpressions = await context.GetRelevantNodesAsync <TExpressionSyntax>().ConfigureAwait(false);

            var syntaxFacts   = document.GetRequiredLanguageService <ISyntaxFactsService>();
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            // let's take the largest (last) StringConcat we can given current textSpan
            var top = possibleExpressions
                      .Where(expr => IsStringConcat(syntaxFacts, expr, semanticModel, cancellationToken))
                      .LastOrDefault();

            if (top == null)
            {
                return;
            }

            if (!syntaxFacts.SupportsConstantInterpolatedStrings(document.Project.ParseOptions !))
            {
                // if there is a const keyword, the refactoring shouldn't show because interpolated string is not const string
                var declarator = top.FirstAncestorOrSelf <SyntaxNode>(syntaxFacts.IsVariableDeclarator);
                if (declarator != null)
                {
                    var generator = SyntaxGenerator.GetGenerator(document);
                    if (generator.GetModifiers(declarator).IsConst)
                    {
                        return;
                    }
                }
            }

            // Currently we can concatenate only full subtrees. Therefore we can't support arbitrary selection. We could
            // theoretically support selecting the selections that correspond to full sub-trees (e.g. prefixes of
            // correct length but from UX point of view that it would feel arbitrary).
            // Thus, we only support selection that takes the whole topmost expression. It breaks some leniency around under-selection
            // but it's the best solution so far.
            if (CodeRefactoringHelpers.IsNodeUnderselected(top, textSpan) ||
                IsStringConcat(syntaxFacts, top.Parent, semanticModel, cancellationToken))
            {
                return;
            }

            // Now walk down the concatenation collecting all the pieces that we are
            // concatenating.
            using var _ = ArrayBuilder <SyntaxNode> .GetInstance(out var pieces);

            CollectPiecesDown(syntaxFacts, pieces, top, semanticModel, cancellationToken);

            var stringLiterals = pieces
                                 .Where(x => syntaxFacts.IsStringLiteralExpression(x) || syntaxFacts.IsCharacterLiteralExpression(x))
                                 .ToImmutableArray();

            // If the entire expression is just concatenated strings, then don't offer to
            // make an interpolated string.  The user likely manually split this for
            // readability.
            if (stringLiterals.Length == pieces.Count)
            {
                return;
            }

            var isVerbatimStringLiteral = false;

            if (stringLiterals.Length > 0)
            {
                // Make sure that all the string tokens we're concatenating are the same type
                // of string literal.  i.e. if we have an expression like: @" "" " + " \r\n "
                // then we don't merge this.  We don't want to be munging different types of
                // escape sequences in these strings, so we only support combining the string
                // tokens if they're all the same type.
                var firstStringToken = stringLiterals[0].GetFirstToken();
                isVerbatimStringLiteral = syntaxFacts.IsVerbatimStringLiteral(firstStringToken);
                if (stringLiterals.Any(