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

Implement IdentifiersShouldNotContainUnderscoresFixer #3706

Merged
merged 4 commits into from
May 21, 2021

Conversation

Youssef1313
Copy link
Member

@Youssef1313 Youssef1313 commented Jun 2, 2020

Fixes #444

@Youssef1313 Youssef1313 requested a review from a team as a code owner June 2, 2020 18:15
private static async Task<Document> RenameWithoutUnderscores(Document document, ISymbol symbol, CancellationToken cancellationToken)
{
Solution newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, symbol, symbol.Name.Replace("_", ""), null, cancellationToken).ConfigureAwait(false);
return newSolution.GetDocument(document.Id);
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 is written just like this.

But I'm getting a null-reference warning:

image

Why a warning is emitted here but not there? I can't get the difference.

Copy link
Contributor

Choose a reason for hiding this comment

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

GetDocument can return a null value if the document ID does not belong to the solution. You should add a ! suppression here. We should consider adding GetRequiredXXX extension methods to our repo similar to the ones in Roslyn: https://github.com/dotnet/roslyn/blob/bc2d6a2597d07a574be4e292f9dbd9c8f975fd7f/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs#L36-L68

Copy link
Member Author

Choose a reason for hiding this comment

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

@mavasani Thanks. I didn't see the ! there 😆

That makes sense now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Should I add the extension methods in this PR? or leave it as another task?

A small question: why the ISolutionExtensions is made internal in roslyn? I can't see an obvious problem with exposing these extensions to be public.

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 there is possible a pending work item to make these public with API review approval. @CyrusNajmabadi do you know?

Copy link
Contributor

Choose a reason for hiding this comment

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

Should I add the extension methods in this PR? or leave it as another task?

A separate PR might get in quicker.

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 there is possible a pending work item to make these public with API review approval. @CyrusNajmabadi do you know?

Don't know what we're planning there. It's a pretty awful situation all around. i'd prefer we just break these to throw.

@Youssef1313
Copy link
Member Author

@mavasani Any thing left except unit test?

@codecov
Copy link

codecov bot commented Jun 3, 2020

Codecov Report

Merging #3706 (e7c3d0e) into main (9e5f533) will increase coverage by 0.00%.
The diff coverage is 98.23%.

@@            Coverage Diff            @@
##             main    #3706     +/-   ##
=========================================
  Coverage   95.54%   95.55%             
=========================================
  Files        1185     1205     +20     
  Lines      272465   279213   +6748     
  Branches    16428    17835   +1407     
=========================================
+ Hits       260338   266794   +6456     
- Misses      10001    10256    +255     
- Partials     2126     2163     +37     

@Youssef1313
Copy link
Member Author

I think there is something wrong in the way I wrote the test causing it to fail.

@mavasani, Can you check please?

@mavasani
Copy link
Contributor

mavasani commented Jun 3, 2020

I think there is something wrong in the way I wrote the test causing it to fail.

CA1707 analyzer uses multiple descriptors with same rule ID (see here), but different messages. So the test framework cannot implicitly deduce which descriptor/message is expected from markup. You need to explicitly provide which descriptor to use, see an example test below:

[Fact]
public async Task CA2248_EnumTypesAreDifferent_Diagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
public class C
{
[Flags]
public enum MyEnum { A, B, }
[Flags]
public enum OtherEnum { A, }
public void Method(MyEnum m)
{
{|#0:m.HasFlag(OtherEnum.A)|};
}
}",
VerifyCS.Diagnostic(ProvideCorrectArgumentToEnumHasFlag.DifferentTypeRule).WithLocation(0).WithArguments("OtherEnum", "MyEnum"));

@Youssef1313
Copy link
Member Author

@Evangelink Thanks for your review. 💯


private static async Task<Solution> RenameWithoutUnderscoresAsync(Document document, ISymbol symbol, string newName, CancellationToken cancellationToken)
{
return await Renamer.RenameSymbolAsync(document.Project.Solution, symbol, newName, null, cancellationToken).ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just inline this call and avoid an await?

Copy link
Contributor

@mavasani mavasani left a comment

Choose a reason for hiding this comment

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

Overall looks good. Please add some more tests to cover all scenarios. Thanks!

@Youssef1313
Copy link
Member Author

Youssef1313 commented Jun 6, 2020

@mavasani, I've added an equivalent VB test to the test I previously added.
But have no idea about what kind of more cases you'd like to see?

edit:

A two possible cases that came to my mind and will likely fail with the current approach are:

  • If the name contains only underscores, the new name will become just an empty string.
  • If the name starts with an underscore then followed by a digit. By removing the underscore the name will start with a digit which shouldn't be a valid name.

Are these the kind of cases you'd like to see?

@Youssef1313
Copy link
Member Author

The current test failure is due to the identifier being a reserved word after renaming. My_class is renaming to MyClass, which is invalid due to MyClass being a VB keyword.

/cc @Evangelink Mentioning as your PR (#3352) was based on renaming too. Did you handle such case?

@Evangelink
Copy link
Member

@Youssef1313 I am not handling this case so I have the same issue as you do. I would have expected the symbol renamer to handle this case. I can see a couple of implementations to workaround that but let's wait for @mavasani's feedback.

@Youssef1313
Copy link
Member Author

Sorry for the delay on this one. I've updated it locally to put @newName (C#) or [newName] (VB) when the new name becomes invalid (because it's a keyword). But the test fails (it doesn't enter the fixer at all) with the following error:

The markup location '#0' was not found in the input.

However, I see that the input has {|#0:My_class|}, not sure why it's not found.

public enum DoesNotMatterEnum
{
{|#2:_EnumWithUnderscore|},
{|#3:_|}
Copy link
Member

Choose a reason for hiding this comment

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

I would probably not report on this case.

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 thought about that at first, but preferred to keep the warning in this case without a code fix.

But after a second thought, it would probably be okay if we don't report on this case. It's really a very edge case. Almost no one will write something like that 😄

@mavasani, would you be okay if we don't report on this case?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I don't really care if we report a diagnostic on _ or not.

Copy link
Member

Choose a reason for hiding this comment

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

On the second thought I also think reporting or not are both valid options.

@Evangelink
Copy link
Member

@Youssef1313 Let me know if I haven't provided enough info for you to move forward on this PR.

@Evangelink
Copy link
Member

@Youssef1313 Do you mind if I push a commit on your PR?

@Youssef1313
Copy link
Member Author

Youssef1313 commented Aug 9, 2020

@Evangelink You can push anytime :)

Does that require giving you write access on my fork?

@Evangelink
Copy link
Member

@Youssef1313 Don't bother! I thought I found something to unlock but it's not the case...

To sum-up my local changes:

  • Update the few broken tests because of the rename not renaming the override/implementations. It'd be nice to create a ticket (I couldn't find any I was supposed to have created).

  • Create a new method to retrieve the declaration node (for example protected abstract SyntaxNode GetDeclarationNode(SyntaxNode node);), the implementation is rather simple.

C#:

protected override SyntaxNode GetDeclarationNode(SyntaxNode node)
    => node.IsKind(SyntaxKind.IdentifierName)
        ? node.Parent
        : node;

VB.NET:

Protected Overrides Function GetDeclarationNode(node As SyntaxNode) As SyntaxNode
    If node.IsKind(SyntaxKind.IdentifierName) Then
        Return node.Parent
    Else
        Return node
    End If
End Function

@Youssef1313
Copy link
Member Author

@Evangelink Thanks for the great help you provided here. I opened an issue in dotnet/roslyn for the problem with RenameSymbolAsync. I'll do your suggestions very soon and push.

@Youssef1313 Youssef1313 mentioned this pull request Aug 10, 2020
@Youssef1313
Copy link
Member Author

Youssef1313 commented Aug 10, 2020

What should the expected behavior be for a case like the following (which is far away from a real scenario)?

public class My_Class { }
public class My__Class { }

@Evangelink @mavasani


Another case that comes to mind also:

public class Base
{
    public void My_Method() { }
}
public class Derived : Base
{
    new public void My_Method() { }
}

For the above case, if the user chose to fix all, then it's simple.

But if the user chose to fix in only one of them, the "new" keyword should be removed. (I'm not aware of how this would be implemented, nor how it would be tested)

Comment on lines 1254 to 1264
Public Class ImplementI1
Implements I1
' No diagnostic
Public Event E_ As System.EventHandler Implements I1.E_
Public Event E2_ As System.EventHandler
Public Event {|#3:E2_|} As System.EventHandler
End Class

Public Class Derives
Inherits ImplementI1

Public Shadows Event {|#4:E2_|} As System.EventHandler
Copy link
Member Author

Choose a reason for hiding this comment

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

When renaming E2_ in Derives class, the new name "E2" is detected as a clashing, most probably because E2 already exists inImplementI1. However, we indeed want to rename it. It's marked with Shadows and is meant to shadow the member in ImplementI1.

My opinion is that the analyzer don't report for the "Shadows" case, and RenameSymbolAsync gets it updated when dotnet/roslyn#46663 is fixed.

@mavasani @Evangelink What are your opinions?

Copy link
Member

Choose a reason for hiding this comment

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

It seems that there is a compiler warning when there is a member shadowing a non-existing member so I think that's fine if the code-fix misses renaming this case.

Tagging @mavasani for confirmation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I am fine to keep it as-is for now to reduce the scope of this PR. If required, this can be tackled in a follow-up issue/PR.

@mavasani
Copy link
Contributor

mavasani commented Oct 9, 2020

@Youssef1313 are you able to fix the tests here? Anything else remaining for this PR?

@@ -19,7 +19,7 @@ public class IdentifiersShouldNotContainUnderscoresTests
{
#region CSharp Tests
[Fact]
public async Task CA1707_ForAssembly_CSharp()
public async Task CA1707_ForAssembly_CSharp() // TODO: How to test the code fixer for this?
Copy link
Member

Choose a reason for hiding this comment

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

Shall we skip the codefix for the assembly level then?

@Evangelink
Copy link
Member

@Youssef1313 Could you fix this little conflict and the few open comments?

Base automatically changed from master to main March 5, 2021 02:20
@Evangelink Evangelink requested a review from mavasani March 7, 2021 15:33
@mavasani mavasani self-assigned this May 20, 2021
@mavasani
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@mavasani mavasani merged commit e8cd97a into dotnet:main May 21, 2021
@mavasani
Copy link
Contributor

Thanks @Youssef1313 and sorry for the delay in getting this merged.

@Youssef1313 Youssef1313 deleted the ca1707 branch May 21, 2021 04:47
@Youssef1313
Copy link
Member Author

Thanks @Youssef1313 and sorry for the delay in getting this merged.

No problem.

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.

Port FxCop rule CA1707: IdentifiersShouldNotContainUnderscores
4 participants