private static SyntaxAnnotation CreateConflictAnnotation() { return(ConflictAnnotation.Create(CSharpFeaturesResources.Conflict_s_detected)); }
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])); }
// 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));
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)); }
private static SyntaxAnnotation CreateConflictAnnotation() { return(ConflictAnnotation.Create(GettextCatalog.GetString("Conflict(s) detected."))); }
/// <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); }