-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Fix bug when caret is at the end of an argument #19489
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.
Can you add some tests for VB omitted arguments syntax:
MsgBox("Test message", , "Title bar text")
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 going to request one more test, just to make sure. Make M
return an Integer
.
Input:
M(1, M(1, [||], 3))
Expected behavior: no code fix provided
@@ -34,7 +34,13 @@ protected abstract class Analyzer<TBaseArgumentSyntax, TArgumentSyntax, TArgumen | |||
return; | |||
} | |||
|
|||
var argument = root.FindNode(context.Span).FirstAncestorOrSelf<TBaseArgumentSyntax>() as TArgumentSyntax; | |||
var token = root.FindToken(context.Span.Start); | |||
if (token.ValueText == ")" || token.ValueText == ",") |
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.
💭 Personally I would prefer to use the token's kind instead of text (which may require an abstract method implemented by the derived types), but if @CyrusNajmabadi doesn't have a problem with the current approach then I won't block on 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.
this should definitely be checking if it is .Kind() == SyntaxKind.CloseParen
and SyntaxKind.Comma.
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.
It should also only move back if the caret is literally at the .Span.Start of the token.
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.
Because this is in abstrcat code, you'll need to provide customized logic for VB/C# through the derived types.
|
||
<WorkItem(19175, "https://github.com/dotnet/roslyn/issues/19175")> | ||
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)> | ||
Public Async Function TestCaretPositionAtTheEnd4() As Task |
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.
💡 Maybe file a issue for the future showing how this refactoring operation could support missing arguments by omitting them during the conversion to named arguments. The output for this example would be:
M(arg1:=1, arg3:=3)
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.
That was in the initial implementation (#15687) but superseded by Cyrus changes, supposedly that's not the desirable behavior. IMO it makes sense because the refactoring says "add argument name" and it merely would do that, otherwise we're changing the code more than it's expected.
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.
See TestOmittedArguments2
for an example of a use case that we do offer the fix in presence of omitted args.
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 changes by Cyrus affecting this behavior appear to have been an oversight. I went through every comment and commit message of the issue and both PRs, and it appears that the change slipped through as opposed to being deliberate. I believe it would be helpful to include the refactoring operation for the following basic reason: if the user believes that the use of a named argument will improve readability for the argument 1
, then this is the minimal change required to provide that readability improvement. Also, it will not be clear to users why this feature works for some provided arguments, but not for others.
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.
My concern with such an approach would be that it could subtly change overload resolution if you now removed an argument. if there was a two-arg method, with matching names, you'd now be calling that, instead of the original member.
if (IsCloseParenOrComma(token)) | ||
{ | ||
if (token.Span.Start != context.Span.Start || | ||
IsCloseParenOrComma(token = token.GetPreviousToken())) |
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 certainly smells.
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 don't write it this way :)
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.
You mean the inline assignment or duplicated check for comma? I could inline the whole FirstAncestorOrSelf and bail when we reach an argument list instead. thoughts?
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 inline assignment. I would simply start with:
if (token.Span.Start == context.Span.Start &&
IsCloseParenOrComma(token) &&
token.GetPreviousToken().Span.End == context.Span.Start)
{
token = token.GetPreviousToken();
}
// rest of your logic.
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'll let you work with @CyrusNajmabadi on the implementation details, but the tests appear to cover the cases I was thinking of.
IsCloseParenOrComma(token = token.GetPreviousToken())) | ||
token = token.GetPreviousToken(); | ||
if (token.Span.End != context.Span.Start || | ||
IsCloseParenOrComma(token)) |
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 don't think you need this second IsCloseParenOrComma
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.
TestCaretPositionAtTheEnd5
requires it. though it's VB only.
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.
@CyrusNajmabadi It's a sneaky edge case. The missing argument led to the previous token being ,
, which is not part of the argument list but not the argument itself. This caused the code fix to search upwards and find the outer invocation and try to add the named argument there. It would be a rare but really weird experience when it occurred.
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 think the walk up should stop immediately at the first parameterlist. that would make more sense to me.
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.
yeah I did suggest that earlier.. so I need to fork FirstAncestorOrSelf method somewhere else since there is no helper for that case, I guess.
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.
@alrz I think the following would work:
FirstAncestorOrSelf<SyntaxNode>(node => node.IsKind(SyntaxKind.A, SyntaxKind.B))
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.
@sharwell it "filters" the result, it doesn't cause the walk up to stop.
|
||
private static SyntaxNode GetParent(this SyntaxNode node) | ||
{ | ||
return (node as IStructuredTriviaSyntax)?.ParentTrivia.Token.Parent ?? node.Parent; |
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 think this is a behavioral change. should use a pattern instead.
{ | ||
for (var current = node; current != null; current = current.GetParent()) | ||
{ | ||
if (predicate(current)) |
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 needs to come after the other check
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 would prefer e18581ad3ad280cd528307e1c27e8a2d76bba0fc be a separate pull request.
The method I added depends on |
Tagging @MattGertz Customer scenario Our 'add named argument' feature doesn't work if the user is at the end of an argument. This can be annoying and confusing. Bugs this fixes: Fixes #19175 Workarounds, if any None. Risk Low. Performance impact Low. Is this a regression from a previous update? No. Root cause analysis: We didn't validate the feature at all possible positions of the caret. How was the bug found? Bug report. |
Approved pending sharwell signoff. Thank you! |
Thanks @alrz for the PR, and @CyrusNajmabadi for getting the approval through! |
Fixes #19175