-
Notifications
You must be signed in to change notification settings - Fork 419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support go to definition from metadata source #883
Support go to definition from metadata source #883
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple minor changes, otherwise 👍 .
Expanding to include perhaps find usages, and type info would be cool, but certainly not needed on the first pass.
var document = _workspace.GetDocument(request.FileName); | ||
var document = request.IsMetadataFile() ? | ||
_metadataHelper.FindDocumentInMetadataCache(request.FileName) : | ||
_workspace.GetDocument(request.FileName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could technically work for other endpoints as well right? Thinking of /findusages
, /typelookup
and maybe even /highlight
.
Would be great if we could simplify this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could probably just make it _metadataHelper.FindDocumentInMetadataCache(request.FileName) ?? _workspace.GetDocument(request.FileName);
to avoid the first if there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes this approach should work for other endpoints too
} | ||
|
||
Document metadataDocument = null; | ||
if (!_metadataDocumentCache.TryGetValue(fileName, out metadataDocument)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use out var metadataDocument
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely. 😄
@@ -127,6 +127,8 @@ public IEnumerable<Document> GetDocuments(string filePath) | |||
|
|||
public Document GetDocument(string filePath) | |||
{ | |||
if (filePath == null) return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably use string.IsNullOrEmpty()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or string.IsNullOrWhitespace(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just a few "clean up" suggestions. Also, the CI failure looks like a real test failure.
} | ||
|
||
public Task<Document> GetDocumentFromMetadata(Project project, ISymbol symbol, CancellationToken cancellationToken = new CancellationToken()) | ||
public async Task<MetadataDocumentResult> GetAndAddDocumentFromMetadata(Project project, ISymbol symbol, CancellationToken cancellationToken = new CancellationToken()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What a beautiful place to use a C# 7 tuple!
} | ||
|
||
Document metadataDocument = null; | ||
if (!_metadataDocumentCache.TryGetValue(fileName, out metadataDocument)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely. 😄
var topLevelSymbol = GetTopLevelContainingNamedType(symbol); | ||
var fileName = GetFilePathForSymbol(project, symbol); | ||
|
||
Project metadataProject = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tiny, tiny, minuscule nit: I don't think you need = null
here because `metadataProject is definitely assigned before it is used below.
@@ -127,6 +127,8 @@ public IEnumerable<Document> GetDocuments(string filePath) | |||
|
|||
public Document GetDocument(string filePath) | |||
{ | |||
if (filePath == null) return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or string.IsNullOrWhitespace(...)
@@ -6,6 +6,8 @@ | |||
using TestUtility; | |||
using Xunit; | |||
using Xunit.Abstractions; | |||
using OmniSharp.Models.GotoDefinition; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort usings?
var metadataTree = CSharpSyntaxTree.ParseText(metadataResponse.Source); | ||
var iComparable = metadataTree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType<BaseTypeDeclarationSyntax>(). | ||
First().BaseList.Types.FirstOrDefault(x => x.Type.ToString() == "IComparable"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: extra blank line
[Theory] | ||
[InlineData("bar.cs")] | ||
[InlineData("bar.csx")] | ||
public async Task ReturnsDefinitionInMetadata_FromMetadata_WhenSymbolIsType(string filename) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, unifying the pattern for calling each handler would be helpful for a minor readability improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comments though! Very helpful!
FileName = testFile.FileName, | ||
Line = point.Line, | ||
Column = point.Offset, | ||
Timeout = 60000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to call this endpoint without a timeout?
// 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<BaseTypeDeclarationSyntax>(). | ||
First().BaseList.Types.FirstOrDefault(x => x.Type.ToString() == "IComparable"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love to see this broken down onto separate lines for readability.
.WithCompilationOptions(project.CompilationOptions) | ||
.WithMetadataReferences(project.MetadataReferences) | ||
: project; | ||
.WithMetadataReferences(project.MetadataReferences); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be indented?
thanks for the comments 👍 and sorry for the delay (long weekend) 🌴 |
d68e040
to
2a8be67
Compare
I addressed the comments, squashed the commits to remove the noise I managed to generate and rebased this on top of the latest |
dd627bd
to
c317826
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Are you good here @filipw? If so, I think you can merge this once CI completes again. |
I think so! |
This is an attempt to fix #876
I am not sure if this is the best way to do it, but I wanted to fit into the existing endpoint structure to avoid breaking changes.
The current flow of the client trying to go to metadata is the following
/gotodefinition
. The server code recognizes the symbol location is in metadata, and uses Roslyn'sMetadataAsSourceService
to generate a temporary document a provide basic metadata information to the caller - such as the position of the symbol in the metadata source. No metadata source is returned though./metadata
and pass in the meta information (i.e.TypeName
,AssemblyName
). The server will then once again call intoMetadataAsSourceService
, generate a temporary document again, and return the source text of that document, along with its temporary name./gotodefinition
call.It's impossible at the moment to continue the navigation from metadata to other metadata sources, because that would require going through
/gotodefinition
endpoint again, and it doesn't support starting points that are not a document inside the workspace.This PR changes the above flow in the following, non-breaking way
/gotodefinition
. The code recognizes the symbol location is in metadata, and uses Roslyn'sMetadataAsSourceService
to generate a temporary document a provide basic metadata information to the caller - such as the position of the symbol in the metadata source. No metadata source is returned though. The difference is that the temporary document document containing metadata source is stored in memory for future reuse (not added to current workspace, just stored on a side in a dictionary)./metadata
and pass in the meta information (i.e.TypeName
,AssemblyName
). The server now doesn't call intoMetadataAsSourceService
again (so we have a perf boost), but rather it will use the cached document and return the source text of that document, along with its temporary name./gotodefinition
call./gotodefinition
and ask for anything based on the location in the metadata source document - that is because the/gotodefinition
endpoint has also been modified to not only work with documents that are part of Workspace but also with metadata sources using the aformentioned metadata cache to look up the sources. From there the process goes back to point 1, and we can have a neverending navigation cycle between metadata documents.Let me know what you think, and whether this approach makes sense - we can absolutely change it to something better.
I also specifically avoided storing the metadata source documents inside the workspace as that started impacting a lot of other endpoints in an unwelcome way (i.e. go to symbol).