private static void ReplaceSetReferences( string propertyName, bool nameChanged, IEnumerable <ReferenceLocation> setReferences, SyntaxNode root, SyntaxEditor editor, IReplaceMethodWithPropertyService service, CancellationToken cancellationToken) { if (setReferences != null) { foreach (var referenceLocation in setReferences) { cancellationToken.ThrowIfCancellationRequested(); var location = referenceLocation.Location; var nameToken = root.FindToken(location.SourceSpan.Start); if (referenceLocation.IsImplicit) { // Warn the user that we can't properly replace this method with a property. editor.ReplaceNode(nameToken.Parent, nameToken.Parent.WithAdditionalAnnotations( ConflictAnnotation.Create(FeaturesResources.Method_referenced_implicitly))); } else { service.ReplaceSetReference(editor, nameToken, propertyName, nameChanged); } } } }
public void ReplaceInvocation(SyntaxEditor editor, SyntaxToken nameToken, string propertyName, bool nameChanged, Action <SyntaxEditor, InvocationExpressionSyntax, SimpleNameSyntax, SimpleNameSyntax> replace) { if (nameToken.Kind() != SyntaxKind.IdentifierToken) { return; } var nameNode = nameToken.Parent as IdentifierNameSyntax; if (nameNode == null) { return; } var newName = nameChanged ? SyntaxFactory.IdentifierName(SyntaxFactory.Identifier(propertyName).WithTriviaFrom(nameToken)) : nameNode; var invocation = nameNode?.FirstAncestorOrSelf <InvocationExpressionSyntax>(); var invocationExpression = invocation?.Expression; if (!IsInvocationName(nameNode, invocationExpression)) { // Wasn't invoked. Change the name, but report a conflict. var annotation = ConflictAnnotation.Create(FeaturesResources.NonInvokedMethodCannotBeReplacedWithProperty); editor.ReplaceNode(nameNode, newName.WithIdentifier(newName.Identifier.WithAdditionalAnnotations(annotation))); return; } // It was invoked. Remove the invocation, and also change the name if necessary. replace(editor, invocation, nameNode, newName); }
private static SyntaxToken AddConflictAnnotation(SyntaxToken token, string?conflictMessage) { if (conflictMessage != null) { token = token.WithAdditionalAnnotations(ConflictAnnotation.Create(conflictMessage)); } return(token); }
/// <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 identifier = pattern.Identifier; var annotation = ConflictAnnotation.Create(CSharpFeaturesResources.Conflict_s_detected); var newIdentifier = identifier.WithAdditionalAnnotations(annotation); replacements.Add(pattern, pattern.WithIdentifier(newIdentifier)); break; } } return(statement.ReplaceNodes(replacements.Keys, (orig, partiallyReplaced) => replacements[orig])); }
private static async Task ReplaceReferencesAsync( Document originalDocument, IEnumerable <ValueTuple <IPropertySymbol, ReferenceLocation> > references, IDictionary <IPropertySymbol, IFieldSymbol> propertyToBackingField, SyntaxNode root, SyntaxEditor editor, IReplacePropertyWithMethodsService service, string desiredGetMethodName, string desiredSetMethodName, CancellationToken cancellationToken) { if (references != null) { foreach (var tuple in references) { cancellationToken.ThrowIfCancellationRequested(); var property = tuple.Item1; var referenceLocation = tuple.Item2; var location = referenceLocation.Location; var nameToken = root.FindToken(location.SourceSpan.Start); if (referenceLocation.IsImplicit) { // Warn the user that we can't properly replace this property with a method. editor.ReplaceNode(nameToken.Parent, nameToken.Parent.WithAdditionalAnnotations( ConflictAnnotation.Create(FeaturesResources.Property_referenced_implicitly))); } else { var fieldSymbol = propertyToBackingField.GetValueOrDefault(tuple.Item1); await service.ReplaceReferenceAsync( originalDocument, editor, nameToken, property, fieldSymbol, desiredGetMethodName, desiredSetMethodName, cancellationToken).ConfigureAwait(false); } } } }
public async Task <Solution> AddParameterAsync( Document invocationDocument, IMethodSymbol method, ITypeSymbol newParamaterType, RefKind refKind, string parameterName, int?newParameterIndex, bool fixAllReferences, CancellationToken cancellationToken) { var solution = invocationDocument.Project.Solution; 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 = 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, newParamaterType, refKind, parameterMustBeOptional, parameterName); var argumentInitializer = parameterMustBeOptional ? generator.DefaultExpression(newParamaterType) : 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, insertionIndex, parameterDeclaration, cancellationToken); } var newRoot = editor.GetChangedRoot(); solution = solution.WithDocumentSyntaxRoot(document.Id, newRoot); } return(solution); }
private static SyntaxAnnotation CreateConflictAnnotation() { return(ConflictAnnotation.Create(CSharpFeaturesResources.Conflict_s_detected)); }
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()); var originalNodesEnum = originalIdentifierNodes.GetEnumerator(); var inlinedNodesEnum = inlinedExprNodes.GetEnumerator(); Dictionary <SyntaxNode, SyntaxNode> replacementNodesWithChangedSemantics = null; while (originalNodesEnum.MoveNext()) { inlinedNodesEnum.MoveNext(); var originalNode = originalNodesEnum.Current; // expressionToInline is Parenthesized prior to replacement, so get the parenting parenthesized expression. var inlinedNode = (ExpressionSyntax)inlinedNodesEnum.Current.AsNode().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 innerInitializerInInlineNode = (ExpressionSyntax)inlinedNode.GetAnnotatedNodesAndTokens(InitializerAnnotation).First().AsNode(); var newInializerSymbolInfo = 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, newInializerSymbolInfo, performEquivalenceCheck: true)) { newInializerSymbolInfo = newSemanticModelForInlinedDocument.GetSymbolInfo(inlinedNode, cancellationToken); if (!SpeculationAnalyzer.SymbolInfosAreCompatible(originalInitializerSymbolInfo, newInializerSymbolInfo, 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)); }
// 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));
private static SyntaxAnnotation CreateConflictAnnotation() { return(ConflictAnnotation.Create(GettextCatalog.GetString("Conflict(s) detected."))); }