Skip to content
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

Remove more LocateOwner usage #9132

Merged

Conversation

ryzngard
Copy link
Contributor

  • Update code actions and completion
  • Update diagnostics to get off LocateOwner

@ryzngard ryzngard requested a review from a team as a code owner August 16, 2023 22:51
// <button></button>$$
// should get the button node instead of the razor document (which is the parent
// of the EOF token)
if (token.Kind == SyntaxKind.EndOfFile && node.Span.Contains(index - 1))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the right answer. Would love thoughts on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess this makes sense. if there were whitespace at the end, i think the compiler method would do the same thing. Maybe @333fred has opinions though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there were whitespace at the end, i think the compiler method would do the same thing.

Yes. This is the type of heuristic that I would expect the IDE to need to do, but with FindToken you have a predictable set of rules to base that heuristic on.

return token.Parent;
}

public static SyntaxNode? FindNode(this SyntaxNode @this, Language.Syntax.TextSpan span, bool includeWhitespace = false, bool getInnermostNodeForTie = false)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shamelessly copied from Roslyn.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks scary, and seems like it only has one caller, which is surprising and doesn't fill me with joy, but the fact that its copied from Roslyn means I'm going to assume its fine :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably add a lot of tests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about that, and also wondering if this should move down to the compiler, next to FindToken. I think @333fred has mentioned thinking about adding something that dealt with ties, but couldn't see any usage that required spans, so left it out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can certainly move this down to the compiler, but yes the initial FindToken method was all we thought we needed in the design meeting a few months ago. If we find that need FindNode as well, I'm perfectly fine with adding it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, FindToken was considered table stakes and that we'd build whatever helpers we needed based on that and then push APIs down to the compiler that were particularly useful. In Roslyn, SyntaxNode didn't have a FindNode API for many years (or Ancestors, Descendants, FirstAncestorOrSelf`, etc.) and were only added when it was recognized that they were essential. I'm happy to not move this to the compiler unless it's super useful for other purposes.

Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is surprising to me that there are two pieces of code here that deal with diagnostic spans/ranges in quite different ways, so my comments are all just around that. It's hard to know if I'm completely off base though without seeing what the syntax tree looks like.

// <button></button>$$
// should get the button node instead of the razor document (which is the parent
// of the EOF token)
if (token.Kind == SyntaxKind.EndOfFile && node.Span.Contains(index - 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess this makes sense. if there were whitespace at the end, i think the compiler method would do the same thing. Maybe @333fred has opinions though

return token.Parent;
}

public static SyntaxNode? FindNode(this SyntaxNode @this, Language.Syntax.TextSpan span, bool includeWhitespace = false, bool getInnermostNodeForTie = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks scary, and seems like it only has one caller, which is surprising and doesn't fill me with joy, but the fact that its copied from Roslyn means I'm going to assume its fine :)

var owner = syntaxTree.GetOwner(sourceText, d.Range.End, logger);
if (owner is null)

var owner = syntaxTree.Root.FindNode(d.Range.AsRazorTextSpan(sourceText), getInnermostNodeForTie: true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I added another FindNode usage!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I can't explain why it doesn't find the CSharpCodeBlockSyntax with the same span. This could be a difference in how Roslyn and Razor make trees/how FindToken works.

My lack of understanding also means I don't think I can make a good comment. I just know it works and all the tests are passing

}

var isCSharp = owner.Kind is SyntaxKind.CSharpExpressionLiteral or SyntaxKind.CSharpStatementLiteral or SyntaxKind.CSharpEphemeralTextLiteral;
if (owner is CSharpImplicitExpressionSyntax implicitExpressionSyntax &&
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this could have been written with some fancy matching, but I couldn't figure it out for the 2 minutes I tried.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't actually comment on the entire range, this replaces from here to return false;

return owner is CSharpImplicitExpressionSyntax { Body: CSharpImplicitExpressionBodySyntax { CSharpCode: CSharpCodeBlockSyntax { Children: [var child] } } }
       && IsCSharpKind(child);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and I see that David had a separate comment with exactly that below.

Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new code that traverses the tree. I think FindNode tests would be good, if for no other reason than to demonstrate how it works, but could be a follow up too. The existing tests are presumably good enough for this usage.

{
return codeBlock.Children.Count == 1
&& IsCsharpKind(codeBlock.Children[0]);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this whole thing is possibly:

        return (owner is CSharpImplicitExpressionSyntax 
            {
                Body: CSharpImplicitExpressionBodySyntax
                {
                    CSharpCode: CSharpCodeBlockSyntax
                    {
                        Children: [var firstChild]
                    }
                }
            } &&
            IsCsharpKind(firstChild);

though I'm not sure thats actually readable in any way, so this is not a suggestion to change it necessarily, I just enjoy these trivia challenges.

Also since when did the && go at the start of the line? I always put them at the end, so the conditions all line up neatly under the if (

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also since when did the && go at the start of the line?

This was from using the Wrap Expression code fix. I'm happy with either way though.

Benefit of this style is for diffing, so you only see the line being added/removed, and don't mess with others. Similar to wrapping arguments with

static void Method(
	arg1
	, arg2
	, arg3

Although for methods and enums I'd prefer to just leave a trailing comma if possible

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Similar to " is not the argument you think it is 😛

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I prefer && at the start of the line because I find it more readable. I'm only wrapping when the expression is long, and the && is more visible at the start of the line, which (imo) makes the precedence stand out more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WannaFightJackReacherGIF

// Tie-breaking.
if (!getInnermostNodeForTie)
{
var cuRoot = node.Ancestors().Last();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@333fred: Should there be a cheaper way to retrieve the RazorSyntaxTree for a given red node?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised there wasn't as well when I implemented FindToken. It's one of the things missing from our copy of Roslyn's nodes.

@ryzngard
Copy link
Contributor Author

@davidwengier I added some generated tests for the time being, I think some hand written ones will also be useful. This at least asserts current behavior with a bunch of different spans

@ryzngard ryzngard merged commit 598c7d5 into dotnet:main Aug 30, 2023
@ryzngard ryzngard deleted the LocateOwner/CodeActions_Diagnostics_Completion branch August 30, 2023 19:46
@ghost ghost added this to the Next milestone Aug 30, 2023
@Cosifne Cosifne modified the milestones: Next, 17.8 P3 Sep 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants