| File: GoToDefinition\CSharpGoToDefinitionSymbolService.cs | Web Access |
| Project: src\roslyn\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.GoToDefinition; [ExportLanguageService(typeof(IGoToDefinitionSymbolService), LanguageNames.CSharp), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class CSharpGoToDefinitionSymbolService() : AbstractGoToDefinitionSymbolService { protected override Task<ISymbol> FindRelatedExplicitlyDeclaredSymbolAsync(Project project, ISymbol symbol, CancellationToken cancellationToken) => Task.FromResult(symbol); protected override int? GetTargetPositionIfControlFlow(SemanticModel semanticModel, SyntaxToken token) { var node = token.GetRequiredParent(); switch (token.Kind()) { case SyntaxKind.ContinueKeyword: return GetBreakOrContinueTargetPosition(semanticModel, node, isBreak: false); case SyntaxKind.BreakKeyword: if (token.GetPreviousToken().IsKind(SyntaxKind.YieldKeyword)) goto case SyntaxKind.YieldKeyword; return GetBreakOrContinueTargetPosition(semanticModel, node, isBreak: true); case SyntaxKind.YieldKeyword: case SyntaxKind.ReturnKeyword: { var foundReturnableConstruct = TryFindContainingReturnableConstruct(node); if (foundReturnableConstruct is null) { return null; } var symbol = semanticModel.GetDeclaredSymbol(foundReturnableConstruct); if (symbol is null) { // for lambdas return foundReturnableConstruct.GetFirstToken().Span.Start; } return symbol.Locations.FirstOrDefault()?.SourceSpan.Start ?? 0; } case SyntaxKind.GotoKeyword: case SyntaxKind.DefaultKeyword: case SyntaxKind.CaseKeyword: { if (node.FirstAncestorOrSelf<GotoStatementSyntax>() is not GotoStatementSyntax gotoStatement) return null; if (semanticModel.GetOperation(gotoStatement) is not IBranchOperation gotoOperation) return null; var target = gotoOperation.Target; return target.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax()?.SpanStart; } } return null; static int? GetBreakOrContinueTargetPosition(SemanticModel semanticModel, SyntaxNode node, bool isBreak) { if (semanticModel.GetOperation(node) is not IBranchOperation branchOperation) return null; // 'corresponding' is the loop or switch that this break/continue transfers control to. var corresponding = branchOperation.GetCorrespondingOperation(); if (corresponding is null) return null; // break/continue cannot cross a lambda, anonymous method, or local function. If one of those // sits between the statement and the loop/switch that GetCorrespondingOperation found, the jump // is not actually valid (e.g. 'break' inside a lambda nested in a switch), so don't navigate. for (var current = node; current is not null && current != corresponding.Syntax; current = current.Parent) { if (current.IsReturnableConstruct()) return null; } // 'break' transfers control to just past the construct it exits, so navigate to its end. // 'continue' transfers control back to the construct itself, so navigate to its start. return isBreak ? corresponding.Syntax.GetLastToken().Span.End : corresponding.Syntax.GetFirstToken().Span.Start; } static SyntaxNode? TryFindContainingReturnableConstruct(SyntaxNode? node) { while (node is not null && !node.IsReturnableConstruct()) { if (SyntaxFacts.GetTypeDeclarationKind(node.Kind()) != SyntaxKind.None) { return null; } node = node.Parent; } return node; } } }