From 75bd9291f27087f927da50d4b46457eae48ad27a Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Wed, 15 Dec 2021 22:07:09 -0600 Subject: [PATCH 1/8] Initial changes for goToTypeDefinition --- .../GotoTypeDefinitionRequest.cs | 11 ++ .../GotoTypeDefinitionResponse.cs | 20 +++ .../OmniSharpEndpoints.cs | 2 + .../Navigation/GotoTypeDefinitionHandler.cs | 116 +++++++++++++++++ .../GotoTypeDefinitionHandlerHelper.cs | 122 ++++++++++++++++++ .../Navigation/GotoTypeDefinitionHelpers.cs | 64 +++++++++ .../Navigation/GotoTypeDefinitionService.cs | 88 +++++++++++++ 7 files changed, 423 insertions(+) create mode 100644 src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs create mode 100644 src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs create mode 100644 src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs create mode 100644 src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs create mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionHelpers.cs create mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs diff --git a/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs new file mode 100644 index 0000000000..3c957d5c82 --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs @@ -0,0 +1,11 @@ +using OmniSharp.Mef; + +namespace OmniSharp.Models.V2.GotoTypeDefinition +{ + [OmniSharpEndpoint(OmniSharpEndpoints.V2.GotoTypeDefinition, typeof(GotoTypeDefinitionRequest), typeof(GotoTypeDefinitionResponse))] + public class GotoTypeDefinitionRequest : Request + { + public int Timeout { get; init; } = 10000; + public bool WantMetadata { get; init; } + } +} diff --git a/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs new file mode 100644 index 0000000000..c00fcdb13d --- /dev/null +++ b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs @@ -0,0 +1,20 @@ +#nullable enable + +using OmniSharp.Models.Metadata; +using OmniSharp.Models.v1.SourceGeneratedFile; +using System.Collections.Generic; + +namespace OmniSharp.Models.V2.GotoTypeDefinition +{ + public record GotoTypeDefinitionResponse + { + public List? Definitions { get; init; } + } + + public record TypeDefinition + { + public Location Location { get; init; } = null!; + public MetadataSource? MetadataSource { get; init; } + public SourceGeneratedFileInfo? SourceGeneratedFileInfo { get; init; } + } +} diff --git a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs index c26fa48fa0..3c25efc4a5 100644 --- a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs +++ b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs @@ -77,6 +77,8 @@ public static class V2 public const string Highlight = "/v2/highlight"; public const string GotoDefinition = "/v2/gotodefinition"; + + public const string GotoTypeDefinition = "/v2/gototypedefinition"; } } } diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs new file mode 100644 index 0000000000..1cbc6d7e92 --- /dev/null +++ b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using OmniSharp.Extensions; +using OmniSharp.Mef; +using OmniSharp.Models.V2.GotoDefinition; +using OmniSharp.Models.Metadata; +using OmniSharp.Models.v1.SourceGeneratedFile; +using OmniSharp.Models.V2; +using OmniSharp.Roslyn; +using OmniSharp.Utilities; +using Location = OmniSharp.Models.V2.Location; +using Range = OmniSharp.Models.V2.Range; + +namespace OmniSharp.Cake.Services.RequestHandlers.Navigation +{ + [OmniSharpHandler(OmniSharpEndpoints.V2.GotoDefinition, Constants.LanguageNames.Cake), Shared] + public class GotoDefinitionV2Handler : CakeRequestHandler + { + private readonly MetadataExternalSourceService _metadataExternalSourceService; + + [ImportingConstructor] + public GotoDefinitionV2Handler( + OmniSharpWorkspace workspace, + MetadataExternalSourceService metadataExternalSourceService) + : base(workspace) + { + _metadataExternalSourceService = metadataExternalSourceService ?? throw new ArgumentNullException(nameof(metadataExternalSourceService)); + } + + protected override async Task TranslateResponse(GotoDefinitionResponse response, GotoDefinitionRequest request) + { + var definitions = new List(); + foreach (var definition in response.Definitions ?? Enumerable.Empty()) + { + var file = definition.Location.FileName; + + if (string.IsNullOrEmpty(file) || !file.Equals(Constants.Paths.Generated)) + { + if (PlatformHelper.IsWindows && !string.IsNullOrEmpty(file)) + { + file = file.Replace('/', '\\'); + } + + definitions.Add(new Definition + { + MetadataSource = definition.MetadataSource, + SourceGeneratedFileInfo = definition.SourceGeneratedFileInfo, + Location = new Location + { + FileName = file, + Range = definition.Location.Range + } + }); + + continue; + } + + if (!request.WantMetadata) + { + continue; + } + + var aliasLocations = await GotoDefinitionHandlerHelper.GetAliasFromMetadataAsync( + Workspace, + request.FileName, + definition.Location.Range.End.Line, + request.Timeout, + _metadataExternalSourceService + ); + + definitions.AddRange( + aliasLocations.Select(loc => + new Definition + { + Location = new Location + { + FileName = loc.MetadataDocument.FilePath ?? loc.MetadataDocument.Name, + Range = new Range + { + Start = new Point + { + Column = loc.LineSpan.StartLinePosition.Character, + Line = loc.LineSpan.StartLinePosition.Line + }, + End = new Point + { + Column = loc.LineSpan.EndLinePosition.Character, + Line = loc.LineSpan.EndLinePosition.Line + }, + } + }, + MetadataSource = new MetadataSource + { + AssemblyName = loc.Symbol.ContainingAssembly.Name, + ProjectName = loc.Document.Project.Name, + TypeName = loc.Symbol.GetSymbolName() + }, + SourceGeneratedFileInfo = new SourceGeneratedFileInfo + { + DocumentGuid = loc.Document.Id.Id, + ProjectGuid = loc.Document.Id.ProjectId.Id + } + }) + .ToList()); + } + + return new GotoDefinitionResponse + { + Definitions = definitions + }; + } + } +} diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs new file mode 100644 index 0000000000..63cc3b1896 --- /dev/null +++ b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Text; +using OmniSharp.Roslyn; + +namespace OmniSharp.Cake.Services.RequestHandlers.Navigation +{ + public static class GotoTypeDefinitionHandlerHelper + { + private const int MethodLineOffset = 3; + private const int PropertyLineOffset = 7; + + internal static async Task> GetAliasFromMetadataAsync( + OmniSharpWorkspace workspace, + string fileName, + int line, + int timeout, + MetadataExternalSourceService metadataExternalSourceService) + { + var document = workspace.GetDocument(fileName); + var lineIndex = line + MethodLineOffset; + int column; + + if (document == null) + { + return Enumerable.Empty(); + } + + var semanticModel = await document.GetSemanticModelAsync(); + var sourceText = await document.GetTextAsync(); + var sourceLine = sourceText.Lines[lineIndex].ToString(); + if (sourceLine.Contains("(Context")) + { + column = sourceLine.IndexOf("(Context", StringComparison.Ordinal); + } + else + { + lineIndex = line + PropertyLineOffset; + sourceLine = sourceText.Lines[lineIndex].ToString(); + if (sourceLine.Contains("(Context")) + { + column = sourceLine.IndexOf("(Context", StringComparison.Ordinal); + } + else + { + return Enumerable.Empty(); + } + } + + if (column > 0 && sourceLine[column - 1] == '>') + { + column = sourceLine.LastIndexOf("<", column, StringComparison.Ordinal); + } + + var position = sourceText.Lines.GetPosition(new LinePosition(lineIndex, column)); + var symbol = await SymbolFinder.FindSymbolAtPositionAsync(semanticModel, position, workspace); + + if (symbol == null || symbol is INamespaceSymbol) + { + return Enumerable.Empty(); + } + if (symbol is IMethodSymbol method) + { + symbol = method.PartialImplementationPart ?? symbol; + } + + var typeSymbol = symbol switch + { + ILocalSymbol localSymbol => localSymbol.Type, + IFieldSymbol fieldSymbol => fieldSymbol.Type, + IPropertySymbol propertySymbol => propertySymbol.Type, + IParameterSymbol parameterSymbol => parameterSymbol.Type, + _ => null + }; + + var result = new List(); + foreach (var location in typeSymbol.Locations) + { + if (!location.IsInMetadata) + { + continue; + } + + var cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout)); + var (metadataDocument, _) = await metadataExternalSourceService.GetAndAddExternalSymbolDocument(document.Project, typeSymbol, cancellationSource.Token); + if (metadataDocument == null) + { + continue; + } + + cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout)); + var metadataLocation = await metadataExternalSourceService.GetExternalSymbolLocation(typeSymbol, metadataDocument, cancellationSource.Token); + var lineSpan = metadataLocation.GetMappedLineSpan(); + + result.Add(new Alias + { + Document = document, + MetadataDocument = metadataDocument, + Symbol = typeSymbol, + Location = location, + LineSpan = lineSpan + }); + } + + return result; + } + + internal class Alias + { + public Document Document { get; set; } + public ISymbol Symbol { get; set; } + public Location Location { get; set; } + public FileLinePositionSpan LineSpan { get; set; } + public Document MetadataDocument { get; set; } + } + } +} diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionHelpers.cs b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionHelpers.cs new file mode 100644 index 0000000000..bfc3d88aea --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionHelpers.cs @@ -0,0 +1,64 @@ +#nullable enable + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Operations; +using OmniSharp.Extensions; +using OmniSharp.Models.v1.SourceGeneratedFile; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace OmniSharp.Roslyn.CSharp.Services.Navigation +{ + internal static class GotoTypeDefinitionHelpers + { + internal static async Task GetTypeOfSymbol(Document document, int line, int column, CancellationToken cancellationToken) + { + var sourceText = await document.GetTextAsync(cancellationToken); + var position = sourceText.GetPositionFromLineAndOffset(line, column); + var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken); + + return symbol switch + { + ILocalSymbol localSymbol => localSymbol.Type, + IFieldSymbol fieldSymbol => fieldSymbol.Type, + IPropertySymbol propertySymbol => propertySymbol.Type, + IParameterSymbol parameterSymbol => parameterSymbol.Type, + _ => null + }; + } + + internal static async Task GetMetadataMappedSpan( + Document document, + ISymbol symbol, + IExternalSourceService externalSourceService, + CancellationToken cancellationToken) + { + var (metadataDocument, _) = await externalSourceService.GetAndAddExternalSymbolDocument(document.Project, symbol, cancellationToken); + if (metadataDocument != null) + { + var metadataLocation = await externalSourceService.GetExternalSymbolLocation(symbol, metadataDocument, cancellationToken); + return metadataLocation.GetMappedLineSpan(); + } + + return null; + } + + internal static SourceGeneratedFileInfo? GetSourceGeneratedFileInfo(OmniSharpWorkspace workspace, Location location) + { + Debug.Assert(location.IsInSource); + var document = workspace.CurrentSolution.GetDocument(location.SourceTree); + if (document is not SourceGeneratedDocument) + { + return null; + } + + return new SourceGeneratedFileInfo + { + ProjectGuid = document.Project.Id.Id, + DocumentGuid = document.Id.Id + }; + } + } +} diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs new file mode 100644 index 0000000000..4a7f8b87ab --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs @@ -0,0 +1,88 @@ +#nullable enable + +using Microsoft.CodeAnalysis; +using OmniSharp.Extensions; +using OmniSharp.Mef; +using OmniSharp.Models.V2.GotoTypeDefinition; +using OmniSharp.Options; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; + +namespace OmniSharp.Roslyn.CSharp.Services.Navigation +{ + [OmniSharpHandler(OmniSharpEndpoints.V2.GotoTypeDefinition, LanguageNames.CSharp)] + public class GotoTypeDefinitionServiceV2 : IRequestHandler + { + private readonly OmniSharpOptions _omnisharpOptions; + private readonly OmniSharpWorkspace _workspace; + private readonly ExternalSourceServiceFactory _externalSourceServiceFactory; + + [ImportingConstructor] + public GotoTypeDefinitionServiceV2(OmniSharpWorkspace workspace, ExternalSourceServiceFactory externalSourceServiceFactory, OmniSharpOptions omnisharpOptions) + { + _workspace = workspace; + _externalSourceServiceFactory = externalSourceServiceFactory; + _omnisharpOptions = omnisharpOptions; + } + + public async Task Handle(GotoTypeDefinitionRequest request) + { + var cancellationToken = _externalSourceServiceFactory.CreateCancellationToken(_omnisharpOptions, request.Timeout); + var externalSourceService = _externalSourceServiceFactory.Create(_omnisharpOptions); + var document = externalSourceService.FindDocumentInCache(request.FileName) ?? + _workspace.GetDocument(request.FileName); + + if (document == null) + { + return new GotoTypeDefinitionResponse(); + } + + var typeSymbol = await GotoTypeDefinitionHelpers.GetTypeOfSymbol(document, request.Line, request.Column, cancellationToken); + if (typeSymbol?.Locations.IsDefaultOrEmpty != false) + { + return new GotoTypeDefinitionResponse(); + } + + if (typeSymbol.Locations[0].IsInSource) + { + return new GotoTypeDefinitionResponse() + { + Definitions = typeSymbol.Locations + .Select(location => new TypeDefinition + { + Location = location.GetMappedLineSpan().GetLocationFromFileLinePositionSpan(), + SourceGeneratedFileInfo = GoToDefinitionHelpers.GetSourceGeneratedFileInfo(_workspace, location) + }) + .ToList() + }; + } + else + { + var maybeSpan = await GoToDefinitionHelpers.GetMetadataMappedSpan(document, typeSymbol, externalSourceService, cancellationToken); + + if (maybeSpan is FileLinePositionSpan lineSpan) + { + return new GotoTypeDefinitionResponse + { + Definitions = new() + { + new TypeDefinition + { + Location = lineSpan.GetLocationFromFileLinePositionSpan(), + MetadataSource = new OmniSharp.Models.Metadata.MetadataSource() + { + AssemblyName = typeSymbol.ContainingAssembly.Name, + ProjectName = document.Project.Name, + TypeName = typeSymbol.GetSymbolName() + } + } + } + }; + } + + return new GotoTypeDefinitionResponse(); + } + } + } +} From 7cf4938a6ad5d8298a08efe27b6aaa93dd6d9a37 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Mon, 20 Dec 2021 09:41:06 -0600 Subject: [PATCH 2/8] Initial support for GotoTypeDefinition Seems to pass basic tests, but fails to retrieve data in metadata --- .../Navigation/GotoTypeDefinitionHandler.cs | 22 +- .../Navigation/GotoTypeDefinitionService.cs | 4 +- .../GotoTypeDefinitionFacts.cs | 681 ++++++++++++++++++ 3 files changed, 694 insertions(+), 13 deletions(-) create mode 100644 tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs index 1cbc6d7e92..7b8501ec8b 100644 --- a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs +++ b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using OmniSharp.Extensions; using OmniSharp.Mef; -using OmniSharp.Models.V2.GotoDefinition; +using OmniSharp.Models.V2.GotoTypeDefinition; using OmniSharp.Models.Metadata; using OmniSharp.Models.v1.SourceGeneratedFile; using OmniSharp.Models.V2; @@ -16,13 +16,13 @@ namespace OmniSharp.Cake.Services.RequestHandlers.Navigation { - [OmniSharpHandler(OmniSharpEndpoints.V2.GotoDefinition, Constants.LanguageNames.Cake), Shared] - public class GotoDefinitionV2Handler : CakeRequestHandler + [OmniSharpHandler(OmniSharpEndpoints.V2.GotoTypeDefinition, Constants.LanguageNames.Cake), Shared] + public class GotoTypeDefinitionV2Handler : CakeRequestHandler { private readonly MetadataExternalSourceService _metadataExternalSourceService; [ImportingConstructor] - public GotoDefinitionV2Handler( + public GotoTypeDefinitionV2Handler( OmniSharpWorkspace workspace, MetadataExternalSourceService metadataExternalSourceService) : base(workspace) @@ -30,10 +30,10 @@ public GotoDefinitionV2Handler( _metadataExternalSourceService = metadataExternalSourceService ?? throw new ArgumentNullException(nameof(metadataExternalSourceService)); } - protected override async Task TranslateResponse(GotoDefinitionResponse response, GotoDefinitionRequest request) + protected override async Task TranslateResponse(GotoTypeDefinitionResponse response, GotoTypeDefinitionRequest request) { - var definitions = new List(); - foreach (var definition in response.Definitions ?? Enumerable.Empty()) + var definitions = new List(); + foreach (var definition in response.Definitions ?? Enumerable.Empty()) { var file = definition.Location.FileName; @@ -44,7 +44,7 @@ protected override async Task TranslateResponse(GotoDefi file = file.Replace('/', '\\'); } - definitions.Add(new Definition + definitions.Add(new TypeDefinition { MetadataSource = definition.MetadataSource, SourceGeneratedFileInfo = definition.SourceGeneratedFileInfo, @@ -63,7 +63,7 @@ protected override async Task TranslateResponse(GotoDefi continue; } - var aliasLocations = await GotoDefinitionHandlerHelper.GetAliasFromMetadataAsync( + var aliasLocations = await GotoTypeDefinitionHandlerHelper.GetAliasFromMetadataAsync( Workspace, request.FileName, definition.Location.Range.End.Line, @@ -73,7 +73,7 @@ protected override async Task TranslateResponse(GotoDefi definitions.AddRange( aliasLocations.Select(loc => - new Definition + new TypeDefinition { Location = new Location { @@ -107,7 +107,7 @@ protected override async Task TranslateResponse(GotoDefi .ToList()); } - return new GotoDefinitionResponse + return new GotoTypeDefinitionResponse { Definitions = definitions }; diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs index 4a7f8b87ab..ffbc700a0c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs @@ -12,14 +12,14 @@ namespace OmniSharp.Roslyn.CSharp.Services.Navigation { [OmniSharpHandler(OmniSharpEndpoints.V2.GotoTypeDefinition, LanguageNames.CSharp)] - public class GotoTypeDefinitionServiceV2 : IRequestHandler + public class GotoTypeDefinitionService : IRequestHandler { private readonly OmniSharpOptions _omnisharpOptions; private readonly OmniSharpWorkspace _workspace; private readonly ExternalSourceServiceFactory _externalSourceServiceFactory; [ImportingConstructor] - public GotoTypeDefinitionServiceV2(OmniSharpWorkspace workspace, ExternalSourceServiceFactory externalSourceServiceFactory, OmniSharpOptions omnisharpOptions) + public GotoTypeDefinitionService(OmniSharpWorkspace workspace, ExternalSourceServiceFactory externalSourceServiceFactory, OmniSharpOptions omnisharpOptions) { _workspace = workspace; _externalSourceServiceFactory = externalSourceServiceFactory; diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs new file mode 100644 index 0000000000..423a606c47 --- /dev/null +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs @@ -0,0 +1,681 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using OmniSharp.Mef; +using OmniSharp.Models.Metadata; +using OmniSharp.Models.v1.SourceGeneratedFile; +using OmniSharp.Models.V2.GotoTypeDefinition; +using OmniSharp.Roslyn.CSharp.Services.Navigation; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class GotoTypeDefinitionFacts : AbstractSingleRequestHandlerTestFixture + { + public GotoTypeDefinitionFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture) + : base(output, sharedOmniSharpHostFixture) + { + } + protected override string EndpointName => OmniSharpEndpoints.V2.GotoTypeDefinition; + + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsDefinitionInSameFile(string filename) + { + var testFile = new TestFile(filename, @" +class {|def:Foo|} { + private Foo f$$oo; +}"); + + await TestGoToSourceAsync(testFile); + } + + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task DoesNotReturnOnPropertAccessorGet(string filename) + { + var testFile = new TestFile(filename, @" +class Test { + public string Foo{ g$$et; set; } +}"); + + await TestGoToSourceAsync(testFile); + } + + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task DoesNotReturnOnPropertAccessorSet(string filename) + { + var testFile = new TestFile(filename, @" +class Test { + public int Foo{ get; s$$et; } +}"); + + await TestGoToSourceAsync(testFile); + } + +// [Theory] +// [InlineData("foo.cs")] +// [InlineData("foo.csx")] +// public async Task DoesNotReturnOnPropertyAccessorPropertyDef(string filename) +// { +// var testFile = new TestFile(filename, @" +//class Test { +// public int |def:Foo| Fo$$o{ get; set; } +//}"); + +// await TestGoToSourceAsync(testFile); +// } + + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsOnPropertyAccessorPropertySetting(string filename) + { + var testFile = new TestFile(filename, @" +class Test { + public Bar Foo{ get; set; } + public class |def:Bar| + { + public int lorem { get; set; } + } + public static void main() + { + F$$oo = 3; + } +}"); + + await TestGoToSourceAsync(testFile); + } + + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsOnPropertyAccessorField1(string filename) + { + var testFile = new TestFile(filename, @" +class Test { + + public Bar foo; + public class |def:Bar| + { + public int lorem { get; set; } + } + public int Foo + { + get => f$$oo; + set => foo = value; + } +}"); + + await TestGoToSourceAsync(testFile); + } + + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsOnPropertyAccessorField2(string filename) + { + var testFile = new TestFile(filename, @" +class Test { + + public Bar foo { get; set; }; + public class |def:Bar| + { + public int lorem { get; set; } + } + public int Foo + { + get => foo; + set => f$$oo = value; + } +}"); + + await TestGoToSourceAsync(testFile); + } + + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsOnPropertyAccessorPropertyGetting(string filename) + { + var testFile = new TestFile(filename, @" +class Test { + public Bar Foo { get; set; } + public class |def:Bar| + { + public int lorem { get; set; } + } + public static void main() + { + Foo = 3; + Console.WriteLine(F$$oo); + } +}"); + + await TestGoToSourceAsync(testFile); + } + + + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsPartialMethodDefinitionWithBody(string filename) +// { +// var testFile = new TestFile(filename, @" +// public partial class MyClass +// { +// public MyClass() +// { +// Met$$hod(); +// } + +// partial void {|def:Method|}() +// { +// //do stuff +// } +// } + +// public partial class MyClass +// { +// partial void Method(); +// }"); + +// await TestGoToSourceAsync(testFile); +// } + +// [Fact] +// public async Task ReturnsDefinitionInDifferentFile() +// { +// var testFile1 = new TestFile("foo.cs", @" +//using System; +//class {|def:Foo|} { +//}"); +// var testFile2 = new TestFile("bar.cs", @" +//class Bar { +// private F$$oo foo; +//}"); + +// await TestGoToSourceAsync(testFile1, testFile2); +// } + +// [Fact] +// public async Task ReturnsEmptyResultWhenDefinitionIsNotFound() +// { +// var testFile1 = new TestFile("foo.cs", @" +//using System; +//class Foo { +//}"); +// var testFile2 = new TestFile("bar.cs", @" +//class Bar { +// private B$$az foo; +//}"); + +// await TestGoToSourceAsync(testFile1, testFile2); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsStaticMethod(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System; +//class Bar { +// public void Baz() { +// Guid.NewG$$uid(); +// } +//}"); + +// await TestGoToMetadataAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.Guid"); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDecompiledDefinition_WhenSymbolIsStaticMethod(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System; +//class Bar { +// public void Baz() { +// Guid.NewG$$uid(); +// } +//}"); + +// await TestDecompilationAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.Guid"); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsInstanceMethod(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System.Collections.Generic; +//class Bar { +// public void Baz() { +// var foo = new List(); +// foo.ToAr$$ray(); +// } +//}"); + +// await TestGoToMetadataAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.Collections.Generic.List`1"); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDecompiledDefinition_WhenSymbolIsInstanceMethod(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System.Collections.Generic; +//class Bar { +// public void Baz() { +// var foo = new List(); +// foo.ToAr$$ray(); +// } +//}"); + +// await TestDecompilationAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.Collections.Generic.List`1"); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsGenericType(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System.Collections.Generic; +//class Bar { +// public void Baz() { +// var foo = new Li$$st(); +// foo.ToArray(); +// } +//}"); + +// await TestGoToMetadataAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.Collections.Generic.List`1"); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDecompiledDefinition_WhenSymbolIsGenericType(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System.Collections.Generic; +//class Bar { +// public void Baz() { +// var foo = new Li$$st(); +// foo.ToArray(); +// } +//}"); + +// await TestDecompilationAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.Collections.Generic.List`1"); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsType(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System; +//class Bar { +// public void Baz() { +// var str = Stri$$ng.Empty; +// } +//}"); + +// await TestGoToMetadataAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.String"); +// } + +// [Theory] +// [InlineData("bar.cs")] +// [InlineData("bar.csx")] +// public async Task ReturnsDecompiledDefinition_WhenSymbolIsType(string filename) +// { +// var testFile = new TestFile(filename, @" +//using System; +//class Bar { +// public void Baz() { +// var str = Stri$$ng.Empty; +// } +//}"); + +// await TestDecompilationAsync(testFile, +// expectedAssemblyName: AssemblyHelpers.CorLibName, +// expectedTypeName: "System.String"); +// } + + [Theory] + [InlineData("bar.cs")] + [InlineData("bar.csx")] + public async Task ReturnsDefinitionInMetadata_FromMetadata_WhenSymbolIsType(string filename) + { + var testFile = new TestFile(filename, @" +using System; +class Bar { + public void Baz() { + var num$$ber = int.MaxValue; + } +}"); + + using (var host = CreateOmniSharpHost(testFile)) + { + var point = testFile.Content.GetPointFromPosition(); + + // 1. start by asking for definition of "int" + var gotoDefinitionRequest = CreateRequest(testFile.FileName, point.Line, point.Offset, wantMetadata: true, timeout: 60000); + var gotoDefinitionRequestHandler = GetRequestHandler(host); + var gotoDefinitionResponse = await gotoDefinitionRequestHandler.Handle(gotoDefinitionRequest); + var gotoDefinitionResponseMetadataSource = GetMetadataSource(gotoDefinitionResponse); + + // 2. now, based on the response information + // go to the metadata endpoint, and ask for "int" specific metadata + var metadataRequest = new MetadataRequest + { + AssemblyName = gotoDefinitionResponseMetadataSource.AssemblyName, + TypeName = gotoDefinitionResponseMetadataSource.TypeName, + ProjectName = gotoDefinitionResponseMetadataSource.ProjectName, + Language = gotoDefinitionResponseMetadataSource.Language + }; + var metadataRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.Metadata); + var metadataResponse = await metadataRequestHandler.Handle(metadataRequest); + + // 3. the metadata response contains SourceName (metadata "file") and SourceText (syntax tree) + // use the source to locate "IComparable" which is an interface implemented by Int32 struct + var metadataTree = CSharpSyntaxTree.ParseText(metadataResponse.Source); + + var iComparable = metadataTree.GetCompilationUnitRoot(). + DescendantNodesAndSelf(). + OfType().First(). + BaseList.Types.FirstOrDefault(x => x.Type.ToString() == "IComparable"); + var relevantLineSpan = iComparable.GetLocation().GetLineSpan(); + + // 4. now ask for the definition of "IComparable" + // pass in the SourceName (metadata "file") as FileName - since it's not a regular file in our workspace + var metadataNavigationRequest = CreateRequest(metadataResponse.SourceName, relevantLineSpan.StartLinePosition.Line, relevantLineSpan.StartLinePosition.Character, wantMetadata: true); + var metadataNavigationResponse = await gotoDefinitionRequestHandler.Handle(metadataNavigationRequest); + var metadataNavigationResponseMetadataSource = GetMetadataSource(metadataNavigationResponse); + var info = GetInfo(metadataNavigationResponse); + + // 5. validate the response to be matching the expected IComparable meta info + Assert.NotNull(metadataNavigationResponseMetadataSource); + Assert.Equal(AssemblyHelpers.CorLibName, metadataNavigationResponseMetadataSource.AssemblyName); + Assert.Equal("System.IComparable", metadataNavigationResponseMetadataSource.TypeName); + + Assert.NotEqual(0, info.Single().Line); + Assert.NotEqual(0, info.Single().Column); + } + } + + [Theory] + [InlineData("bar.cs")] + [InlineData("bar.csx")] + public async Task ReturnsDecompiledDefinition_FromMetadata_WhenSymbolIsType(string filename) + { + var testFile = new TestFile(filename, @" +using System; +class Bar { + public void Baz() { + var num$$ber = int.MaxValue; + } +}"); + + using var host = CreateOmniSharpHost(new[] { testFile }, new Dictionary + { + ["RoslynExtensionsOptions:EnableDecompilationSupport"] = "true" + }); + + var point = testFile.Content.GetPointFromPosition(); + + // 1. start by asking for definition of "int" + var gotoDefinitionRequest = CreateRequest(testFile.FileName, point.Line, point.Offset, wantMetadata: true, timeout: 60000); + var gotoDefinitionRequestHandler = GetRequestHandler(host); + var gotoDefinitionResponse = await gotoDefinitionRequestHandler.Handle(gotoDefinitionRequest); + + // 2. now, based on the response information + // go to the metadata endpoint, and ask for "int" specific decompiled source + var metadataSource = GetMetadataSource(gotoDefinitionResponse); + var metadataRequest = new MetadataRequest + { + AssemblyName = metadataSource!.AssemblyName, + TypeName = metadataSource.TypeName, + ProjectName = metadataSource.ProjectName, + Language = metadataSource.Language, + Timeout = 60000 + }; + var metadataRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.Metadata); + var metadataResponse = await metadataRequestHandler.Handle(metadataRequest); + + // 3. the response contains SourceName ("file") and SourceText (syntax tree) + // use the source to locate "IComparable" which is an interface implemented by Int32 struct + var decompiledTree = CSharpSyntaxTree.ParseText(metadataResponse.Source); + var compilationUnit = decompiledTree.GetCompilationUnitRoot(); + + // second comment should indicate we have decompiled + var comments = compilationUnit.DescendantTrivia().Where(t => t.Kind() == SyntaxKind.SingleLineCommentTrivia).ToArray(); + Assert.NotNull(comments); + Assert.Equal("// Decompiled with ICSharpCode.Decompiler 7.1.0.6543", comments[1].ToString()); + + // contrary to regular metadata, we should have methods with full bodies + // this condition would fail if decompilation wouldn't work + var methods = compilationUnit. + DescendantNodesAndSelf(). + OfType(). + Where(m => m.Body != null); + + Assert.NotEmpty(methods); + + var iComparable = compilationUnit. + DescendantNodesAndSelf(). + OfType().First(). + BaseList.Types.FirstOrDefault(x => x.Type.ToString() == "IComparable"); + var relevantLineSpan = iComparable.GetLocation().GetLineSpan(); + + // 4. now ask for the definition of "IComparable" + // pass in the SourceName (metadata "file") as FileName - since it's not a regular file in our workspace + var metadataNavigationRequest = CreateRequest(metadataResponse.SourceName, relevantLineSpan.StartLinePosition.Line, relevantLineSpan.StartLinePosition.Character, wantMetadata: true); + var metadataNavigationResponse = await gotoDefinitionRequestHandler.Handle(metadataNavigationRequest); + var metadataSourceResponse = GetMetadataSource(metadataNavigationResponse); + var metadataNavigationInfo = GetInfo(metadataNavigationResponse); + + // 5. validate the response to be matching the expected IComparable meta info + Assert.NotNull(metadataSource); + Assert.Equal(AssemblyHelpers.CorLibName, metadataSourceResponse.AssemblyName); + Assert.Equal("System.IComparable", metadataSourceResponse.TypeName); + + Assert.NotEqual(0, metadataNavigationInfo.Single().Line); + Assert.NotEqual(0, metadataNavigationInfo.Single().Column); + } + + [Fact] + public async Task ReturnsNoResultsButDoesNotThrowForNamespaces() + { + var testFile = new TestFile("foo.cs", "namespace F$$oo {}"); + var response = await GetResponseAsync(new[] { testFile }, wantMetadata: false); + Assert.Empty(GetInfo(response)); + } + +// [Fact] +// public async Task ReturnsResultsForSourceGenerators() +// { +// const string Source = @" +//public class {|generatedClassName:Generated|} +//{ +// public int {|propertyName:Property|} { get; set; } +//} +//"; +// const string FileName = "real.cs"; +// TestFile generatedTestFile = new("GeneratedFile.cs", Source); +// var testFile = new TestFile(FileName, @" +//class C +//{ +// public void M(Generated g) +// { +// _ = g.P$$roperty; +// } +//} +//"); + +// TestHelpers.AddProjectToWorkspace(SharedOmniSharpTestHost.Workspace, +// "project.csproj", +// new[] { "netcoreapp3.1" }, +// new[] { testFile }, +// analyzerRefs: ImmutableArray.Create(new TestGeneratorReference( +// context => context.AddSource("GeneratedFile", generatedTestFile.Content.Code)))); + +// var point = testFile.Content.GetPointFromPosition(); + +// var gotoDefRequest = CreateRequest(FileName, point.Line, point.Offset, wantMetadata: true); +// var gotoDefHandler = GetRequestHandler(SharedOmniSharpTestHost); +// var response = await gotoDefHandler.Handle(gotoDefRequest); +// var info = GetInfo(response).Single(); + +// Assert.NotNull(info.SourceGeneratorInfo); + +// var expectedSpan = generatedTestFile.Content.GetSpans("propertyName").Single(); +// var expectedRange = generatedTestFile.Content.GetRangeFromSpan(expectedSpan); + +// Assert.Equal(expectedRange.Start.Line, info.Line); +// Assert.Equal(expectedRange.Start.Offset, info.Column); + +// var sourceGeneratedFileHandler = SharedOmniSharpTestHost.GetRequestHandler(OmniSharpEndpoints.SourceGeneratedFile); +// var sourceGeneratedRequest = new SourceGeneratedFileRequest +// { +// DocumentGuid = info.SourceGeneratorInfo.DocumentGuid, +// ProjectGuid = info.SourceGeneratorInfo.ProjectGuid +// }; + +// var sourceGeneratedFileResponse = await sourceGeneratedFileHandler.Handle(sourceGeneratedRequest); +// Assert.NotNull(sourceGeneratedFileResponse); +// Assert.Equal(generatedTestFile.Content.Code, sourceGeneratedFileResponse.Source); +// Assert.Equal(@"OmniSharp.Roslyn.CSharp.Tests\OmniSharp.Roslyn.CSharp.Tests.TestSourceGenerator\GeneratedFile.cs", sourceGeneratedFileResponse.SourceName.Replace("/", @"\")); +// } + + protected async Task TestGoToSourceAsync(params TestFile[] testFiles) + { + var response = await GetResponseAsync(testFiles, wantMetadata: false); + + var targets = + from tf in testFiles + from span in tf.Content.GetSpans("def") + select (tf, span); + + var info = GetInfo(response); + + if (targets.Any()) + { + foreach (var (file, definitionSpan) in targets) + { + var definitionRange = file.Content.GetRangeFromSpan(definitionSpan); + + Assert.Contains(info, + def => file.FileName == def.FileName + && definitionRange.Start.Line == def.Line + && definitionRange.Start.Offset == def.Column); + } + } + else + { + Assert.Empty(info); + } + } + + protected async Task TestDecompilationAsync(TestFile testFile, string expectedAssemblyName, string expectedTypeName) + { + using var host = CreateOmniSharpHost(new[] { testFile }, new Dictionary + { + ["RoslynExtensionsOptions:EnableDecompilationSupport"] = "true" + }); + + var response = await GetResponseAsync(new[] { testFile }, wantMetadata: true); + var metadataSource = GetMetadataSource(response); + + Assert.NotNull(metadataSource); + Assert.NotEmpty(GetInfo(response)); + Assert.Equal(expectedAssemblyName, metadataSource.AssemblyName); + Assert.Equal(expectedTypeName, metadataSource.TypeName); + } + + protected async Task TestGoToMetadataAsync(TestFile testFile, string expectedAssemblyName, string expectedTypeName) + { + var response = await GetResponseAsync(new[] { testFile }, wantMetadata: true); + var metadataSource = GetMetadataSource(response); + + var responseInfo = GetInfo(response); + Assert.NotNull(metadataSource); + Assert.NotEmpty(responseInfo); + Assert.Equal(expectedAssemblyName, metadataSource.AssemblyName); + Assert.Equal(expectedTypeName, metadataSource.TypeName); + + // We probably shouldn't hard code metadata locations (they could change randomly) + Assert.NotEqual(0, responseInfo.Single().Line); + Assert.NotEqual(0, responseInfo.Single().Column); + } + + protected async Task GetResponseAsync(TestFile[] testFiles, bool wantMetadata) + { + SharedOmniSharpTestHost.AddFilesToWorkspace(testFiles); + var source = testFiles.Single(tf => tf.Content.HasPosition); + var point = source.Content.GetPointFromPosition(); + + var request = CreateRequest(source.FileName, point.Line, point.Offset, timeout: 60000, wantMetadata: wantMetadata); + + var requestHandler = GetRequestHandler(SharedOmniSharpTestHost); + return await requestHandler.Handle(request); + } + + protected GotoTypeDefinitionRequest CreateRequest(string fileName, int line, int column, bool wantMetadata, int timeout = 60000) + => new GotoTypeDefinitionRequest + { + FileName = fileName, + Line = line, + Column = column, + WantMetadata = wantMetadata, + Timeout = timeout + }; + + protected IEnumerable<(int Line, int Column, string FileName, SourceGeneratedFileInfo SourceGeneratorInfo)> GetInfo(GotoTypeDefinitionResponse response) + { + if (response.Definitions is null) + yield break; + + foreach (var definition in response.Definitions) + { + yield return (definition.Location.Range.Start.Line, definition.Location.Range.Start.Column, definition.Location.FileName, definition.SourceGeneratedFileInfo); + } + } + + protected MetadataSource GetMetadataSource(GotoTypeDefinitionResponse response) + { + Assert.Single(response.Definitions); + return response.Definitions[0].MetadataSource; + } + } +} From 37c0c3e6983d27d812a67ed913d32a342761b9e2 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Mon, 20 Dec 2021 23:45:02 -0600 Subject: [PATCH 3/8] Add more tests for gotoTypeDefinition --- .../GotoTypeDefinitionFacts.cs | 543 ++++++++---------- 1 file changed, 240 insertions(+), 303 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs index 423a606c47..5a36d3e61a 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs @@ -79,7 +79,7 @@ class Test { [Theory] [InlineData("foo.cs")] [InlineData("foo.csx")] - public async Task ReturnsOnPropertyAccessorPropertySetting(string filename) + public async Task ReturnsOnPropertyAccessor(string filename) { var testFile = new TestFile(filename, @" class Test { @@ -100,7 +100,7 @@ public static void main() [Theory] [InlineData("foo.cs")] [InlineData("foo.csx")] - public async Task ReturnsOnPropertyAccessorField1(string filename) + public async Task ReturnsOnPrivateField(string filename) { var testFile = new TestFile(filename, @" class Test { @@ -143,6 +143,29 @@ public int Foo await TestGoToSourceAsync(testFile); } + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsOnPropertySetterParam(string filename) + { + var testFile = new TestFile(filename, @" +class Test { + + public Bar foo { get; set; } + public class |def:Bar| + { + public int lorem { get; set; } + } + public Bar Foo + { + get => foo; + set => foo = va$$lue; + } +}"); + + await TestGoToSourceAsync(testFile); + } + [Theory] [InlineData("foo.cs")] [InlineData("foo.csx")] @@ -165,212 +188,178 @@ public static void main() await TestGoToSourceAsync(testFile); } + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsOnImplicitLambdaParam(string filename) + { + var testFile = new TestFile(filename, @" +using System.Collections.Generic; +class Test { + public Bar Foo { get; set; } + public class |def:Bar| + { + public int lorem { get; set; } + } + public static void Main() + { + var list = new List(); + list.Add(new Bar()); + list.ForEach(inp$$ut => _ = input.lorem); + } +}"); -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsPartialMethodDefinitionWithBody(string filename) -// { -// var testFile = new TestFile(filename, @" -// public partial class MyClass -// { -// public MyClass() -// { -// Met$$hod(); -// } - -// partial void {|def:Method|}() -// { -// //do stuff -// } -// } - -// public partial class MyClass -// { -// partial void Method(); -// }"); - -// await TestGoToSourceAsync(testFile); -// } - -// [Fact] -// public async Task ReturnsDefinitionInDifferentFile() -// { -// var testFile1 = new TestFile("foo.cs", @" -//using System; -//class {|def:Foo|} { -//}"); -// var testFile2 = new TestFile("bar.cs", @" -//class Bar { -// private F$$oo foo; -//}"); - -// await TestGoToSourceAsync(testFile1, testFile2); -// } - -// [Fact] -// public async Task ReturnsEmptyResultWhenDefinitionIsNotFound() -// { -// var testFile1 = new TestFile("foo.cs", @" -//using System; -//class Foo { -//}"); -// var testFile2 = new TestFile("bar.cs", @" -//class Bar { -// private B$$az foo; -//}"); + await TestGoToSourceAsync(testFile); + } -// await TestGoToSourceAsync(testFile1, testFile2); -// } + [Theory] + [InlineData("foo.cs")] + [InlineData("foo.csx")] + public async Task ReturnsOnListFindResult(string filename) + { + var testFile = new TestFile(filename, @" +using System.Collections.Generic; -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsStaticMethod(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System; -//class Bar { -// public void Baz() { -// Guid.NewG$$uid(); -// } -//}"); +class Test { + public Bar Foo { get; set; } + public class |def:Bar| + { + public int lorem { get; set; } + } + public static void Main() + { + var list = new List(); + list.Add(new Bar()); + var out$$put = list.Find(input => _ = input.lorem == 12); + } +}"); -// await TestGoToMetadataAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.Guid"); -// } + await TestGoToSourceAsync(testFile); + } -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDecompiledDefinition_WhenSymbolIsStaticMethod(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System; -//class Bar { -// public void Baz() { -// Guid.NewG$$uid(); -// } -//}"); + [Fact] + public async Task ReturnsDefinitionInDifferentFile() + { + var testFile1 = new TestFile("foo.cs", @" +using System; +class {|def:Foo|} { +}"); + var testFile2 = new TestFile("bar.cs", @" +class Bar { + private Foo f$$oo; +}"); -// await TestDecompilationAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.Guid"); -// } + await TestGoToSourceAsync(testFile1, testFile2); + } -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsInstanceMethod(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System.Collections.Generic; -//class Bar { -// public void Baz() { -// var foo = new List(); -// foo.ToAr$$ray(); -// } -//}"); + [Fact] + public async Task ReturnsEmptyResultWhenDefinitionIsNotFound() + { + var testFile1 = new TestFile("foo.cs", @" + using System; + class Foo { + }"); + var testFile2 = new TestFile("bar.cs", @" + class Bar { + private Baz f$$oo; + }"); + + await TestGoToSourceAsync(testFile1, testFile2); + } -// await TestGoToMetadataAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.Collections.Generic.List`1"); -// } + [Theory] + [InlineData("bar.cs")] + [InlineData("bar.csx")] + public async Task ReturnsDefinitionInMetadata_WhenSymbolIsStaticMethod(string filename) + { + var testFile = new TestFile(filename, @" +using System; +class Bar { + public void Baz() { + var gu$$id = Guid.NewGuid(); + } +}"); -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDecompiledDefinition_WhenSymbolIsInstanceMethod(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System.Collections.Generic; -//class Bar { -// public void Baz() { -// var foo = new List(); -// foo.ToAr$$ray(); -// } -//}"); + await TestGoToMetadataAsync(testFile, + expectedAssemblyName: AssemblyHelpers.CorLibName, + expectedTypeName: "System.Guid"); + } -// await TestDecompilationAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.Collections.Generic.List`1"); -// } + [Theory] + [InlineData("bar.cs")] + [InlineData("bar.csx")] + public async Task ReturnsDecompiledDefinition_WhenSymbolIsStaticMethod(string filename) + { + var testFile = new TestFile(filename, @" +using System; +class Bar { + public void Baz() { + var g$$ = Guid.NewGuid(); + } +}"); -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsGenericType(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System.Collections.Generic; -//class Bar { -// public void Baz() { -// var foo = new Li$$st(); -// foo.ToArray(); -// } -//}"); + await TestDecompilationAsync(testFile, + expectedAssemblyName: AssemblyHelpers.CorLibName, + expectedTypeName: "System.Guid"); + } -// await TestGoToMetadataAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.Collections.Generic.List`1"); -// } -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDecompiledDefinition_WhenSymbolIsGenericType(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System.Collections.Generic; -//class Bar { -// public void Baz() { -// var foo = new Li$$st(); -// foo.ToArray(); -// } -//}"); + [Theory] + [InlineData("bar.cs")] + [InlineData("bar.csx")] + public async Task ReturnsDefinitionInMetadata_WhenSymbolIsParam(string filename) + { + var testFile = new TestFile(filename, @" + using System.Collections.Generic; + class Bar { + public void Baz(List par$$am1) { + var foo = new List(); + var f = param1; + } + }"); -// await TestDecompilationAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.Collections.Generic.List`1"); -// } + await TestGoToMetadataAsync(testFile, + expectedAssemblyName: AssemblyHelpers.CorLibName, + expectedTypeName: "System.Collections.Generic.List`1"); + } -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDefinitionInMetadata_WhenSymbolIsType(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System; -//class Bar { -// public void Baz() { -// var str = Stri$$ng.Empty; -// } -//}"); + [Theory] + [InlineData("bar.cs")] + [InlineData("bar.csx")] + public async Task ReturnsDecompiledDefinition_WhenSymbolIsIndexedList(string filename) + { + var testFile = new TestFile(filename, @" +using System.Collections.Generic; +class Bar { + public void Baz() { + var foo = new List(); + var lorem = fo$$o[0]; + } +}"); -// await TestGoToMetadataAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.String"); -// } + await TestDecompilationAsync(testFile, + expectedAssemblyName: AssemblyHelpers.CorLibName, + expectedTypeName: "System.Collections.Generic.List`1"); + } -// [Theory] -// [InlineData("bar.cs")] -// [InlineData("bar.csx")] -// public async Task ReturnsDecompiledDefinition_WhenSymbolIsType(string filename) -// { -// var testFile = new TestFile(filename, @" -//using System; -//class Bar { -// public void Baz() { -// var str = Stri$$ng.Empty; -// } -//}"); + [Theory] + [InlineData("bar.cs")] + [InlineData("bar.csx")] + public async Task ReturnsDefinitionInMetadata_WhenSymbolIsType(string filename) + { + var testFile = new TestFile(filename, @" + using System; + class Bar { + public void Baz() { + var str = String.Em$$pty; + } + }"); -// await TestDecompilationAsync(testFile, -// expectedAssemblyName: AssemblyHelpers.CorLibName, -// expectedTypeName: "System.String"); -// } + await TestGoToMetadataAsync(testFile, + expectedAssemblyName: AssemblyHelpers.CorLibName, + expectedTypeName: "System.String"); + } [Theory] [InlineData("bar.cs")] @@ -390,45 +379,14 @@ public void Baz() { var point = testFile.Content.GetPointFromPosition(); // 1. start by asking for definition of "int" - var gotoDefinitionRequest = CreateRequest(testFile.FileName, point.Line, point.Offset, wantMetadata: true, timeout: 60000); + var gotoDefinitionRequest = CreateRequest(testFile.FileName, point.Line, point.Offset, wantMetadata: true, timeout: 600000000); var gotoDefinitionRequestHandler = GetRequestHandler(host); var gotoDefinitionResponse = await gotoDefinitionRequestHandler.Handle(gotoDefinitionRequest); var gotoDefinitionResponseMetadataSource = GetMetadataSource(gotoDefinitionResponse); - - // 2. now, based on the response information - // go to the metadata endpoint, and ask for "int" specific metadata - var metadataRequest = new MetadataRequest - { - AssemblyName = gotoDefinitionResponseMetadataSource.AssemblyName, - TypeName = gotoDefinitionResponseMetadataSource.TypeName, - ProjectName = gotoDefinitionResponseMetadataSource.ProjectName, - Language = gotoDefinitionResponseMetadataSource.Language - }; - var metadataRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.Metadata); - var metadataResponse = await metadataRequestHandler.Handle(metadataRequest); - - // 3. the metadata response contains SourceName (metadata "file") and SourceText (syntax tree) - // use the source to locate "IComparable" which is an interface implemented by Int32 struct - var metadataTree = CSharpSyntaxTree.ParseText(metadataResponse.Source); - - var iComparable = metadataTree.GetCompilationUnitRoot(). - DescendantNodesAndSelf(). - OfType().First(). - BaseList.Types.FirstOrDefault(x => x.Type.ToString() == "IComparable"); - var relevantLineSpan = iComparable.GetLocation().GetLineSpan(); - - // 4. now ask for the definition of "IComparable" - // pass in the SourceName (metadata "file") as FileName - since it's not a regular file in our workspace - var metadataNavigationRequest = CreateRequest(metadataResponse.SourceName, relevantLineSpan.StartLinePosition.Line, relevantLineSpan.StartLinePosition.Character, wantMetadata: true); - var metadataNavigationResponse = await gotoDefinitionRequestHandler.Handle(metadataNavigationRequest); - var metadataNavigationResponseMetadataSource = GetMetadataSource(metadataNavigationResponse); - var info = GetInfo(metadataNavigationResponse); - - // 5. validate the response to be matching the expected IComparable meta info - Assert.NotNull(metadataNavigationResponseMetadataSource); - Assert.Equal(AssemblyHelpers.CorLibName, metadataNavigationResponseMetadataSource.AssemblyName); - Assert.Equal("System.IComparable", metadataNavigationResponseMetadataSource.TypeName); - + Assert.NotNull(gotoDefinitionResponseMetadataSource); + Assert.Equal(AssemblyHelpers.CorLibName, gotoDefinitionResponseMetadataSource.AssemblyName); + Assert.Equal("System.Int32", gotoDefinitionResponseMetadataSource.TypeName); + var info = GetInfo(gotoDefinitionResponse); Assert.NotEqual(0, info.Single().Line); Assert.NotEqual(0, info.Single().Column); } @@ -455,7 +413,7 @@ public void Baz() { var point = testFile.Content.GetPointFromPosition(); // 1. start by asking for definition of "int" - var gotoDefinitionRequest = CreateRequest(testFile.FileName, point.Line, point.Offset, wantMetadata: true, timeout: 60000); + var gotoDefinitionRequest = CreateRequest(testFile.FileName, point.Line, point.Offset, wantMetadata: true, timeout: 60000000); var gotoDefinitionRequestHandler = GetRequestHandler(host); var gotoDefinitionResponse = await gotoDefinitionRequestHandler.Handle(gotoDefinitionRequest); @@ -468,7 +426,7 @@ public void Baz() { TypeName = metadataSource.TypeName, ProjectName = metadataSource.ProjectName, Language = metadataSource.Language, - Timeout = 60000 + Timeout = 6000000 }; var metadataRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.Metadata); var metadataResponse = await metadataRequestHandler.Handle(metadataRequest); @@ -491,27 +449,6 @@ public void Baz() { Where(m => m.Body != null); Assert.NotEmpty(methods); - - var iComparable = compilationUnit. - DescendantNodesAndSelf(). - OfType().First(). - BaseList.Types.FirstOrDefault(x => x.Type.ToString() == "IComparable"); - var relevantLineSpan = iComparable.GetLocation().GetLineSpan(); - - // 4. now ask for the definition of "IComparable" - // pass in the SourceName (metadata "file") as FileName - since it's not a regular file in our workspace - var metadataNavigationRequest = CreateRequest(metadataResponse.SourceName, relevantLineSpan.StartLinePosition.Line, relevantLineSpan.StartLinePosition.Character, wantMetadata: true); - var metadataNavigationResponse = await gotoDefinitionRequestHandler.Handle(metadataNavigationRequest); - var metadataSourceResponse = GetMetadataSource(metadataNavigationResponse); - var metadataNavigationInfo = GetInfo(metadataNavigationResponse); - - // 5. validate the response to be matching the expected IComparable meta info - Assert.NotNull(metadataSource); - Assert.Equal(AssemblyHelpers.CorLibName, metadataSourceResponse.AssemblyName); - Assert.Equal("System.IComparable", metadataSourceResponse.TypeName); - - Assert.NotEqual(0, metadataNavigationInfo.Single().Line); - Assert.NotEqual(0, metadataNavigationInfo.Single().Column); } [Fact] @@ -522,61 +459,61 @@ public async Task ReturnsNoResultsButDoesNotThrowForNamespaces() Assert.Empty(GetInfo(response)); } -// [Fact] -// public async Task ReturnsResultsForSourceGenerators() -// { -// const string Source = @" -//public class {|generatedClassName:Generated|} -//{ -// public int {|propertyName:Property|} { get; set; } -//} -//"; -// const string FileName = "real.cs"; -// TestFile generatedTestFile = new("GeneratedFile.cs", Source); -// var testFile = new TestFile(FileName, @" -//class C -//{ -// public void M(Generated g) -// { -// _ = g.P$$roperty; -// } -//} -//"); - -// TestHelpers.AddProjectToWorkspace(SharedOmniSharpTestHost.Workspace, -// "project.csproj", -// new[] { "netcoreapp3.1" }, -// new[] { testFile }, -// analyzerRefs: ImmutableArray.Create(new TestGeneratorReference( -// context => context.AddSource("GeneratedFile", generatedTestFile.Content.Code)))); - -// var point = testFile.Content.GetPointFromPosition(); - -// var gotoDefRequest = CreateRequest(FileName, point.Line, point.Offset, wantMetadata: true); -// var gotoDefHandler = GetRequestHandler(SharedOmniSharpTestHost); -// var response = await gotoDefHandler.Handle(gotoDefRequest); -// var info = GetInfo(response).Single(); - -// Assert.NotNull(info.SourceGeneratorInfo); - -// var expectedSpan = generatedTestFile.Content.GetSpans("propertyName").Single(); -// var expectedRange = generatedTestFile.Content.GetRangeFromSpan(expectedSpan); - -// Assert.Equal(expectedRange.Start.Line, info.Line); -// Assert.Equal(expectedRange.Start.Offset, info.Column); - -// var sourceGeneratedFileHandler = SharedOmniSharpTestHost.GetRequestHandler(OmniSharpEndpoints.SourceGeneratedFile); -// var sourceGeneratedRequest = new SourceGeneratedFileRequest -// { -// DocumentGuid = info.SourceGeneratorInfo.DocumentGuid, -// ProjectGuid = info.SourceGeneratorInfo.ProjectGuid -// }; - -// var sourceGeneratedFileResponse = await sourceGeneratedFileHandler.Handle(sourceGeneratedRequest); -// Assert.NotNull(sourceGeneratedFileResponse); -// Assert.Equal(generatedTestFile.Content.Code, sourceGeneratedFileResponse.Source); -// Assert.Equal(@"OmniSharp.Roslyn.CSharp.Tests\OmniSharp.Roslyn.CSharp.Tests.TestSourceGenerator\GeneratedFile.cs", sourceGeneratedFileResponse.SourceName.Replace("/", @"\")); -// } + [Fact] + public async Task ReturnsResultsForSourceGenerators() + { + const string Source = @" +public class {|generatedClassName:Generated|} +{ + public int {|propertyName:Property|} { get; set; } +} +"; + const string FileName = "real.cs"; + TestFile generatedTestFile = new("GeneratedFile.cs", Source); + var testFile = new TestFile(FileName, @" +class C +{ + public void M(Generated gen) + { + _ = ge$$n.Property; + } +} +"); + + TestHelpers.AddProjectToWorkspace(SharedOmniSharpTestHost.Workspace, + "project.csproj", + new[] { "netcoreapp3.1" }, + new[] { testFile }, + analyzerRefs: ImmutableArray.Create(new TestGeneratorReference( + context => context.AddSource("GeneratedFile", generatedTestFile.Content.Code)))); + + var point = testFile.Content.GetPointFromPosition(); + + var gotoDefRequest = CreateRequest(FileName, point.Line, point.Offset, wantMetadata: true); + var gotoDefHandler = GetRequestHandler(SharedOmniSharpTestHost); + var response = await gotoDefHandler.Handle(gotoDefRequest); + var info = GetInfo(response).Single(); + + Assert.NotNull(info.SourceGeneratorInfo); + + var expectedSpan = generatedTestFile.Content.GetSpans("generatedClassName").Single(); + var expectedRange = generatedTestFile.Content.GetRangeFromSpan(expectedSpan); + + Assert.Equal(expectedRange.Start.Line, info.Line); + Assert.Equal(expectedRange.Start.Offset, info.Column); + + var sourceGeneratedFileHandler = SharedOmniSharpTestHost.GetRequestHandler(OmniSharpEndpoints.SourceGeneratedFile); + var sourceGeneratedRequest = new SourceGeneratedFileRequest + { + DocumentGuid = info.SourceGeneratorInfo.DocumentGuid, + ProjectGuid = info.SourceGeneratorInfo.ProjectGuid + }; + + var sourceGeneratedFileResponse = await sourceGeneratedFileHandler.Handle(sourceGeneratedRequest); + Assert.NotNull(sourceGeneratedFileResponse); + Assert.Equal(generatedTestFile.Content.Code, sourceGeneratedFileResponse.Source); + Assert.Equal(@"OmniSharp.Roslyn.CSharp.Tests\OmniSharp.Roslyn.CSharp.Tests.TestSourceGenerator\GeneratedFile.cs", sourceGeneratedFileResponse.SourceName.Replace("/", @"\")); + } protected async Task TestGoToSourceAsync(params TestFile[] testFiles) { From 3ba51bf6af7a476385700f3d6df6b14066711ed7 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Tue, 21 Dec 2021 10:15:40 -0600 Subject: [PATCH 4/8] Remove Commented gototypedefinition test --- .../GotoTypeDefinitionFacts.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs index 5a36d3e61a..85d214e406 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs @@ -63,19 +63,6 @@ class Test { await TestGoToSourceAsync(testFile); } -// [Theory] -// [InlineData("foo.cs")] -// [InlineData("foo.csx")] -// public async Task DoesNotReturnOnPropertyAccessorPropertyDef(string filename) -// { -// var testFile = new TestFile(filename, @" -//class Test { -// public int |def:Foo| Fo$$o{ get; set; } -//}"); - -// await TestGoToSourceAsync(testFile); -// } - [Theory] [InlineData("foo.cs")] [InlineData("foo.csx")] From 8936863ddeff2a91b9dce9761934c2fb56c0dba7 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Thu, 3 Feb 2022 14:43:27 -0800 Subject: [PATCH 5/8] Use .IsKind() instead of .Kind() == x --- tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs index 85d214e406..df0ce29d6f 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs @@ -424,7 +424,7 @@ public void Baz() { var compilationUnit = decompiledTree.GetCompilationUnitRoot(); // second comment should indicate we have decompiled - var comments = compilationUnit.DescendantTrivia().Where(t => t.Kind() == SyntaxKind.SingleLineCommentTrivia).ToArray(); + var comments = compilationUnit.DescendantTrivia().Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia)).ToArray(); Assert.NotNull(comments); Assert.Equal("// Decompiled with ICSharpCode.Decompiler 7.1.0.6543", comments[1].ToString()); From c79f647de47f832efd7127c486b9dbb2f98a8ba0 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Thu, 3 Feb 2022 14:54:14 -0800 Subject: [PATCH 6/8] Add necessary include for IsKind() --- .../OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs index df0ce29d6f..352de6cb58 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs @@ -1,7 +1,7 @@ -using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using OmniSharp.Mef; using OmniSharp.Models.Metadata; using OmniSharp.Models.v1.SourceGeneratedFile; using OmniSharp.Models.V2.GotoTypeDefinition; From 070884e7df860dad5f4cda477e8b56956a064809 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Wed, 2 Mar 2022 11:52:24 -0800 Subject: [PATCH 7/8] PR feedback --- .../GotoTypeDefinitionRequest.cs | 4 ++-- .../GotoTypeDefinitionResponse.cs | 4 ++-- .../OmniSharpEndpoints.cs | 3 +-- src/OmniSharp.Cake/OmniSharp.Cake.csproj | 1 + .../Navigation/GotoTypeDefinitionHandler.cs | 21 +++++++++++-------- .../GotoTypeDefinitionHandlerHelper.cs | 12 +++++++---- .../Navigation/GotoTypeDefinitionService.cs | 5 +++-- .../GotoTypeDefinitionFacts.cs | 4 ++-- 8 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs index 3c957d5c82..bea8e6b9a8 100644 --- a/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs +++ b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionRequest.cs @@ -1,8 +1,8 @@ using OmniSharp.Mef; -namespace OmniSharp.Models.V2.GotoTypeDefinition +namespace OmniSharp.Models.GotoTypeDefinition { - [OmniSharpEndpoint(OmniSharpEndpoints.V2.GotoTypeDefinition, typeof(GotoTypeDefinitionRequest), typeof(GotoTypeDefinitionResponse))] + [OmniSharpEndpoint(OmniSharpEndpoints.GotoTypeDefinition, typeof(GotoTypeDefinitionRequest), typeof(GotoTypeDefinitionResponse))] public class GotoTypeDefinitionRequest : Request { public int Timeout { get; init; } = 10000; diff --git a/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs index c00fcdb13d..34dc8b264a 100644 --- a/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs +++ b/src/OmniSharp.Abstractions/Models/v2/GotoTypeDefinition/GotoTypeDefinitionResponse.cs @@ -4,7 +4,7 @@ using OmniSharp.Models.v1.SourceGeneratedFile; using System.Collections.Generic; -namespace OmniSharp.Models.V2.GotoTypeDefinition +namespace OmniSharp.Models.GotoTypeDefinition { public record GotoTypeDefinitionResponse { @@ -13,7 +13,7 @@ public record GotoTypeDefinitionResponse public record TypeDefinition { - public Location Location { get; init; } = null!; + public V2.Location Location { get; init; } = null!; public MetadataSource? MetadataSource { get; init; } public SourceGeneratedFileInfo? SourceGeneratedFileInfo { get; init; } } diff --git a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs index 3c25efc4a5..cf24b1c981 100644 --- a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs +++ b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs @@ -3,6 +3,7 @@ namespace OmniSharp public static class OmniSharpEndpoints { public const string GotoDefinition = "/gotodefinition"; + public const string GotoTypeDefinition = "/gototypedefinition"; public const string FindSymbols = "/findsymbols"; public const string UpdateBuffer = "/updatebuffer"; public const string ChangeBuffer = "/changebuffer"; @@ -77,8 +78,6 @@ public static class V2 public const string Highlight = "/v2/highlight"; public const string GotoDefinition = "/v2/gotodefinition"; - - public const string GotoTypeDefinition = "/v2/gototypedefinition"; } } } diff --git a/src/OmniSharp.Cake/OmniSharp.Cake.csproj b/src/OmniSharp.Cake/OmniSharp.Cake.csproj index 15a6211691..779edb532c 100644 --- a/src/OmniSharp.Cake/OmniSharp.Cake.csproj +++ b/src/OmniSharp.Cake/OmniSharp.Cake.csproj @@ -7,6 +7,7 @@ + diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs index 7b8501ec8b..a5ff2c0da9 100644 --- a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs +++ b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandler.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using OmniSharp.Extensions; using OmniSharp.Mef; -using OmniSharp.Models.V2.GotoTypeDefinition; +using OmniSharp.Models.GotoTypeDefinition; using OmniSharp.Models.Metadata; using OmniSharp.Models.v1.SourceGeneratedFile; using OmniSharp.Models.V2; @@ -13,21 +13,24 @@ using OmniSharp.Utilities; using Location = OmniSharp.Models.V2.Location; using Range = OmniSharp.Models.V2.Range; +using OmniSharp.Roslyn.CSharp.Services; +using OmniSharp.Options; namespace OmniSharp.Cake.Services.RequestHandlers.Navigation { - [OmniSharpHandler(OmniSharpEndpoints.V2.GotoTypeDefinition, Constants.LanguageNames.Cake), Shared] - public class GotoTypeDefinitionV2Handler : CakeRequestHandler + [OmniSharpHandler(OmniSharpEndpoints.GotoTypeDefinition, Constants.LanguageNames.Cake), Shared] + public class GotoTypeDefinitionHandler : CakeRequestHandler { - private readonly MetadataExternalSourceService _metadataExternalSourceService; + private readonly IExternalSourceService _externalSourceService; [ImportingConstructor] - public GotoTypeDefinitionV2Handler( + public GotoTypeDefinitionHandler( OmniSharpWorkspace workspace, - MetadataExternalSourceService metadataExternalSourceService) + ExternalSourceServiceFactory externalSourceServiceFactory, + OmniSharpOptions omniSharpOptions) : base(workspace) { - _metadataExternalSourceService = metadataExternalSourceService ?? throw new ArgumentNullException(nameof(metadataExternalSourceService)); + _externalSourceService = externalSourceServiceFactory?.Create(omniSharpOptions) ?? throw new ArgumentNullException(nameof(externalSourceServiceFactory)); } protected override async Task TranslateResponse(GotoTypeDefinitionResponse response, GotoTypeDefinitionRequest request) @@ -63,12 +66,12 @@ protected override async Task TranslateResponse(Goto continue; } - var aliasLocations = await GotoTypeDefinitionHandlerHelper.GetAliasFromMetadataAsync( + var aliasLocations = await GotoTypeDefinitionHandlerHelper.GetAliasFromExternalSourceAsync( Workspace, request.FileName, definition.Location.Range.End.Line, request.Timeout, - _metadataExternalSourceService + _externalSourceService ); definitions.AddRange( diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs index 63cc3b1896..48924b7f94 100644 --- a/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs +++ b/src/OmniSharp.Cake/Services/RequestHandlers/Navigation/GotoTypeDefinitionHandlerHelper.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Text; using OmniSharp.Roslyn; +using OmniSharp.Roslyn.CSharp.Services; namespace OmniSharp.Cake.Services.RequestHandlers.Navigation { @@ -15,12 +16,12 @@ public static class GotoTypeDefinitionHandlerHelper private const int MethodLineOffset = 3; private const int PropertyLineOffset = 7; - internal static async Task> GetAliasFromMetadataAsync( + internal static async Task> GetAliasFromExternalSourceAsync( OmniSharpWorkspace workspace, string fileName, int line, int timeout, - MetadataExternalSourceService metadataExternalSourceService) + IExternalSourceService externalSourceService) { var document = workspace.GetDocument(fileName); var lineIndex = line + MethodLineOffset; @@ -78,6 +79,9 @@ internal static async Task> GetAliasFromMetadataAsync( _ => null }; + if (typeSymbol == null) + return Enumerable.Empty(); + var result = new List(); foreach (var location in typeSymbol.Locations) { @@ -87,14 +91,14 @@ internal static async Task> GetAliasFromMetadataAsync( } var cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout)); - var (metadataDocument, _) = await metadataExternalSourceService.GetAndAddExternalSymbolDocument(document.Project, typeSymbol, cancellationSource.Token); + var (metadataDocument, _) = await externalSourceService.GetAndAddExternalSymbolDocument(document.Project, typeSymbol, cancellationSource.Token); if (metadataDocument == null) { continue; } cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout)); - var metadataLocation = await metadataExternalSourceService.GetExternalSymbolLocation(typeSymbol, metadataDocument, cancellationSource.Token); + var metadataLocation = await externalSourceService.GetExternalSymbolLocation(typeSymbol, metadataDocument, cancellationSource.Token); var lineSpan = metadataLocation.GetMappedLineSpan(); result.Add(new Alias diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs index ffbc700a0c..64a4de2db2 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/GotoTypeDefinitionService.cs @@ -3,15 +3,16 @@ using Microsoft.CodeAnalysis; using OmniSharp.Extensions; using OmniSharp.Mef; -using OmniSharp.Models.V2.GotoTypeDefinition; +using OmniSharp.Models.GotoTypeDefinition; using OmniSharp.Options; using System.Composition; using System.Linq; using System.Threading.Tasks; +using OmniSharp.Models; namespace OmniSharp.Roslyn.CSharp.Services.Navigation { - [OmniSharpHandler(OmniSharpEndpoints.V2.GotoTypeDefinition, LanguageNames.CSharp)] + [OmniSharpHandler(OmniSharpEndpoints.GotoTypeDefinition, LanguageNames.CSharp)] public class GotoTypeDefinitionService : IRequestHandler { private readonly OmniSharpOptions _omnisharpOptions; diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs index 352de6cb58..a1ebf30429 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/GotoTypeDefinitionFacts.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using OmniSharp.Models.Metadata; using OmniSharp.Models.v1.SourceGeneratedFile; -using OmniSharp.Models.V2.GotoTypeDefinition; +using OmniSharp.Models.GotoTypeDefinition; using OmniSharp.Roslyn.CSharp.Services.Navigation; using System.Collections.Generic; using System.Collections.Immutable; @@ -22,7 +22,7 @@ public GotoTypeDefinitionFacts(ITestOutputHelper output, SharedOmniSharpHostFixt : base(output, sharedOmniSharpHostFixture) { } - protected override string EndpointName => OmniSharpEndpoints.V2.GotoTypeDefinition; + protected override string EndpointName => OmniSharpEndpoints.GotoTypeDefinition; [Theory] [InlineData("foo.cs")] From 14129b03d5fdba500b6ccd489e8fcb6dc4b5fd0b Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Thu, 3 Mar 2022 16:17:03 -0800 Subject: [PATCH 8/8] Empty commit to trigger CI retry