src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs (1,036 lines of code) (raw):

// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Rename.ConflictEngine; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Rename { [ExportLanguageService(typeof(IRenameRewriterLanguageService), LanguageNames.CSharp), Shared] internal class CSharpRenameConflictLanguageService : IRenameRewriterLanguageService { #region "Annotation" public SyntaxNode AnnotateAndRename(RenameRewriterParameters parameters) { var renameAnnotationRewriter = new RenameRewriter(parameters); return renameAnnotationRewriter.Visit(parameters.SyntaxRoot); } private class RenameRewriter : CSharpSyntaxRewriter { private readonly DocumentId _documentId; private readonly RenameAnnotation _renameRenamableSymbolDeclaration; private readonly Solution _solution; private readonly string _replacementText; private readonly string _originalText; private readonly ICollection<string> _possibleNameConflicts; private readonly Dictionary<TextSpan, RenameLocation> _renameLocations; private readonly ISet<TextSpan> _conflictLocations; private readonly SemanticModel _semanticModel; private readonly CancellationToken _cancellationToken; private readonly ISymbol _renamedSymbol; private readonly IAliasSymbol _aliasSymbol; private readonly Location _renamableDeclarationLocation; private readonly RenamedSpansTracker _renameSpansTracker; private readonly bool _isVerbatim; private readonly bool _replacementTextValid; private readonly bool _isRenamingInStrings; private readonly bool _isRenamingInComments; private readonly ISet<TextSpan> _stringAndCommentTextSpans; private readonly ISimplificationService _simplificationService; private readonly ISemanticFactsService _semanticFactsService; private readonly HashSet<SyntaxToken> _annotatedIdentifierTokens = new HashSet<SyntaxToken>(); private readonly HashSet<InvocationExpressionSyntax> _invocationExpressionsNeedingConflictChecks = new HashSet<InvocationExpressionSyntax>(); private readonly AnnotationTable<RenameAnnotation> _renameAnnotations; public bool AnnotateForComplexification { get { return _skipRenameForComplexification > 0 && !_isProcessingComplexifiedSpans; } } private int _skipRenameForComplexification; private bool _isProcessingComplexifiedSpans; private List<(TextSpan oldSpan, TextSpan newSpan)> _modifiedSubSpans; private SemanticModel _speculativeModel; private int _isProcessingTrivia; private void AddModifiedSpan(TextSpan oldSpan, TextSpan newSpan) { newSpan = new TextSpan(oldSpan.Start, newSpan.Length); if (!_isProcessingComplexifiedSpans) { _renameSpansTracker.AddModifiedSpan(_documentId, oldSpan, newSpan); } else { _modifiedSubSpans.Add((oldSpan, newSpan)); } } public RenameRewriter(RenameRewriterParameters parameters) : base(visitIntoStructuredTrivia: true) { _documentId = parameters.Document.Id; _renameRenamableSymbolDeclaration = parameters.RenamedSymbolDeclarationAnnotation; _solution = parameters.OriginalSolution; _replacementText = parameters.ReplacementText; _originalText = parameters.OriginalText; _possibleNameConflicts = parameters.PossibleNameConflicts; _renameLocations = parameters.RenameLocations; _conflictLocations = parameters.ConflictLocationSpans; _cancellationToken = parameters.CancellationToken; _semanticModel = parameters.SemanticModel; _renamedSymbol = parameters.RenameSymbol; _replacementTextValid = parameters.ReplacementTextValid; _renameSpansTracker = parameters.RenameSpansTracker; _isRenamingInStrings = parameters.OptionSet.GetOption(RenameOptions.RenameInStrings); _isRenamingInComments = parameters.OptionSet.GetOption(RenameOptions.RenameInComments); _stringAndCommentTextSpans = parameters.StringAndCommentTextSpans; _renameAnnotations = parameters.RenameAnnotations; _aliasSymbol = _renamedSymbol as IAliasSymbol; _renamableDeclarationLocation = _renamedSymbol.Locations.FirstOrDefault(loc => loc.IsInSource && loc.SourceTree == _semanticModel.SyntaxTree); _isVerbatim = _replacementText.StartsWith("@", StringComparison.Ordinal); _simplificationService = parameters.Document.Project.LanguageServices.GetService<ISimplificationService>(); _semanticFactsService = parameters.Document.Project.LanguageServices.GetService<ISemanticFactsService>(); } public override SyntaxNode Visit(SyntaxNode node) { if (node == null) { return node; } var isInConflictLambdaBody = false; var lambdas = node.GetAncestorsOrThis(n => n is SimpleLambdaExpressionSyntax || n is ParenthesizedLambdaExpressionSyntax); if (lambdas.Count() != 0) { foreach (var lambda in lambdas) { if (_conflictLocations.Any(cf => cf.Contains(lambda.Span))) { isInConflictLambdaBody = true; break; } } } var shouldComplexifyNode = ShouldComplexifyNode(node, isInConflictLambdaBody); SyntaxNode result; // in case the current node was identified as being a complexification target of // a previous node, we'll handle it accordingly. if (shouldComplexifyNode) { _skipRenameForComplexification++; result = base.Visit(node); _skipRenameForComplexification--; result = Complexify(node, result); } else { result = base.Visit(node); } return result; } private bool ShouldComplexifyNode(SyntaxNode node, bool isInConflictLambdaBody) { return !isInConflictLambdaBody && _skipRenameForComplexification == 0 && !_isProcessingComplexifiedSpans && _conflictLocations.Contains(node.Span) && (node is AttributeSyntax || node is AttributeArgumentSyntax || node is ConstructorInitializerSyntax || node is ExpressionSyntax || node is FieldDeclarationSyntax || node is StatementSyntax || node is CrefSyntax || node is XmlNameAttributeSyntax || node is TypeConstraintSyntax || node is BaseTypeSyntax); } public override SyntaxToken VisitToken(SyntaxToken token) { var shouldCheckTrivia = _stringAndCommentTextSpans.Contains(token.Span); _isProcessingTrivia += shouldCheckTrivia ? 1 : 0; var newToken = base.VisitToken(token); _isProcessingTrivia -= shouldCheckTrivia ? 1 : 0; // Handle Alias annotations newToken = UpdateAliasAnnotation(newToken); // Rename matches in strings and comments newToken = RenameWithinToken(token, newToken); // We don't want to annotate XmlName with RenameActionAnnotation if (newToken.Parent.IsKind(SyntaxKind.XmlName)) { return newToken; } bool isRenameLocation = IsRenameLocation(token); // if this is a reference location, or the identifier token's name could possibly // be a conflict, we need to process this token var isOldText = token.ValueText == _originalText; var tokenNeedsConflictCheck = isRenameLocation || token.ValueText == _replacementText || isOldText || _possibleNameConflicts.Contains(token.ValueText) || IsPossiblyDestructorConflict(token, _replacementText); if (tokenNeedsConflictCheck) { newToken = RenameAndAnnotateAsync(token, newToken, isRenameLocation, isOldText).WaitAndGetResult_CanCallOnBackground(_cancellationToken); if (!_isProcessingComplexifiedSpans) { _invocationExpressionsNeedingConflictChecks.AddRange(token.GetAncestors<InvocationExpressionSyntax>()); } } return newToken; } private bool IsPossiblyDestructorConflict(SyntaxToken token, string replacementText) { return _replacementText == "Finalize" && token.IsKind(SyntaxKind.IdentifierToken) && token.Parent.IsKind(SyntaxKind.DestructorDeclaration); } private SyntaxNode Complexify(SyntaxNode originalNode, SyntaxNode newNode) { _isProcessingComplexifiedSpans = true; _modifiedSubSpans = new List<(TextSpan oldSpan, TextSpan newSpan)>(); var annotation = new SyntaxAnnotation(); newNode = newNode.WithAdditionalAnnotations(annotation); var speculativeTree = originalNode.SyntaxTree.GetRoot(_cancellationToken).ReplaceNode(originalNode, newNode); newNode = speculativeTree.GetAnnotatedNodes<SyntaxNode>(annotation).First(); _speculativeModel = GetSemanticModelForNode(newNode, _semanticModel); Debug.Assert(_speculativeModel != null, "expanding a syntax node which cannot be speculated?"); var oldSpan = originalNode.Span; var expandParameter = originalNode.GetAncestorsOrThis(n => n is SimpleLambdaExpressionSyntax || n is ParenthesizedLambdaExpressionSyntax).Count() == 0; newNode = _simplificationService.Expand(newNode, _speculativeModel, annotationForReplacedAliasIdentifier: null, expandInsideNode: null, expandParameter: expandParameter, cancellationToken: _cancellationToken); speculativeTree = originalNode.SyntaxTree.GetRoot(_cancellationToken).ReplaceNode(originalNode, newNode); newNode = speculativeTree.GetAnnotatedNodes<SyntaxNode>(annotation).First(); _speculativeModel = GetSemanticModelForNode(newNode, _semanticModel); newNode = base.Visit(newNode); var newSpan = newNode.Span; newNode = newNode.WithoutAnnotations(annotation); newNode = _renameAnnotations.WithAdditionalAnnotations(newNode, new RenameNodeSimplificationAnnotation() { OriginalTextSpan = oldSpan }); _renameSpansTracker.AddComplexifiedSpan(_documentId, oldSpan, new TextSpan(oldSpan.Start, newSpan.Length), _modifiedSubSpans); _modifiedSubSpans = null; _isProcessingComplexifiedSpans = false; _speculativeModel = null; return newNode; } private bool IsExpandWithinMultiLineLambda(SyntaxNode node) { if (node == null) { return false; } if (_conflictLocations.Contains(node.Span)) { return true; } if (node.IsParentKind(SyntaxKind.ParenthesizedLambdaExpression)) { var parent = (ParenthesizedLambdaExpressionSyntax)node; if (ReferenceEquals(parent.ParameterList, node)) { return true; } else { return false; } } if (node.IsParentKind(SyntaxKind.SimpleLambdaExpression)) { var parent = (SimpleLambdaExpressionSyntax)node; if (ReferenceEquals(parent.Parameter, node)) { return true; } else { return false; } } return true; } private async Task<SyntaxToken> RenameAndAnnotateAsync(SyntaxToken token, SyntaxToken newToken, bool isRenameLocation, bool isOldText) { try { if (_isProcessingComplexifiedSpans) { // Rename Token if (isRenameLocation) { var annotation = _renameAnnotations.GetAnnotations(token).OfType<RenameActionAnnotation>().FirstOrDefault(); if (annotation != null) { newToken = RenameToken(token, newToken, annotation.Prefix, annotation.Suffix); AddModifiedSpan(annotation.OriginalSpan, newToken.Span); } else { newToken = RenameToken(token, newToken, prefix: null, suffix: null); } } return newToken; } var symbols = RenameUtilities.GetSymbolsTouchingPosition(token.Span.Start, _semanticModel, _solution.Workspace, _cancellationToken); string suffix = null; string prefix = isRenameLocation && _renameLocations[token.Span].IsRenamableAccessor ? newToken.ValueText.Substring(0, newToken.ValueText.IndexOf('_') + 1) : null; if (symbols.Length == 1) { var symbol = symbols[0]; if (symbol.IsConstructor()) { symbol = symbol.ContainingSymbol; } var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync(symbol, _solution, _cancellationToken).ConfigureAwait(false); symbol = sourceDefinition ?? symbol; if (symbol is INamedTypeSymbol namedTypeSymbol) { if (namedTypeSymbol.IsImplicitlyDeclared && namedTypeSymbol.IsDelegateType() && namedTypeSymbol.AssociatedSymbol != null) { suffix = "EventHandler"; } } // This is a conflicting namespace declaration token. Even if the rename results in conflict with this namespace // conflict is not shown for the namespace so we are tracking this token if (!isRenameLocation && symbol is INamespaceSymbol && token.GetPreviousToken().IsKind(SyntaxKind.NamespaceKeyword)) { return newToken; } } // Rename Token if (isRenameLocation && !this.AnnotateForComplexification) { var oldSpan = token.Span; newToken = RenameToken(token, newToken, prefix, suffix); AddModifiedSpan(oldSpan, newToken.Span); } var renameDeclarationLocations = await ConflictResolver.CreateDeclarationLocationAnnotationsAsync(_solution, symbols, _cancellationToken).ConfigureAwait(false); var isNamespaceDeclarationReference = false; if (isRenameLocation && token.GetPreviousToken().IsKind(SyntaxKind.NamespaceKeyword)) { isNamespaceDeclarationReference = true; } var isMemberGroupReference = _semanticFactsService.IsNameOfContext(_semanticModel, token.Span.Start, _cancellationToken); var renameAnnotation = new RenameActionAnnotation( token.Span, isRenameLocation, prefix, suffix, renameDeclarationLocations: renameDeclarationLocations, isOriginalTextLocation: isOldText, isNamespaceDeclarationReference: isNamespaceDeclarationReference, isInvocationExpression: false, isMemberGroupReference: isMemberGroupReference); newToken = _renameAnnotations.WithAdditionalAnnotations(newToken, renameAnnotation, new RenameTokenSimplificationAnnotation() { OriginalTextSpan = token.Span }); _annotatedIdentifierTokens.Add(token); if (_renameRenamableSymbolDeclaration != null && _renamableDeclarationLocation == token.GetLocation()) { newToken = _renameAnnotations.WithAdditionalAnnotations(newToken, _renameRenamableSymbolDeclaration); } return newToken; } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } private RenameActionAnnotation GetAnnotationForInvocationExpression(InvocationExpressionSyntax invocationExpression) { var identifierToken = default(SyntaxToken); var expressionOfInvocation = invocationExpression.Expression; while (expressionOfInvocation != null) { switch (expressionOfInvocation.Kind()) { case SyntaxKind.IdentifierName: case SyntaxKind.GenericName: identifierToken = ((SimpleNameSyntax)expressionOfInvocation).Identifier; break; case SyntaxKind.SimpleMemberAccessExpression: identifierToken = ((MemberAccessExpressionSyntax)expressionOfInvocation).Name.Identifier; break; case SyntaxKind.QualifiedName: identifierToken = ((QualifiedNameSyntax)expressionOfInvocation).Right.Identifier; break; case SyntaxKind.AliasQualifiedName: identifierToken = ((AliasQualifiedNameSyntax)expressionOfInvocation).Name.Identifier; break; case SyntaxKind.ParenthesizedExpression: expressionOfInvocation = ((ParenthesizedExpressionSyntax)expressionOfInvocation).Expression; continue; } break; } if (identifierToken != default && !_annotatedIdentifierTokens.Contains(identifierToken)) { var symbolInfo = _semanticModel.GetSymbolInfo(invocationExpression, _cancellationToken); IEnumerable<ISymbol> symbols = null; if (symbolInfo.Symbol == null) { return null; } else { symbols = SpecializedCollections.SingletonEnumerable(symbolInfo.Symbol); } RenameDeclarationLocationReference[] renameDeclarationLocations = ConflictResolver.CreateDeclarationLocationAnnotationsAsync( _solution, symbols, _cancellationToken) .WaitAndGetResult_CanCallOnBackground(_cancellationToken); var renameAnnotation = new RenameActionAnnotation( identifierToken.Span, isRenameLocation: false, prefix: null, suffix: null, renameDeclarationLocations: renameDeclarationLocations, isOriginalTextLocation: false, isNamespaceDeclarationReference: false, isInvocationExpression: true, isMemberGroupReference: false); return renameAnnotation; } return null; } public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { var result = base.VisitInvocationExpression(node); if (_invocationExpressionsNeedingConflictChecks.Contains(node)) { var renameAnnotation = GetAnnotationForInvocationExpression(node); if (renameAnnotation != null) { result = _renameAnnotations.WithAdditionalAnnotations(result, renameAnnotation); } } return result; } private bool IsRenameLocation(SyntaxToken token) { if (!_isProcessingComplexifiedSpans) { return _renameLocations.ContainsKey(token.Span); } else { if (token.HasAnnotations(AliasAnnotation.Kind)) { return false; } if (token.HasAnnotations(RenameAnnotation.Kind)) { return _renameAnnotations.GetAnnotations(token).OfType<RenameActionAnnotation>().First().IsRenameLocation; } if (token.Parent is SimpleNameSyntax && !token.IsKind(SyntaxKind.GlobalKeyword) && token.Parent.Parent.IsKind(SyntaxKind.AliasQualifiedName, SyntaxKind.QualifiedCref, SyntaxKind.QualifiedName)) { var symbol = _speculativeModel.GetSymbolInfo(token.Parent, _cancellationToken).Symbol; if (symbol != null && _renamedSymbol.Kind != SymbolKind.Local && _renamedSymbol.Kind != SymbolKind.RangeVariable && (symbol == _renamedSymbol || SymbolKey.GetComparer(ignoreCase: true, ignoreAssemblyKeys: false).Equals(symbol.GetSymbolKey(), _renamedSymbol.GetSymbolKey()))) { return true; } } return false; } } private SyntaxToken UpdateAliasAnnotation(SyntaxToken newToken) { if (_aliasSymbol != null && !this.AnnotateForComplexification && newToken.HasAnnotations(AliasAnnotation.Kind)) { newToken = RenameUtilities.UpdateAliasAnnotation(newToken, _aliasSymbol, _replacementText); } return newToken; } private SyntaxToken RenameToken(SyntaxToken oldToken, SyntaxToken newToken, string prefix, string suffix) { var parent = oldToken.Parent; string currentNewIdentifier = _isVerbatim ? _replacementText.Substring(1) : _replacementText; var oldIdentifier = newToken.ValueText; var isAttributeName = SyntaxFacts.IsAttributeName(parent); if (isAttributeName) { if (oldIdentifier != _renamedSymbol.Name) { if (currentNewIdentifier.TryGetWithoutAttributeSuffix(out var withoutSuffix)) { currentNewIdentifier = withoutSuffix; } } } else { if (!string.IsNullOrEmpty(prefix)) { currentNewIdentifier = prefix + currentNewIdentifier; } if (!string.IsNullOrEmpty(suffix)) { currentNewIdentifier = currentNewIdentifier + suffix; } } // determine the canonical identifier name (unescaped, no unicode escaping, ...) string valueText = currentNewIdentifier; var kind = SyntaxFacts.GetKeywordKind(currentNewIdentifier); if (kind != SyntaxKind.None) { valueText = SyntaxFacts.GetText(kind); } else { var parsedIdentifier = SyntaxFactory.ParseName(currentNewIdentifier); if (parsedIdentifier.IsKind(SyntaxKind.IdentifierName)) { valueText = ((IdentifierNameSyntax)parsedIdentifier).Identifier.ValueText; } } // TODO: we can't use escaped unicode characters in xml doc comments, so we need to pass the valuetext as text as well. // <param name="\u... is invalid. // if it's an attribute name we don't mess with the escaping because it might change overload resolution newToken = _isVerbatim || (isAttributeName && oldToken.IsVerbatimIdentifier()) ? newToken = newToken.CopyAnnotationsTo(SyntaxFactory.VerbatimIdentifier(newToken.LeadingTrivia, currentNewIdentifier, valueText, newToken.TrailingTrivia)) : newToken = newToken.CopyAnnotationsTo(SyntaxFactory.Identifier(newToken.LeadingTrivia, SyntaxKind.IdentifierToken, currentNewIdentifier, valueText, newToken.TrailingTrivia)); if (_replacementTextValid) { if (newToken.IsVerbatimIdentifier()) { // a reference location should always be tried to be unescaped, whether it was escaped before rename // or the replacement itself is escaped. newToken = newToken.WithAdditionalAnnotations(Simplifier.Annotation); } else { var semanticModel = GetSemanticModelForNode(parent, _speculativeModel ?? _semanticModel); newToken = Simplification.CSharpSimplificationService.TryEscapeIdentifierToken(newToken, parent, semanticModel); } } return newToken; } private SyntaxToken RenameInStringLiteral(SyntaxToken oldToken, SyntaxToken newToken, Func<SyntaxTriviaList, string, string, SyntaxTriviaList, SyntaxToken> createNewStringLiteral) { var originalString = newToken.ToString(); string replacedString = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText); if (replacedString != originalString) { var oldSpan = oldToken.Span; newToken = createNewStringLiteral(newToken.LeadingTrivia, replacedString, replacedString, newToken.TrailingTrivia); AddModifiedSpan(oldSpan, newToken.Span); return newToken.CopyAnnotationsTo(_renameAnnotations.WithAdditionalAnnotations(newToken, new RenameTokenSimplificationAnnotation() { OriginalTextSpan = oldSpan })); } return newToken; } private SyntaxToken RenameInTrivia(SyntaxToken token, IEnumerable<SyntaxTrivia> leadingOrTrailingTriviaList) { return token.ReplaceTrivia(leadingOrTrailingTriviaList, (oldTrivia, newTrivia) => { if (newTrivia.IsSingleLineComment() || newTrivia.IsMultiLineComment()) { return RenameInCommentTrivia(newTrivia); } return newTrivia; }); } private SyntaxTrivia RenameInCommentTrivia(SyntaxTrivia trivia) { var originalString = trivia.ToString(); string replacedString = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText); if (replacedString != originalString) { var oldSpan = trivia.Span; var newTrivia = SyntaxFactory.Comment(replacedString); AddModifiedSpan(oldSpan, newTrivia.Span); return trivia.CopyAnnotationsTo(_renameAnnotations.WithAdditionalAnnotations(newTrivia, new RenameTokenSimplificationAnnotation() { OriginalTextSpan = oldSpan })); } return trivia; } private SyntaxToken RenameWithinToken(SyntaxToken oldToken, SyntaxToken newToken) { if (_isProcessingComplexifiedSpans || (_isProcessingTrivia == 0 && !_stringAndCommentTextSpans.Contains(oldToken.Span))) { return newToken; } if (_isRenamingInStrings) { if (newToken.IsKind(SyntaxKind.StringLiteralToken)) { newToken = RenameInStringLiteral(oldToken, newToken, SyntaxFactory.Literal); } else if (newToken.IsKind(SyntaxKind.InterpolatedStringTextToken)) { newToken = RenameInStringLiteral(oldToken, newToken, (leadingTrivia, text, value, trailingTrivia) => SyntaxFactory.Token(newToken.LeadingTrivia, SyntaxKind.InterpolatedStringTextToken, text, value, newToken.TrailingTrivia)); } } if (_isRenamingInComments) { if (newToken.IsKind(SyntaxKind.XmlTextLiteralToken)) { newToken = RenameInStringLiteral(oldToken, newToken, SyntaxFactory.XmlTextLiteral); } else if (newToken.IsKind(SyntaxKind.IdentifierToken) && newToken.Parent.IsKind(SyntaxKind.XmlName) && newToken.ValueText == _originalText) { var newIdentifierToken = SyntaxFactory.Identifier(newToken.LeadingTrivia, _replacementText, newToken.TrailingTrivia); newToken = newToken.CopyAnnotationsTo(_renameAnnotations.WithAdditionalAnnotations(newIdentifierToken, new RenameTokenSimplificationAnnotation() { OriginalTextSpan = oldToken.Span })); AddModifiedSpan(oldToken.Span, newToken.Span); } if (newToken.HasLeadingTrivia) { var updatedToken = RenameInTrivia(oldToken, oldToken.LeadingTrivia); if (updatedToken != oldToken) { newToken = newToken.WithLeadingTrivia(updatedToken.LeadingTrivia); } } if (newToken.HasTrailingTrivia) { var updatedToken = RenameInTrivia(oldToken, oldToken.TrailingTrivia); if (updatedToken != oldToken) { newToken = newToken.WithTrailingTrivia(updatedToken.TrailingTrivia); } } } return newToken; } } #endregion #region "Declaration Conflicts" public bool LocalVariableConflict( SyntaxToken token, IEnumerable<ISymbol> newReferencedSymbols) { if (token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.IsParentKind(SyntaxKind.InvocationExpression) && token.GetPreviousToken().Kind() != SyntaxKind.DotToken && token.GetNextToken().Kind() != SyntaxKind.DotToken) { var expression = (ExpressionSyntax)token.Parent; var enclosingMemberDeclaration = expression.FirstAncestorOrSelf<MemberDeclarationSyntax>(); if (enclosingMemberDeclaration != null) { var locals = enclosingMemberDeclaration.GetLocalDeclarationMap()[token.ValueText]; if (locals.Length > 0) { // This unqualified invocation name matches the name of an existing local // or parameter. Report a conflict if the matching local/parameter is not // a delegate type. var relevantLocals = newReferencedSymbols .Where(s => s.MatchesKind(SymbolKind.Local, SymbolKind.Parameter) && s.Name == token.ValueText); if (relevantLocals.Count() != 1) { return true; } var matchingLocal = relevantLocals.Single(); var invocationTargetsLocalOfDelegateType = (matchingLocal.IsKind(SymbolKind.Local) && ((ILocalSymbol)matchingLocal).Type.IsDelegateType()) || (matchingLocal.IsKind(SymbolKind.Parameter) && ((IParameterSymbol)matchingLocal).Type.IsDelegateType()); return !invocationTargetsLocalOfDelegateType; } } } return false; } public async Task<ImmutableArray<Location>> ComputeDeclarationConflictsAsync( string replacementText, ISymbol renamedSymbol, ISymbol renameSymbol, IEnumerable<SymbolAndProjectId> referencedSymbols, Solution baseSolution, Solution newSolution, IDictionary<Location, Location> reverseMappedLocations, CancellationToken cancellationToken) { try { var conflicts = ArrayBuilder<Location>.GetInstance(); // If we're renaming a named type, we can conflict with members w/ our same name. Note: // this doesn't apply to enums. if (renamedSymbol.Kind == SymbolKind.NamedType && ((INamedTypeSymbol)renamedSymbol).TypeKind != TypeKind.Enum) { var namedType = (INamedTypeSymbol)renamedSymbol; AddSymbolSourceSpans(conflicts, namedType.GetMembers(renamedSymbol.Name), reverseMappedLocations); } // If we're contained in a named type (we may be a named type ourself!) then we have a // conflict. NOTE(cyrusn): This does not apply to enums. if (renamedSymbol.ContainingSymbol is INamedTypeSymbol && renamedSymbol.ContainingType.Name == renamedSymbol.Name && renamedSymbol.ContainingType.TypeKind != TypeKind.Enum) { AddSymbolSourceSpans(conflicts, SpecializedCollections.SingletonEnumerable(renamedSymbol.ContainingType), reverseMappedLocations); } if (renamedSymbol.Kind == SymbolKind.Parameter || renamedSymbol.Kind == SymbolKind.Local || renamedSymbol.Kind == SymbolKind.RangeVariable) { var token = renamedSymbol.Locations.Single().FindToken(cancellationToken); var methodDeclaration = token.GetAncestor<MemberDeclarationSyntax>(); var visitor = new LocalConflictVisitor(token); visitor.Visit(methodDeclaration); conflicts.AddRange(visitor.ConflictingTokens.Select(t => reverseMappedLocations[t.GetLocation()])); } else if (renamedSymbol.Kind == SymbolKind.Label) { var token = renamedSymbol.Locations.Single().FindToken(cancellationToken); var methodDeclaration = token.GetAncestor<MemberDeclarationSyntax>(); var visitor = new LabelConflictVisitor(token); visitor.Visit(methodDeclaration); conflicts.AddRange(visitor.ConflictingTokens.Select(t => reverseMappedLocations[t.GetLocation()])); } else if (renamedSymbol.Kind == SymbolKind.Method) { conflicts.AddRange(DeclarationConflictHelpers.GetMembersWithConflictingSignatures((IMethodSymbol)renamedSymbol, trimOptionalParameters: false).Select(t => reverseMappedLocations[t])); // we allow renaming overrides of VB property accessors with parameters in C#. // VB has a special rule that properties are not allowed to have the same name as any of the parameters. // Because this declaration in C# affects the property declaration in VB, we need to check this VB rule here in C#. var properties = new List<ISymbol>(); foreach (var referencedSymbol in referencedSymbols) { var property = await RenameLocations.ReferenceProcessing.GetPropertyFromAccessorOrAnOverride( referencedSymbol, baseSolution, cancellationToken).ConfigureAwait(false); if (property.Symbol != null) { properties.Add(property.Symbol); } } ConflictResolver.AddConflictingParametersOfProperties( properties.Distinct(), replacementText, conflicts); } else if (renamedSymbol.Kind == SymbolKind.Alias) { // in C# there can only be one using with the same alias name in the same block (top of file of namespace). // It's ok to redefine the alias in different blocks. var location = renamedSymbol.Locations.Single(); var token = await location.SourceTree.GetTouchingTokenAsync(location.SourceSpan.Start, cancellationToken, findInsideTrivia: true).ConfigureAwait(false); var currentUsing = (UsingDirectiveSyntax)token.Parent.Parent.Parent; var namespaceDecl = token.Parent.GetAncestorsOrThis(n => n.Kind() == SyntaxKind.NamespaceDeclaration).FirstOrDefault(); SyntaxList<UsingDirectiveSyntax> usings; if (namespaceDecl != null) { usings = ((NamespaceDeclarationSyntax)namespaceDecl).Usings; } else { var compilationUnit = (CompilationUnitSyntax)token.Parent.GetAncestorsOrThis(n => n.Kind() == SyntaxKind.CompilationUnit).Single(); usings = compilationUnit.Usings; } foreach (var usingDirective in usings) { if (usingDirective.Alias != null && usingDirective != currentUsing) { if (usingDirective.Alias.Name.Identifier.ValueText == currentUsing.Alias.Name.Identifier.ValueText) { conflicts.Add(reverseMappedLocations[usingDirective.Alias.Name.GetLocation()]); } } } } else if (renamedSymbol.Kind == SymbolKind.TypeParameter) { foreach (var location in renamedSymbol.Locations) { var token = await location.SourceTree.GetTouchingTokenAsync(location.SourceSpan.Start, cancellationToken, findInsideTrivia: true).ConfigureAwait(false); var currentTypeParameter = token.Parent; foreach (var typeParameter in ((TypeParameterListSyntax)currentTypeParameter.Parent).Parameters) { if (typeParameter != currentTypeParameter && token.ValueText == typeParameter.Identifier.ValueText) { conflicts.Add(reverseMappedLocations[typeParameter.Identifier.GetLocation()]); } } } } // if the renamed symbol is a type member, it's name should not conflict with a type parameter if (renamedSymbol.ContainingType != null && renamedSymbol.ContainingType.GetMembers(renamedSymbol.Name).Contains(renamedSymbol)) { var conflictingLocations = renamedSymbol.ContainingType.TypeParameters .Where(t => t.Name == renamedSymbol.Name) .SelectMany(t => t.Locations); foreach (var location in conflictingLocations) { var typeParameterToken = location.FindToken(cancellationToken); conflicts.Add(reverseMappedLocations[typeParameterToken.GetLocation()]); } } return conflicts.ToImmutableAndFree(); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } private static async Task<ISymbol> GetVBPropertyFromAccessorOrAnOverrideAsync(ISymbol symbol, Solution solution, CancellationToken cancellationToken) { try { if (symbol.IsPropertyAccessor()) { var property = ((IMethodSymbol)symbol).AssociatedSymbol; return property.Language == LanguageNames.VisualBasic ? property : null; } if (symbol.IsOverride && symbol.OverriddenMember() != null) { var originalSourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(symbol.OverriddenMember(), solution, cancellationToken).ConfigureAwait(false); if (originalSourceSymbol != null) { return await GetVBPropertyFromAccessorOrAnOverrideAsync(originalSourceSymbol, solution, cancellationToken).ConfigureAwait(false); } } return null; } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } private void AddSymbolSourceSpans( ArrayBuilder<Location> conflicts, IEnumerable<ISymbol> symbols, IDictionary<Location, Location> reverseMappedLocations) { foreach (var symbol in symbols) { foreach (var location in symbol.Locations) { // reverseMappedLocations may not contain the location if the location's token // does not contain the text of it's name (e.g. the getter of "int X { get; }" // does not contain the text "get_X" so conflicting renames to "get_X" will not // have added the getter to reverseMappedLocations). if (location.IsInSource && reverseMappedLocations.ContainsKey(location)) { conflicts.Add(reverseMappedLocations[location]); } } } } public async Task<ImmutableArray<Location>> ComputeImplicitReferenceConflictsAsync( ISymbol renameSymbol, ISymbol renamedSymbol, IEnumerable<ReferenceLocation> implicitReferenceLocations, CancellationToken cancellationToken) { // Handle renaming of symbols used for foreach bool implicitReferencesMightConflict = renameSymbol.Kind == SymbolKind.Property && string.Compare(renameSymbol.Name, "Current", StringComparison.OrdinalIgnoreCase) == 0; implicitReferencesMightConflict = implicitReferencesMightConflict || (renameSymbol.Kind == SymbolKind.Method && (string.Compare(renameSymbol.Name, WellKnownMemberNames.MoveNextMethodName, StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(renameSymbol.Name, WellKnownMemberNames.GetEnumeratorMethodName, StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(renameSymbol.Name, WellKnownMemberNames.DeconstructMethodName, StringComparison.OrdinalIgnoreCase) == 0)); // TODO: handle Dispose for using statement and Add methods for collection initializers. if (implicitReferencesMightConflict) { if (renamedSymbol.Name != renameSymbol.Name) { foreach (var implicitReferenceLocation in implicitReferenceLocations) { var token = await implicitReferenceLocation.Location.SourceTree.GetTouchingTokenAsync( implicitReferenceLocation.Location.SourceSpan.Start, cancellationToken, findInsideTrivia: false).ConfigureAwait(false); switch (token.Kind()) { case SyntaxKind.ForEachKeyword: return ImmutableArray.Create(((CommonForEachStatementSyntax)token.Parent).Expression.GetLocation()); } if (token.Parent.IsInDeconstructionLeft(out var deconstructionLeft)) { return ImmutableArray.Create(deconstructionLeft.GetLocation()); } } } } return ImmutableArray<Location>.Empty; } public ImmutableArray<Location> ComputePossibleImplicitUsageConflicts( ISymbol renamedSymbol, SemanticModel semanticModel, Location originalDeclarationLocation, int newDeclarationLocationStartingPosition, CancellationToken cancellationToken) { // TODO: support other implicitly used methods like dispose if ((renamedSymbol.Name == "MoveNext" || renamedSymbol.Name == "GetEnumerator" || renamedSymbol.Name == "Current") && renamedSymbol.GetAllTypeArguments().Length == 0) { // TODO: partial methods currently only show the location where the rename happens as a conflict. // Consider showing both locations as a conflict. var baseType = renamedSymbol.ContainingType?.GetBaseTypes().FirstOrDefault(); if (baseType != null) { var implicitSymbols = semanticModel.LookupSymbols( newDeclarationLocationStartingPosition, baseType, renamedSymbol.Name) .Where(sym => !sym.Equals(renamedSymbol)); foreach (var symbol in implicitSymbols) { if (symbol.GetAllTypeArguments().Length != 0) { continue; } if (symbol.Kind == SymbolKind.Method) { var method = (IMethodSymbol)symbol; if (symbol.Name == "MoveNext") { if (!method.ReturnsVoid && !method.Parameters.Any() && method.ReturnType.SpecialType == SpecialType.System_Boolean) { return ImmutableArray.Create(originalDeclarationLocation); } } else if (symbol.Name == "GetEnumerator") { // we are a bit pessimistic here. // To be sure we would need to check if the returned type is having a MoveNext and Current as required by foreach if (!method.ReturnsVoid && !method.Parameters.Any()) { return ImmutableArray.Create(originalDeclarationLocation); } } } else if (symbol.Kind == SymbolKind.Property && symbol.Name == "Current") { var property = (IPropertySymbol)symbol; if (!property.Parameters.Any() && !property.IsWriteOnly) { return ImmutableArray.Create(originalDeclarationLocation); } } } } } return ImmutableArray<Location>.Empty; } #endregion public void TryAddPossibleNameConflicts(ISymbol symbol, string replacementText, ICollection<string> possibleNameConflicts) { if (replacementText.EndsWith("Attribute", StringComparison.Ordinal) && replacementText.Length > 9) { var conflict = replacementText.Substring(0, replacementText.Length - 9); if (!possibleNameConflicts.Contains(conflict)) { possibleNameConflicts.Add(conflict); } } if (symbol.Kind == SymbolKind.Property) { foreach (var conflict in new string[] { "_" + replacementText, "get_" + replacementText, "set_" + replacementText }) { if (!possibleNameConflicts.Contains(conflict)) { possibleNameConflicts.Add(conflict); } } } // in C# we also need to add the valueText because it can be different from the text in source // e.g. it can contain escaped unicode characters. Otherwise conflicts would be detected for // v\u0061r and var or similar. string valueText = replacementText; SyntaxKind kind = SyntaxFacts.GetKeywordKind(replacementText); if (kind != SyntaxKind.None) { valueText = SyntaxFacts.GetText(kind); } else { var name = SyntaxFactory.ParseName(replacementText); if (name.Kind() == SyntaxKind.IdentifierName) { valueText = ((IdentifierNameSyntax)name).Identifier.ValueText; } } // this also covers the case of an escaped replacementText if (valueText != replacementText) { possibleNameConflicts.Add(valueText); } } /// <summary> /// Gets the top most enclosing statement or CrefSyntax as target to call MakeExplicit on. /// It's either the enclosing statement, or if this statement is inside of a lambda expression, the enclosing /// statement of this lambda. /// </summary> /// <param name="token">The token to get the complexification target for.</param> /// <returns></returns> public SyntaxNode GetExpansionTargetForLocation(SyntaxToken token) { return GetExpansionTarget(token); } private static SyntaxNode GetExpansionTarget(SyntaxToken token) { // get the directly enclosing statement var enclosingStatement = token.GetAncestors(n => n is StatementSyntax).FirstOrDefault(); // System.Func<int, int> myFunc = arg => X; SyntaxNode possibleLambdaExpression = enclosingStatement == null ? token.GetAncestors(n => n is SimpleLambdaExpressionSyntax || n is ParenthesizedLambdaExpressionSyntax).FirstOrDefault() : null; if (possibleLambdaExpression != null) { var lambdaExpression = ((LambdaExpressionSyntax)possibleLambdaExpression); if (lambdaExpression.Body is ExpressionSyntax) { return lambdaExpression.Body; } } // int M() => X; var possibleArrowExpressionClause = enclosingStatement == null ? token.GetAncestors<ArrowExpressionClauseSyntax>().FirstOrDefault() : null; if (possibleArrowExpressionClause != null) { return possibleArrowExpressionClause.Expression; } var enclosingNameMemberCrefOrnull = token.GetAncestors(n => n is NameMemberCrefSyntax).LastOrDefault(); if (enclosingNameMemberCrefOrnull != null) { if (token.Parent is TypeSyntax && token.Parent.Parent is TypeSyntax) { enclosingNameMemberCrefOrnull = null; } } var enclosingXmlNameAttr = token.GetAncestors(n => n is XmlNameAttributeSyntax).FirstOrDefault(); if (enclosingXmlNameAttr != null) { return null; } var enclosingInitializer = token.GetAncestors<EqualsValueClauseSyntax>().FirstOrDefault(); if (enclosingStatement == null && enclosingInitializer != null && enclosingInitializer.Parent is VariableDeclaratorSyntax) { return enclosingInitializer.Value; } var attributeSyntax = token.GetAncestor<AttributeSyntax>(); if (attributeSyntax != null) { return attributeSyntax; } // there seems to be no statement above this one. Let's see if we can at least get an SimpleNameSyntax return enclosingStatement ?? enclosingNameMemberCrefOrnull ?? token.GetAncestors(n => n is SimpleNameSyntax).FirstOrDefault(); } #region "Helper Methods" public bool IsIdentifierValid(string replacementText, ISyntaxFactsService syntaxFactsService) { string escapedIdentifier; if (replacementText.StartsWith("@", StringComparison.Ordinal)) { escapedIdentifier = replacementText; } else { escapedIdentifier = "@" + replacementText; } // Make sure we got an identifier. if (!syntaxFactsService.IsValidIdentifier(escapedIdentifier)) { // We still don't have an identifier, so let's fail return false; } return true; } /// <summary> /// Gets the semantic model for the given node. /// If the node belongs to the syntax tree of the original semantic model, then returns originalSemanticModel. /// Otherwise, returns a speculative model. /// The assumption for the later case is that span start position of the given node in it's syntax tree is same as /// the span start of the original node in the original syntax tree. /// </summary> public static SemanticModel GetSemanticModelForNode(SyntaxNode node, SemanticModel originalSemanticModel) { if (node.SyntaxTree == originalSemanticModel.SyntaxTree) { // This is possible if the previous rename phase didn't rewrite any nodes in this tree. return originalSemanticModel; } var nodeToSpeculate = node.GetAncestorsOrThis(n => SpeculationAnalyzer.CanSpeculateOnNode(n)).LastOrDefault(); if (nodeToSpeculate == null) { if (node.IsKind(SyntaxKind.NameMemberCref)) { nodeToSpeculate = ((NameMemberCrefSyntax)node).Name; } else if (node.IsKind(SyntaxKind.QualifiedCref)) { nodeToSpeculate = ((QualifiedCrefSyntax)node).Container; } else if (node.IsKind(SyntaxKind.TypeConstraint)) { nodeToSpeculate = ((TypeConstraintSyntax)node).Type; } else if (node is BaseTypeSyntax baseType) { nodeToSpeculate = baseType.Type; } else { return null; } } bool isInNamespaceOrTypeContext = SyntaxFacts.IsInNamespaceOrTypeContext(node as ExpressionSyntax); var position = nodeToSpeculate.SpanStart; return SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(nodeToSpeculate, originalSemanticModel, position, isInNamespaceOrTypeContext); } #endregion } }