예제 #1
0
 private static SyntaxAnnotation CreateConflictAnnotation()
 {
     return(ConflictAnnotation.Create(CSharpFeaturesResources.Conflict_s_detected));
 }
예제 #2
0
        private async Task <Solution> FixAsync(
            Document invocationDocument,
            IMethodSymbol method,
            TArgumentSyntax argument,
            SeparatedSyntaxList <TArgumentSyntax> argumentList,
            bool fixAllReferences,
            CancellationToken cancellationToken)
        {
            var solution = invocationDocument.Project.Solution;

            var(argumentType, refKind) = await GetArgumentTypeAndRefKindAsync(invocationDocument, argument, cancellationToken).ConfigureAwait(false);

            // The argumentNameSuggestion is the base for the parameter name.
            // For each method declaration the name is made unique to avoid name collisions.
            var(argumentNameSuggestion, isNamedArgument) = await GetNameSuggestionForArgumentAsync(
                invocationDocument, argument, cancellationToken).ConfigureAwait(false);

            var referencedSymbols = fixAllReferences
                ? await FindMethodDeclarationReferences(invocationDocument, method, cancellationToken).ConfigureAwait(false)
                : method.GetAllMethodSymbolsOfPartialParts();

            var anySymbolReferencesNotInSource = referencedSymbols.Any(symbol => !symbol.IsFromSource());
            var locationsInSource = referencedSymbols.Where(symbol => symbol.IsFromSource());

            // Indexing Locations[0] is valid because IMethodSymbols have one location at most
            // and IsFromSource() tests if there is at least one location.
            var locationsByDocument = locationsInSource.ToLookup(declarationLocation
                                                                 => solution.GetDocument(declarationLocation.Locations[0].SourceTree));

            foreach (var documentLookup in locationsByDocument)
            {
                var document    = documentLookup.Key;
                var syntaxFacts = document.GetLanguageService <ISyntaxFactsService>();
                var syntaxRoot  = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

                var editor    = new SyntaxEditor(syntaxRoot, solution.Workspace);
                var generator = editor.Generator;
                foreach (var methodDeclaration in documentLookup)
                {
                    var methodNode         = syntaxRoot.FindNode(methodDeclaration.Locations[0].SourceSpan);
                    var existingParameters = generator.GetParameters(methodNode);
                    var insertionIndex     = isNamedArgument
                        ? existingParameters.Count
                        : argumentList.IndexOf(argument);

                    // if the preceding parameter is optional, the new parameter must also be optional
                    // see also BC30202 and CS1737
                    var parameterMustBeOptional = insertionIndex > 0 &&
                                                  syntaxFacts.GetDefaultOfParameter(existingParameters[insertionIndex - 1]) != null;

                    var parameterSymbol = CreateParameterSymbol(
                        methodDeclaration, argumentType, refKind, parameterMustBeOptional, argumentNameSuggestion);

                    var argumentInitializer  = parameterMustBeOptional ? generator.DefaultExpression(argumentType) : null;
                    var parameterDeclaration = generator.ParameterDeclaration(parameterSymbol, argumentInitializer)
                                               .WithAdditionalAnnotations(Formatter.Annotation);
                    if (anySymbolReferencesNotInSource && methodDeclaration == method)
                    {
                        parameterDeclaration = parameterDeclaration.WithAdditionalAnnotations(
                            ConflictAnnotation.Create(FeaturesResources.Related_method_signatures_found_in_metadata_will_not_be_updated));
                    }


                    if (method.MethodKind == MethodKind.ReducedExtension)
                    {
                        insertionIndex++;
                    }

                    AddParameter(
                        syntaxFacts, editor, methodNode, argument,
                        insertionIndex, parameterDeclaration, cancellationToken);
                }

                var newRoot = editor.GetChangedRoot();
                solution = solution.WithDocumentSyntaxRoot(document.Id, newRoot);
            }

            return(solution);
        }
        private static async Task <Document> DetectSemanticConflicts(
            Document inlinedDocument,
            SemanticModel newSemanticModelForInlinedDocument,
            SemanticModel semanticModelBeforeInline,
            SymbolInfo originalInitializerSymbolInfo,
            CancellationToken cancellationToken)
        {
            // In this method we detect if inlining the expression introduced the following semantic change:
            // The symbol info associated with any of the inlined expressions does not match the symbol info for original initializer expression prior to inline.

            // If any semantic changes were introduced by inlining, we update the document with conflict annotations.
            // Otherwise we return the given inlined document without any changes.

            var syntaxRootBeforeInline = await semanticModelBeforeInline.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            // Get all the identifier nodes which were replaced with inlined expression.
            var originalIdentifierNodes = FindReferenceAnnotatedNodes(syntaxRootBeforeInline);

            if (originalIdentifierNodes.IsEmpty())
            {
                // No conflicts
                return(inlinedDocument);
            }

            // Get all the inlined expression nodes.
            var syntaxRootAfterInline = await inlinedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var inlinedExprNodes = syntaxRootAfterInline.GetAnnotatedNodesAndTokens(ExpressionToInlineAnnotation);

            Debug.Assert(originalIdentifierNodes.Count() == inlinedExprNodes.Count());

            Dictionary <SyntaxNode, SyntaxNode> replacementNodesWithChangedSemantics = null;

            using (var originalNodesEnum = originalIdentifierNodes.GetEnumerator())
            {
                using (var inlinedNodesOrTokensEnum = inlinedExprNodes.GetEnumerator())
                {
                    while (originalNodesEnum.MoveNext())
                    {
                        inlinedNodesOrTokensEnum.MoveNext();
                        var originalNode = originalNodesEnum.Current;

                        // expressionToInline is Parenthesized prior to replacement, so get the parenting parenthesized expression.
                        var inlinedNode = (ExpressionSyntax)inlinedNodesOrTokensEnum.Current.Parent;
                        Debug.Assert(inlinedNode.IsKind(SyntaxKind.ParenthesizedExpression));

                        // inlinedNode is the expanded form of the actual initializer expression in the original document.
                        // We have annotated the inner initializer with a special syntax annotation "InitializerAnnotation".
                        // Get this annotated node and compute the symbol info for this node in the inlined document.
                        var innerInitializerInInlineNodeOrToken = inlinedNode.GetAnnotatedNodesAndTokens(InitializerAnnotation).First();

                        ExpressionSyntax innerInitializerInInlineNode = (ExpressionSyntax)(innerInitializerInInlineNodeOrToken.IsNode ?
                                                                                           innerInitializerInInlineNodeOrToken.AsNode() :
                                                                                           innerInitializerInInlineNodeOrToken.AsToken().Parent);
                        var newInitializerSymbolInfo = newSemanticModelForInlinedDocument.GetSymbolInfo(innerInitializerInInlineNode, cancellationToken);

                        // Verification: The symbol info associated with any of the inlined expressions does not match the symbol info for original initializer expression prior to inline.
                        if (!SpeculationAnalyzer.SymbolInfosAreCompatible(originalInitializerSymbolInfo, newInitializerSymbolInfo, performEquivalenceCheck: true))
                        {
                            newInitializerSymbolInfo = newSemanticModelForInlinedDocument.GetSymbolInfo(inlinedNode, cancellationToken);
                            if (!SpeculationAnalyzer.SymbolInfosAreCompatible(originalInitializerSymbolInfo, newInitializerSymbolInfo, performEquivalenceCheck: true))
                            {
                                if (replacementNodesWithChangedSemantics == null)
                                {
                                    replacementNodesWithChangedSemantics = new Dictionary <SyntaxNode, SyntaxNode>();
                                }

                                replacementNodesWithChangedSemantics.Add(inlinedNode, originalNode);
                            }
                        }
                    }
                }
            }

            if (replacementNodesWithChangedSemantics == null)
            {
                // No conflicts.
                return(inlinedDocument);
            }

            // Replace the conflicting inlined nodes with the original nodes annotated with conflict annotation.
            Func <SyntaxNode, SyntaxNode, SyntaxNode> conflictAnnotationAdder =
                (SyntaxNode oldNode, SyntaxNode newNode) =>
                newNode.WithAdditionalAnnotations(ConflictAnnotation.Create(CSharpFeaturesResources.ConflictsDetected));

            return(await inlinedDocument.ReplaceNodesAsync(replacementNodesWithChangedSemantics.Keys, conflictAnnotationAdder, cancellationToken).ConfigureAwait(false));
        }
            /// <summary>
            /// If the statement has an `out var` declaration expression for a variable which
            /// needs to be removed, we need to turn it into a plain `out` parameter, so that
            /// it doesn't declare a duplicate variable.
            /// If the statement has a pattern declaration (such as `3 is int i`) for a variable
            /// which needs to be removed, we will annotate it as a conflict, since we don't have
            /// a better refactoring.
            /// </summary>
            private StatementSyntax FixDeclarationExpressionsAndDeclarationPatterns(StatementSyntax statement,
                                                                                    HashSet <SyntaxAnnotation> variablesToRemove)
            {
                var replacements = new Dictionary <SyntaxNode, SyntaxNode>();

                var declarations = statement.DescendantNodes()
                                   .Where(n => n.IsKind(SyntaxKind.DeclarationExpression, SyntaxKind.DeclarationPattern));

                foreach (var node in declarations)
                {
                    switch (node.Kind())
                    {
                    case SyntaxKind.DeclarationExpression:
                    {
                        var declaration = (DeclarationExpressionSyntax)node;
                        if (declaration.Designation.Kind() != SyntaxKind.SingleVariableDesignation)
                        {
                            break;
                        }

                        var designation = (SingleVariableDesignationSyntax)declaration.Designation;
                        var name        = designation.Identifier.ValueText;
                        if (variablesToRemove.HasSyntaxAnnotation(designation))
                        {
                            var newLeadingTrivia = new SyntaxTriviaList();
                            newLeadingTrivia = newLeadingTrivia.AddRange(declaration.Type.GetLeadingTrivia());
                            newLeadingTrivia = newLeadingTrivia.AddRange(declaration.Type.GetTrailingTrivia());
                            newLeadingTrivia = newLeadingTrivia.AddRange(designation.GetLeadingTrivia());

                            replacements.Add(declaration, SyntaxFactory.IdentifierName(designation.Identifier)
                                             .WithLeadingTrivia(newLeadingTrivia));
                        }

                        break;
                    }

                    case SyntaxKind.DeclarationPattern:
                    {
                        var pattern = (DeclarationPatternSyntax)node;
                        if (!variablesToRemove.HasSyntaxAnnotation(pattern))
                        {
                            break;
                        }

                        // We don't have a good refactoring for this, so we just annotate the conflict
                        // For instance, when a local declared by a pattern declaration (`3 is int i`) is
                        // used outside the block we're trying to extract.
                        var designation = pattern.Designation as SingleVariableDesignationSyntax;
                        if (designation == null)
                        {
                            break;
                        }

                        var identifier     = designation.Identifier;
                        var annotation     = ConflictAnnotation.Create(CSharpFeaturesResources.Conflict_s_detected);
                        var newIdentifier  = identifier.WithAdditionalAnnotations(annotation);
                        var newDesignation = designation.WithIdentifier(newIdentifier);
                        replacements.Add(pattern, pattern.WithDesignation(newDesignation));

                        break;
                    }
                    }
                }

                return(statement.ReplaceNodes(replacements.Keys, (orig, partiallyReplaced) => replacements[orig]));
            }
예제 #5
0
 // Replace the conflicting inlined nodes with the original nodes annotated with conflict annotation.
 static SyntaxNode conflictAnnotationAdder(SyntaxNode oldNode, SyntaxNode newNode) =>
 newNode.WithAdditionalAnnotations(ConflictAnnotation.Create(CSharpFeaturesResources.Conflict_s_detected));
예제 #6
0
        public Task <object> CreateChangedDocumentPreviewViewAsync(Document oldDocument, Document newDocument, double zoomLevel, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            // Note: We don't use the original buffer that is associated with oldDocument
            // (and currently open in the editor) for oldBuffer below. This is because oldBuffer
            // will be used inside a projection buffer inside our inline diff preview below
            // and platform's implementation currently has a bug where projection buffers
            // are being leaked. This leak means that if we use the original buffer that is
            // currently visible in the editor here, the projection buffer span calculation
            // would be triggered every time user changes some code in this buffer (even though
            // the diff view would long have been dismissed by the time user edits the code)
            // resulting in crashes. Instead we create a new buffer from the same content.
            // TODO: We could use ITextBufferCloneService instead here to clone the original buffer.
            var oldBuffer = CreateNewBuffer(oldDocument, cancellationToken);
            var newBuffer = CreateNewBuffer(newDocument, cancellationToken);

            // Convert the diffs to be line based.
            // Compute the diffs between the old text and the new.
            var diffResult = ComputeEditDifferences(oldDocument, newDocument, cancellationToken);

            // Need to show the spans in the right that are different.
            // We also need to show the spans that are in conflict.
            var originalSpans = GetOriginalSpans(diffResult, cancellationToken);
            var changedSpans  = GetChangedSpans(diffResult, cancellationToken);
            var description   = default(string);
            var allSpans      = default(NormalizedSpanCollection);

            if (newDocument.SupportsSyntaxTree)
            {
                var newRoot              = newDocument.GetSyntaxRootSynchronously(cancellationToken);
                var conflictNodes        = newRoot.GetAnnotatedNodesAndTokens(ConflictAnnotation.Kind);
                var conflictSpans        = conflictNodes.Select(n => n.Span.ToSpan()).ToList();
                var conflictDescriptions = conflictNodes.SelectMany(n => n.GetAnnotations(ConflictAnnotation.Kind))
                                           .Select(a => $"❌ {ConflictAnnotation.GetDescription(a)}")
                                           .Distinct();

                var warningNodes        = newRoot.GetAnnotatedNodesAndTokens(WarningAnnotation.Kind);
                var warningSpans        = warningNodes.Select(n => n.Span.ToSpan()).ToList();
                var warningDescriptions = warningNodes.SelectMany(n => n.GetAnnotations(WarningAnnotation.Kind))
                                          .Select(a => $"⚠ {WarningAnnotation.GetDescription(a)}")
                                          .Distinct();

                var suppressDiagnosticsNodes = newRoot.GetAnnotatedNodesAndTokens(SuppressDiagnosticsAnnotation.Kind);
                var suppressDiagnosticsSpans = suppressDiagnosticsNodes.Select(n => n.Span.ToSpan()).ToList();
                AttachAnnotationsToBuffer(newBuffer, conflictSpans, warningSpans, suppressDiagnosticsSpans);

                description = conflictSpans.Count == 0 && warningSpans.Count == 0
                    ? null
                    : string.Join(Environment.NewLine, conflictDescriptions.Concat(warningDescriptions));
                allSpans = new NormalizedSpanCollection(conflictSpans.Concat(warningSpans).Concat(changedSpans));
            }
            else
            {
                allSpans = new NormalizedSpanCollection(changedSpans);
            }

            var originalLineSpans = CreateLineSpans(oldBuffer.CurrentSnapshot, originalSpans, cancellationToken);
            var changedLineSpans  = CreateLineSpans(newBuffer.CurrentSnapshot, allSpans, cancellationToken);

            if (!originalLineSpans.Any())
            {
                // This means that we have no differences (likely because of conflicts).
                // In such cases, use the same spans for the left (old) buffer as the right (new) buffer.
                originalLineSpans = changedLineSpans;
            }

            // Create PreviewWorkspaces around the buffers to be displayed on the left and right
            // so that all IDE services (colorizer, squiggles etc.) light up in these buffers.
            var leftDocument = oldDocument.Project
                               .RemoveDocument(oldDocument.Id)
                               .AddDocument(oldDocument.Name, oldBuffer.AsTextContainer().CurrentText, oldDocument.Folders, oldDocument.FilePath);
            var leftWorkspace = new PreviewWorkspace(leftDocument.Project.Solution);

            leftWorkspace.OpenDocument(leftDocument.Id);

            var rightWorkspace = new PreviewWorkspace(
                newDocument.WithText(newBuffer.AsTextContainer().CurrentText).Project.Solution);

            rightWorkspace.OpenDocument(newDocument.Id);

            return(CreateChangedDocumentViewAsync(
                       oldBuffer, newBuffer, description, originalLineSpans, changedLineSpans,
                       leftWorkspace, rightWorkspace, zoomLevel, cancellationToken));
        }
예제 #7
0
 private static SyntaxAnnotation CreateConflictAnnotation()
 {
     return(ConflictAnnotation.Create(GettextCatalog.GetString("Conflict(s) detected.")));
 }
예제 #8
0
        /// <summary>
        /// Adds a parameter to a method.
        /// </summary>
        /// <param name="newParameterIndex"><see langword="null"/> to add as the final parameter</param>
        /// <returns></returns>
        public static async Task <Solution> AddParameterAsync(
            Document invocationDocument,
            IMethodSymbol method,
            ITypeSymbol newParameterType,
            RefKind refKind,
            string parameterName,
            int?newParameterIndex,
            bool fixAllReferences,
            CancellationToken cancellationToken)
        {
            var solution = invocationDocument.Project.Solution;

            var referencedSymbols = fixAllReferences
                ? await FindMethodDeclarationReferencesAsync(invocationDocument, method, cancellationToken).ConfigureAwait(false)
                : method.GetAllMethodSymbolsOfPartialParts();

            var anySymbolReferencesNotInSource = referencedSymbols.Any(symbol => !symbol.IsFromSource());
            var locationsInSource = referencedSymbols.Where(symbol => symbol.IsFromSource());

            // Indexing Locations[0] is valid because IMethodSymbols have one location at most
            // and IsFromSource() tests if there is at least one location.
            var locationsByDocument = locationsInSource.ToLookup(declarationLocation
                                                                 => solution.GetRequiredDocument(declarationLocation.Locations[0].SourceTree !));

            foreach (var documentLookup in locationsByDocument)
            {
                var document    = documentLookup.Key;
                var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>();
                var syntaxRoot  = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

                var editor    = new SyntaxEditor(syntaxRoot, solution.Workspace.Services);
                var generator = editor.Generator;
                foreach (var methodDeclaration in documentLookup)
                {
                    var methodNode         = syntaxRoot.FindNode(methodDeclaration.Locations[0].SourceSpan, getInnermostNodeForTie: true);
                    var existingParameters = generator.GetParameters(methodNode);
                    var insertionIndex     = newParameterIndex ?? existingParameters.Count;

                    // if the preceding parameter is optional, the new parameter must also be optional
                    // see also BC30202 and CS1737
                    var parameterMustBeOptional = insertionIndex > 0 &&
                                                  syntaxFacts.GetDefaultOfParameter(existingParameters[insertionIndex - 1]) != null;

                    var parameterSymbol = CreateParameterSymbol(
                        methodDeclaration, newParameterType, refKind, parameterMustBeOptional, parameterName);

                    var argumentInitializer  = parameterMustBeOptional ? generator.DefaultExpression(newParameterType) : null;
                    var parameterDeclaration = generator.ParameterDeclaration(parameterSymbol, argumentInitializer)
                                               .WithAdditionalAnnotations(Formatter.Annotation);
                    if (anySymbolReferencesNotInSource && methodDeclaration == method)
                    {
                        parameterDeclaration = parameterDeclaration.WithAdditionalAnnotations(
                            ConflictAnnotation.Create(FeaturesResources.Related_method_signatures_found_in_metadata_will_not_be_updated));
                    }

                    if (method.MethodKind == MethodKind.ReducedExtension && insertionIndex < existingParameters.Count)
                    {
                        insertionIndex++;
                    }

                    AddParameterEditor.AddParameter(syntaxFacts, editor, methodNode, insertionIndex, parameterDeclaration, cancellationToken);
                }

                var newRoot = editor.GetChangedRoot();
                solution = solution.WithDocumentSyntaxRoot(document.Id, newRoot);
            }

            return(solution);
        }