private async Task <ISymbol> TryGetSymbolAsync( Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken ) { var documentWithOriginalName = solutionWithOriginalName.GetDocument(documentId); var syntaxTreeWithOriginalName = await documentWithOriginalName .GetSyntaxTreeAsync(cancellationToken) .ConfigureAwait(false); var syntaxFacts = documentWithOriginalName.GetLanguageService <ISyntaxFactsService>(); var semanticFacts = documentWithOriginalName.GetLanguageService <ISemanticFactsService>(); var semanticModel = await documentWithOriginalName .GetSemanticModelAsync(cancellationToken) .ConfigureAwait(false); var token = await syntaxTreeWithOriginalName .GetTouchingWordAsync(_snapshotSpan.Start, syntaxFacts, cancellationToken) .ConfigureAwait(false); var tokenRenameInfo = RenameUtilities.GetTokenRenameInfo( semanticFacts, semanticModel, token, cancellationToken ); return(tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null); }
private async Task <TriggerIdentifierKind> DetermineIfRenamableIdentifierAsync(SnapshotSpan snapshotSpan, bool initialCheck) { _threadingContext.ThrowIfNotOnBackgroundThread(); var document = snapshotSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document != null) { var syntaxFactsService = document.GetLanguageService <ISyntaxFactsService>(); var syntaxTree = await document.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false); var token = await syntaxTree.GetTouchingWordAsync(snapshotSpan.Start.Position, syntaxFactsService, _cancellationToken).ConfigureAwait(false); // The OriginalName is determined with a simple textual check, so for a // statement such as "Dim [x = 1" the textual check will return a name of "[x". // The token found for "[x" is an identifier token, but only due to error // recovery (the "[x" is actually in the trailing trivia). If the OriginalName // found through the textual check has a different length than the span of the // touching word, then we cannot perform a rename. if (initialCheck && token.Span.Length != this.OriginalName.Length) { return(TriggerIdentifierKind.NotRenamable); } var languageHeuristicsService = document.GetLanguageService <IRenameTrackingLanguageHeuristicsService>(); if (syntaxFactsService.IsIdentifier(token) && languageHeuristicsService.IsIdentifierValidForRenameTracking(token.Text)) { var semanticModel = await document.ReuseExistingSpeculativeModelAsync(token.Parent, _cancellationToken).ConfigureAwait(false); var semanticFacts = document.GetLanguageService <ISemanticFactsService>(); var renameSymbolInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, token, _cancellationToken); if (!renameSymbolInfo.HasSymbols) { return(TriggerIdentifierKind.NotRenamable); } if (renameSymbolInfo.IsMemberGroup) { // This is a reference from a nameof expression. Allow the rename but set the RenameOverloads option _forceRenameOverloads = true; return(await DetermineIfRenamableSymbolsAsync(renameSymbolInfo.Symbols, document).ConfigureAwait(false)); } else { // We do not yet support renaming (inline rename or rename tracking) on // named tuple elements. if (renameSymbolInfo.Symbols.Single().ContainingType?.IsTupleType() == true) { return(TriggerIdentifierKind.NotRenamable); } return(await DetermineIfRenamableSymbolAsync(renameSymbolInfo.Symbols.Single(), document, token).ConfigureAwait(false)); } } } return(TriggerIdentifierKind.NotRenamable); }
private async Task <TriggerIdentifierKind> DetermineIfRenamableIdentifierAsync(SnapshotSpan snapshotSpan, bool initialCheck) { AssertIsBackground(); var document = snapshotSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document != null) { var syntaxFactsService = document.Project.LanguageServices.GetService <ISyntaxFactsService>(); var syntaxTree = await document.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false); var token = syntaxTree.GetTouchingWord(snapshotSpan.Start.Position, syntaxFactsService, _cancellationToken); // The OriginalName is determined with a simple textual check, so for a // statement such as "Dim [x = 1" the textual check will return a name of "[x". // The token found for "[x" is an identifier token, but only due to error // recovery (the "[x" is actually in the trailing trivia). If the OriginalName // found through the textual check has a different length than the span of the // touching word, then we cannot perform a rename. if (initialCheck && token.Span.Length != this.OriginalName.Length) { return(TriggerIdentifierKind.NotRenamable); } if (syntaxFactsService.IsIdentifier(token)) { var semanticModel = await document.GetSemanticModelForNodeAsync(token.Parent, _cancellationToken).ConfigureAwait(false); var semanticFacts = document.GetLanguageService <ISemanticFactsService>(); var renameSymbolInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, token, _cancellationToken); if (!renameSymbolInfo.HasSymbols) { return(TriggerIdentifierKind.NotRenamable); } if (renameSymbolInfo.IsMemberGroup) { // This is a reference from a nameof expression. Allow the rename but set the RenameOverloads option _forceRenameOverloads = true; return(await DetermineIfRenamableSymbolsAsync(renameSymbolInfo.Symbols, document, token).ConfigureAwait(false)); } else { return(await DetermineIfRenamableSymbolAsync(renameSymbolInfo.Symbols.Single(), document, token).ConfigureAwait(false)); } } } return(TriggerIdentifierKind.NotRenamable); }
private bool TryGetSymbol(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken, out ISymbol symbol) { var documentWithOriginalName = solutionWithOriginalName.GetDocument(documentId); var syntaxTreeWithOriginalName = documentWithOriginalName.GetSyntaxTreeAsync(cancellationToken).WaitAndGetResult(cancellationToken); var syntaxFacts = documentWithOriginalName.GetLanguageService <ISyntaxFactsService>(); var semanticFacts = documentWithOriginalName.GetLanguageService <ISemanticFactsService>(); var semanticModel = documentWithOriginalName.GetSemanticModelAsync(cancellationToken).WaitAndGetResult(cancellationToken); var token = syntaxTreeWithOriginalName.GetTouchingWord(_snapshotSpan.Start, syntaxFacts, cancellationToken); var tokenRenameInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, token, cancellationToken); symbol = tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null; return(symbol != null); }
public static async Task <ISymbol?> GetRenameSymbol( Document document, SyntaxToken triggerToken, CancellationToken cancellationToken) { var syntaxFactsService = document.Project.LanguageServices.GetRequiredService <ISyntaxFactsService>(); if (syntaxFactsService.IsReservedOrContextualKeyword(triggerToken)) { return(null); } var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var semanticFacts = document.GetLanguageService <ISemanticFactsService>(); var tokenRenameInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, triggerToken, cancellationToken); // Rename was invoked on a member group reference in a nameof expression. // Trigger the rename on any of the candidate symbols but force the // RenameOverloads option to be on. var triggerSymbol = tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null; if (triggerSymbol == null) { return(null); } // see https://github.com/dotnet/roslyn/issues/10898 // we are disabling rename for tuple fields for now // 1) compiler does not return correct location information in these symbols // 2) renaming tuple fields seems a complex enough thing to require some design if (triggerSymbol.ContainingType?.IsTupleType == true) { return(null); } // If rename is invoked on a member group reference in a nameof expression, then the // RenameOverloads option should be forced on. var forceRenameOverloads = tokenRenameInfo.IsMemberGroup; if (syntaxFactsService.IsTypeNamedVarInVariableOrFieldDeclaration(triggerToken, triggerToken.Parent)) { // To check if var in this context is a real type, or the keyword, we need to // speculatively bind the identifier "var". If it returns a symbol, it's a real type, // if not, it's the keyword. // see bugs 659683 (compiler API) and 659705 (rename/workspace api) for examples var symbolForVar = semanticModel.GetSpeculativeSymbolInfo( triggerToken.SpanStart, triggerToken.Parent, SpeculativeBindingOption.BindAsTypeOrNamespace).Symbol; if (symbolForVar == null) { return(null); } } var symbolAndProjectId = await RenameLocations.ReferenceProcessing.GetRenamableSymbolAsync(document, triggerToken.SpanStart, cancellationToken : cancellationToken).ConfigureAwait(false); var symbol = symbolAndProjectId.Symbol; if (symbol == null) { return(null); } if (symbol.Kind == SymbolKind.Alias && symbol.IsExtern) { return(null); } // Cannot rename constructors in VB. TODO: this logic should be in the VB subclass of this type. var workspace = document.Project.Solution.Workspace; if (symbol.Kind == SymbolKind.NamedType && symbol.Language == LanguageNames.VisualBasic && triggerToken.ToString().Equals("New", StringComparison.OrdinalIgnoreCase)) { var originalSymbol = await SymbolFinder.FindSymbolAtPositionAsync(semanticModel, triggerToken.SpanStart, workspace, cancellationToken : cancellationToken); if (originalSymbol != null && originalSymbol.IsConstructor()) { return(null); } } if (syntaxFactsService.IsTypeNamedDynamic(triggerToken, triggerToken.Parent)) { if (symbol.Kind == SymbolKind.DynamicType) { return(null); } } // we allow implicit locals and parameters of Event handlers if (symbol.IsImplicitlyDeclared && symbol.Kind != SymbolKind.Local && !(symbol.Kind == SymbolKind.Parameter && symbol.ContainingSymbol.Kind == SymbolKind.Method && symbol.ContainingType != null && symbol.ContainingType.IsDelegateType() && symbol.ContainingType.AssociatedSymbol != null)) { // We enable the parameter in RaiseEvent, if the Event is declared with a signature. If the Event is declared as a // delegate type, we do not have a connection between the delegate type and the event. // this prevents a rename in this case :(. return(null); } if (symbol.Kind == SymbolKind.Property && symbol.ContainingType.IsAnonymousType) { return(null); } if (symbol.IsErrorType()) { return(null); } if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).MethodKind == MethodKind.UserDefinedOperator) { return(null); } var symbolLocations = symbol.Locations; // Does our symbol exist in an unchangeable location? foreach (var location in symbolLocations) { if (location.IsInMetadata) { return(null); } if (location.IsInSource) { if (document.Project.IsSubmission) { var solution = document.Project.Solution; var projectIdOfLocation = solution.GetDocument(location.SourceTree)?.Project.Id; if (solution.Projects.Any(p => p.IsSubmission && p.ProjectReferences.Any(r => r.ProjectId == projectIdOfLocation))) { return(null); } } } else { return(null); } } return(symbol); }
internal static IInlineRenameInfo GetRenameInfo( IEnumerable <IRefactorNotifyService> refactorNotifyServices, Document document, SyntaxToken triggerToken, CancellationToken cancellationToken) { var syntaxFactsService = document.Project.LanguageServices.GetService <ISyntaxFactsService>(); if (syntaxFactsService.IsKeyword(triggerToken)) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouMustRenameAnIdentifier)); } var semanticModel = document.GetSemanticModelAsync(cancellationToken).WaitAndGetResult(cancellationToken); var semanticFacts = document.GetLanguageService <ISemanticFactsService>(); var tokenRenameInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, triggerToken, cancellationToken); // Rename was invoked on a member group reference in a nameof expression. // Trigger the rename on any of the candidate symbols but force the // RenameOverloads option to be on. var triggerSymbol = tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null; if (triggerSymbol == null) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } // If rename is invoked on a member group reference in a nameof expression, then the // RenameOverloads option should be forced on. var forceRenameOverloads = tokenRenameInfo.IsMemberGroup; if (syntaxFactsService.IsTypeNamedVarInVariableOrFieldDeclaration(triggerToken, triggerToken.Parent)) { // To check if va in this context is a real type, or the keyword, we need to // speculatively bind the identifier "var". If it returns a symbol, it's a real type, // if not, it's the keyword. // see bugs 659683 (compiler API) and 659705 (rename/workspace api) for examples var symbolForVar = semanticModel.GetSpeculativeSymbolInfo( triggerToken.SpanStart, triggerToken.Parent, SpeculativeBindingOption.BindAsTypeOrNamespace).Symbol; if (symbolForVar == null) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } } var symbol = RenameLocations.ReferenceProcessing.GetRenamableSymbolAsync(document, triggerToken.SpanStart, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken); if (symbol == null) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } if (symbol.Kind == SymbolKind.Alias && symbol.IsExtern) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } // Cannot rename constructors in VB. TODO: this logic should be in the VB subclass of this type. var workspace = document.Project.Solution.Workspace; if (symbol != null && symbol.Kind == SymbolKind.NamedType && symbol.Language == LanguageNames.VisualBasic && triggerToken.ToString().Equals("New", StringComparison.OrdinalIgnoreCase)) { var originalSymbol = SymbolFinder.FindSymbolAtPosition(semanticModel, triggerToken.SpanStart, workspace, cancellationToken: cancellationToken); if (originalSymbol != null && originalSymbol.IsConstructor()) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } } if (syntaxFactsService.IsTypeNamedDynamic(triggerToken, triggerToken.Parent)) { if (symbol.Kind == SymbolKind.DynamicType) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } } // we allow implicit locals and parameters of Event handlers if (symbol.IsImplicitlyDeclared && symbol.Kind != SymbolKind.Local && !(symbol.Kind == SymbolKind.Parameter && symbol.ContainingSymbol.Kind == SymbolKind.Method && symbol.ContainingType != null && symbol.ContainingType.IsDelegateType() && symbol.ContainingType.AssociatedSymbol != null)) { // We enable the parameter in RaiseEvent, if the Event is declared with a signature. If the Event is declared as a // delegate type, we do not have a connection between the delegate type and the event. // this prevents a rename in this case :(. return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } if (symbol.Kind == SymbolKind.Property && symbol.ContainingType.IsAnonymousType) { return(new FailureInlineRenameInfo(EditorFeaturesResources.RenamingAnonymousTypeMemberNotSupported)); } if (symbol.IsErrorType()) { return(new FailureInlineRenameInfo(EditorFeaturesResources.PleaseResolveErrorsInYourCodeBeforeRenaming)); } if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).MethodKind == MethodKind.UserDefinedOperator) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameOperators)); } var symbolLocations = symbol.Locations; // Does our symbol exist in an unchangeable location? var navigationService = workspace.Services.GetService <IDocumentNavigationService>(); foreach (var location in symbolLocations) { if (location.IsInMetadata) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameElementsInMetadata)); } else if (location.IsInSource) { if (document.Project.IsSubmission) { var solution = document.Project.Solution; var projectIdOfLocation = solution.GetDocument(location.SourceTree).Project.Id; if (solution.Projects.Any(p => p.IsSubmission && p.ProjectReferences.Any(r => r.ProjectId == projectIdOfLocation))) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameElementsFromPrevSubmissions)); } } else { var sourceText = location.SourceTree.GetTextAsync(cancellationToken).WaitAndGetResult(cancellationToken); var textSnapshot = sourceText.FindCorrespondingEditorTextSnapshot(); if (textSnapshot != null) { var buffer = textSnapshot.TextBuffer; var originalSpan = location.SourceSpan.ToSnapshotSpan(textSnapshot).TranslateTo(buffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); if (buffer.IsReadOnly(originalSpan) || !navigationService.CanNavigateToSpan(workspace, document.Id, location.SourceSpan)) { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } } } } else { return(new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement)); } } return(new SymbolInlineRenameInfo(refactorNotifyServices, document, triggerToken.Span, symbol, forceRenameOverloads, cancellationToken)); }
private async Task <IInlineRenameInfo> GetRenameInfoAsync( IEnumerable <IRefactorNotifyService> refactorNotifyServices, Document document, SyntaxToken triggerToken, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); if (syntaxFacts.IsReservedOrContextualKeyword(triggerToken)) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_must_rename_an_identifier)); } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var semanticFacts = document.GetRequiredLanguageService <ISemanticFactsService>(); var tokenRenameInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, triggerToken, cancellationToken); // Rename was invoked on a member group reference in a nameof expression. // Trigger the rename on any of the candidate symbols but force the // RenameOverloads option to be on. var triggerSymbol = tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null; if (triggerSymbol == null) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } // see https://github.com/dotnet/roslyn/issues/10898 // we are disabling rename for tuple fields for now // 1) compiler does not return correct location information in these symbols // 2) renaming tuple fields seems a complex enough thing to require some design if (triggerSymbol.ContainingType?.IsTupleType == true) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } // If rename is invoked on a member group reference in a nameof expression, then the // RenameOverloads option should be forced on. var forceRenameOverloads = tokenRenameInfo.IsMemberGroup; var symbol = await RenameUtilities.TryGetRenamableSymbolAsync(document, triggerToken.SpanStart, cancellationToken : cancellationToken).ConfigureAwait(false); if (symbol == null) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } if (symbol.Kind == SymbolKind.Alias && symbol.IsExtern) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } // Cannot rename constructors in VB. TODO: this logic should be in the VB subclass of this type. var workspace = document.Project.Solution.Workspace; if (symbol.Kind == SymbolKind.NamedType && symbol.Language == LanguageNames.VisualBasic && triggerToken.ToString().Equals("New", StringComparison.OrdinalIgnoreCase)) { var originalSymbol = await SymbolFinder.FindSymbolAtPositionAsync( semanticModel, triggerToken.SpanStart, workspace, cancellationToken : cancellationToken).ConfigureAwait(false); if (originalSymbol != null && originalSymbol.IsConstructor()) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } } if (CheckLanguageSpecificIssues(semanticModel, symbol, triggerToken, out var langError)) { return(new FailureInlineRenameInfo(langError)); } // we allow implicit locals and parameters of Event handlers if (symbol.IsImplicitlyDeclared && symbol.Kind != SymbolKind.Local && !(symbol.Kind == SymbolKind.Parameter && symbol.ContainingSymbol.Kind == SymbolKind.Method && symbol.ContainingType != null && symbol.ContainingType.IsDelegateType() && symbol.ContainingType.AssociatedSymbol != null)) { // We enable the parameter in RaiseEvent, if the Event is declared with a signature. If the Event is declared as a // delegate type, we do not have a connection between the delegate type and the event. // this prevents a rename in this case :(. return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } if (symbol.Kind == SymbolKind.Property && symbol.ContainingType.IsAnonymousType) { return(new FailureInlineRenameInfo(EditorFeaturesResources.Renaming_anonymous_type_members_is_not_yet_supported)); } if (symbol.IsErrorType()) { return(new FailureInlineRenameInfo(EditorFeaturesResources.Please_resolve_errors_in_your_code_before_renaming_this_element)); } if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).MethodKind == MethodKind.UserDefinedOperator) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_operators)); } var symbolLocations = symbol.Locations; // Does our symbol exist in an unchangeable location? var documentSpans = ArrayBuilder <DocumentSpan> .GetInstance(); foreach (var location in symbolLocations) { if (location.IsInMetadata) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_elements_that_are_defined_in_metadata)); } else if (location.IsInSource) { var solution = document.Project.Solution; var sourceDocument = solution.GetRequiredDocument(location.SourceTree); if (sourceDocument is SourceGeneratedDocument) { // The file is generated so we can't go editing it (for now) return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } if (document.Project.IsSubmission) { var projectIdOfLocation = sourceDocument.Project.Id; if (solution.Projects.Any(p => p.IsSubmission && p.ProjectReferences.Any(r => r.ProjectId == projectIdOfLocation))) { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_elements_from_previous_submissions)); } } else { // We eventually need to return the symbol locations, so we must convert each location to a DocumentSpan since our return type is language-agnostic. documentSpans.Add(new DocumentSpan(sourceDocument, location.SourceSpan)); } } else { return(new FailureInlineRenameInfo(EditorFeaturesResources.You_cannot_rename_this_element)); } } var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var triggerText = sourceText.ToString(triggerToken.Span); var fallbackOptions = _globalOptions.CreateProvider(); return(new SymbolInlineRenameInfo( refactorNotifyServices, document, triggerToken.Span, triggerText, symbol, forceRenameOverloads, documentSpans.ToImmutableAndFree(), fallbackOptions, cancellationToken)); }