-
Notifications
You must be signed in to change notification settings - Fork 154
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
fixed peek definition tokenizing #281
Conversation
@abist, thanks for your PR! By analyzing the history of the files in this pull request, we identified @kburtram, @sharonravindran and @llali to be potential reviewers. |
@abist, |
@abist, could you clarify what this is actually fixing? Neither the issue nor this PR describe what the impact will be. For example: "fixes issue where Peek Definition fails to find the correct token position, resulting in failure to return results...." would be my guess? But this helps us prioritize reviewing and signoff since we know what we're looking for. Please also make sure this description is included on checkin so we can look back at commit history and see what was done. Thanks! |
@kevcunnane Agreed, I updated the description. |
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.
Please add in tests to cover this scenario and capture what you're fixing - e.g. what failed before but would now pass? Also please fix the issue where PeekDefinition.GetScript now ends up adding +2 instead of +1 to the line (since you do it here, then PeekDefinition does too
/// <summary> | ||
/// Returns the token that is used for Peek Definition objects | ||
/// </summary> | ||
internal static Token GetPeakDefinitionToken(ScriptParseInfo scriptParseInfo, int startLine, int startColumn) |
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.
Typo: Peak -> Peek
@@ -814,12 +814,17 @@ internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPositio | |||
bindingTimeout: LanguageService.PeekDefinitionTimeout, | |||
bindOperation: (bindingContext, cancelToken) => | |||
{ | |||
string schemaName = this.GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile); | |||
// Handle tokenizing for Peek Definition | |||
Position tokenPosition = new Position(); |
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.
2 suggestions:
- Move the Position creation up to before `Token selectedToken = ScriptDocumentInfo.GetPeekDefinitionToken) and use its line and character values. This minimizes the times we need to convert the line/character counts by adding 1 to them. Every time we do this, we risk an error later since we're tracking in multiple places
- Use the constructor pattern as follow since it's a clean way to create the object :
Position tokenPosition = new Position
{
Line = textDocumentPosition.Position.Line + 1,
Character = textDocumentPosition.Position.Character + 1
};
// Script object using SMO | ||
PeekDefinition peekDefinition = new PeekDefinition(bindingContext.ServerConnection, connInfo); | ||
return peekDefinition.GetScript( | ||
scriptParseInfo.ParseResult, | ||
textDocumentPosition.Position, | ||
tokenPosition, |
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.
Isn't this an error? PeekDefinition.GetScript
already adjusts line & character by 1. You need to alter that method to no longer do so.
@@ -808,18 +808,23 @@ internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPositio | |||
{ | |||
try | |||
{ | |||
// Handle tokenizing for Peek Definition | |||
Position tokenPosition = new Position(); |
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 have an internal class called BufferPosition, which tends to use the VSCode Position values but 1-indexed. Perhaps for clarity, we should change the code to use that? Otherwise it's kind of confusing since we're adjusting this but nobody would know why.
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.
The arguments strictly require the Position class, so I'm not sure if we can use the BufferPosition class here.
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.
I'm suggesting changing the PeekDefinition class to use BufferPosition, since the pattern we have is that this uses Position's values + 1. That way it's clear what the expected input is. Otherwise, if someone else uses this class they might happily send in an unmodified Position object.
@@ -795,7 +795,7 @@ internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPositio | |||
} | |||
|
|||
// Get token from selected text | |||
Token selectedToken = ScriptDocumentInfo.GetToken(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character); | |||
Token selectedToken = ScriptDocumentInfo.GetPeakDefinitionToken(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character + 1); |
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.
@@ -796,76 +842,127 @@ internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPositio | |||
} |
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.
I'd put an isconnected check at the top and error out cleanly here.
@@ -796,76 +842,127 @@ internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPositio | |||
} | |||
|
|||
// Get token from selected text | |||
Token selectedToken = ScriptDocumentInfo.GetToken(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character); | |||
var selectedToken = ScriptDocumentInfo.GetPeekDefinitionTokens(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character + 1); |
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.
DOn't use var here - it's a complicated data type so be clear. Otherwise those reading the code won't know what's happening.
{ | ||
try | ||
var token = selectedToken.Item1.Pop(); |
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.
Please define what Item1 is. Assign to a clear, named variable.
string tokenText = TextUtilities.RemoveSquareBracketSyntax(token.Text); | ||
textDocumentPosition.Position.Line = token.StartLocation.LineNumber; | ||
textDocumentPosition.Position.Character = token.StartLocation.ColumnNumber; | ||
if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) |
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.
Since you'll move the "not connected" error up, please add a separate error for when you fail to enter the monitor. Probably state "Timeout out waiting to query metadata from the server"
// then check the parents | ||
foreach (var i in selectedToken.Item2.ToList()) | ||
{ | ||
var token = selectedToken.Item2.Dequeue(); |
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 is Item2? What does it represent.
{ | ||
Stack<Token> childrenTokens = new Stack<Token>(); | ||
Queue<Token> parentTokens = new Queue<Token>(); | ||
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != 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.
Minor: put each if check on its own line
int currentIndex = 0; | ||
foreach (var token in scriptParseInfo.ParseResult.Script.Tokens) | ||
{ | ||
|
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.
Minor: remove unnecessary whitespace
/// <returns></returns> | ||
public override bool Equals(object obj) | ||
{ | ||
if (obj == null || GetType() != obj.GetType()) |
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 if someone has a subclass of position? I think IsInstanceOf or similar (we have cases of this in the project) work better. You could also just do obj as Position and check if null
|
||
// place the cursor on every token | ||
//cursor on objects | ||
TextDocumentPosition objectDocument = new TextDocumentPosition |
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.
Use a helper method to make these all 1-line definitions
[Fact] | ||
public async void GetDefinitionFromChildrenAndParents() | ||
{ | ||
string queryString = "select * from master.sys.objects"; |
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.
At a minimum please add procedure-specific tests, and tests with and without escaping of brackets on each one. Ideally make it easy to define multiple inputs so future bug regressions can be tested (e.g. add in a framework that takes JSON file input defining the query & positions to test from, and what's expected to be returned at each). This 2nd ask is a stretch goal though - can ship without it.
private DefinitionResult GetDefinitionFromTokenList(TextDocumentPosition textDocumentPosition, List<Token> tokenList, | ||
ScriptParseInfo scriptParseInfo, ScriptFile scriptFile, ConnectionInfo connInfo) | ||
{ | ||
|
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.
Minor: minimize whitespace lines
Fixes peek definition tokenizing.
The issue here was that the parser wasn't correctly getting the correct token position, thus failing Peek Definition if the cursor was right in the starting or the end of the object we want to look up. For example, Peek Definition would fail if, let's say, the cursor were anywhere outside this range - sys.d [atabase] s
According to the new way PeekDefinition works, instead of returning an error if an object isn't found, we first look at the children and parent tokens and return the definition of those. If nothing is defined, then we return the error.