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

Fix bug when caret is at the end of an argument #19489

Merged
merged 4 commits into from
May 17, 2017
Merged

Conversation

alrz
Copy link
Member

@alrz alrz commented May 14, 2017

Fixes #19175

@sharwell sharwell added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label May 14, 2017
Copy link
Member

@sharwell sharwell left a 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")

Copy link
Member

@sharwell sharwell left a 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 == ",")
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 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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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
Copy link
Member

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)

Copy link
Member Author

@alrz alrz May 14, 2017

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.

Copy link
Member Author

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.

Copy link
Member

@sharwell sharwell May 14, 2017

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.

Copy link
Member

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()))
Copy link
Member Author

Choose a reason for hiding this comment

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

This certainly smells.

Copy link
Member

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 :)

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member

@sharwell sharwell left a 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))
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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))

Copy link
Member Author

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;
Copy link
Member Author

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))
Copy link
Member

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

Copy link
Member

@sharwell sharwell left a 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.

@alrz
Copy link
Member Author

alrz commented May 16, 2017

I would prefer e18581a be a separate pull request.

The method I added depends on GetParent method added in the clean up.

@CyrusNajmabadi
Copy link
Member

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.

@MattGertz
Copy link
Contributor

Approved pending sharwell signoff. Thank you!

@sharwell sharwell added this to the 15.3 milestone May 17, 2017
@sharwell sharwell merged commit f9f2b17 into dotnet:master May 17, 2017
@sharwell
Copy link
Member

Thanks @alrz for the PR, and @CyrusNajmabadi for getting the approval through!

@alrz alrz deleted the bug-19175 branch May 17, 2017 21:37
@sharwell sharwell self-assigned this May 25, 2017
@sharwell sharwell removed the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Aug 31, 2017
@sharwell sharwell added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Aug 31, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Approved to merge cla-already-signed Community The pull request was submitted by a contributor who is not a Microsoft employee.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants