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

Add ConstructorParamShouldMatchPropNames analyzer #4877

Draft
wants to merge 64 commits into
base: main
Choose a base branch
from

Conversation

psxvoid
Copy link

@psxvoid psxvoid commented Feb 22, 2021

Partial implementation of #33796: Constructor parameters should match property names

Notes/Issues/Questions:

  • the analyzer does not respect naming style options yet
  • the analyzer does not contain the code fix yet
  • the category of the analyzer is set to "Design" but the analyzer is placed in Microsoft.NetCore.Analyzers/Runtime. Should it be placed in Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines instead?

@mavasani Most likely we have to port NamingStyleOptions from Roslyn repository because naming styles may be set to user-defined prefixes in .editorconfig for properties and fields. But NamingStyleOptions are internal to Roslyn repo. Is my assumption correct? If yes, could you please help define the approach to port NamingStyleOptions to RoslynAnalyzers?
Fixes dotnet/runtime#33796

@psxvoid psxvoid requested a review from a team as a code owner February 22, 2021 05:35
@dnfadmin
Copy link

dnfadmin commented Feb 22, 2021

CLA assistant check
All CLA requirements met.

@psxvoid psxvoid marked this pull request as draft February 22, 2021 06:13
.WithArguments(arguments);

private DiagnosticResult CA2243BasicFieldResultAt(int line, int column, params string[] arguments)
#pragma warning disable RS0030 // Do not used banned APIs
Copy link
Member

Choose a reason for hiding this comment

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

New tests shouldn't use banned APIs.

Copy link
Author

Choose a reason for hiding this comment

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

I've quickly tried to use WithLocation(markupKey) (not banned API) here instead but without luck yet - tests failed with System.InvalidOperationException : The markup location '#0' was not found in the input.. And I'm not sure how to fix it yet. It may be related to #624 in RoslynSDK issue. I'll come back to it later after adding a code fix.

Copy link
Member

Choose a reason for hiding this comment

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

@psxvoid Did you add the span where the diagnostic is expected between {|#0: and |}

Copy link
Author

Choose a reason for hiding this comment

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

Yep, you are right, I was missing those. Thank you, fixed.

Copy link
Member

@Youssef1313 Youssef1313 left a comment

Choose a reason for hiding this comment

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

New analyzers shouldn't target master branch. Please rebase on 6.0.1xx-preview2 branch.

Copy link
Member

@buyaa-n buyaa-n left a comment

Choose a reason for hiding this comment

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

Thank you @psxvoid, overall looks you are solving it well, left some comments

@codecov
Copy link

codecov bot commented Feb 25, 2021

Codecov Report

Merging #4877 (aafe0de) into main (96ae2f9) will decrease coverage by 0.07%.
The diff coverage is 98.88%.

@@            Coverage Diff             @@
##             main    #4877      +/-   ##
==========================================
- Coverage   95.69%   95.61%   -0.08%     
==========================================
  Files        1197     1204       +7     
  Lines      271784   277965    +6181     
  Branches    16433    16723     +290     
==========================================
+ Hits       260078   265774    +5696     
- Misses       9599     9996     +397     
- Partials     2107     2195      +88     

Comment on lines 213 to 215
Public Sub New(firstIField as Integer, secondIField as Object)
Me.firstField = firstIField
Me.secondField = secondIField
Copy link
Member

Choose a reason for hiding this comment

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

Consider adding another test without 'Me' to ensure proper codefix behavior when it's implemented.

Copy link
Author

Choose a reason for hiding this comment

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

Is it something that can happen? I haven't written a code fix yet, but I certainly will add another test if it something that can go wrong.

Comment on lines 79 to 86
private sealed class ParameterAnalyzer
{
private readonly INamedTypeSymbol _jsonConstructorAttributeInfoType;

public ParameterAnalyzer(INamedTypeSymbol jsonConstructorAttributeInfoType)
{
_jsonConstructorAttributeInfoType = jsonConstructorAttributeInfoType;
}
Copy link
Member

Choose a reason for hiding this comment

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

Only one method is using the field. I personally prefer this being as a static class with jsonConstructorAttributeType being passed to the only method needs it.

{
var operation = (IParameterReferenceOperation)context.Operation;

if (operation.Parent is not IAssignmentOperation assignment)
Copy link
Member

@Youssef1313 Youssef1313 Feb 26, 2021

Choose a reason for hiding this comment

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

This is likely to fail for tuple assignments (deconstruction assignment) like (_x, _y) = (x, y);

Copy link
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 whether I've completely got your point, but it seems that tuple assignment works fine with it, see the following commit: [POC] Verify tuple assignment is working with CA1071

Field names you've mentioned do not match parameter names, JsonConstructor will throw a runtime exception. That's why diagnostic must still be reported anyway for this case.

Copy link
Member

Choose a reason for hiding this comment

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

@psxvoid The tests in that commit are no diagnostic tests. I meant to confirm that diagnostic cases are working correctly for tuple assignments.

Copy link
Author

Choose a reason for hiding this comment

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

Yep, sorry, it doesn't work with tuple assignments. I'll come back to it a bit later.

Copy link
Author

@psxvoid psxvoid Mar 4, 2021

Choose a reason for hiding this comment

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

Done. Though, I'm not sure whether it's possible in VB.

@Youssef1313
Copy link
Member

New analyzers shouldn't target master branch. Please rebase on 6.0.1xx-preview2 branch.

You're still targeting main. It should target release/6.0.1xx at this time.

@psxvoid
Copy link
Author

psxvoid commented Jun 28, 2021

New analyzers shouldn't target master branch. Please rebase on 6.0.1xx-preview2 branch.

You're still targeting main. It should target release/6.0.1xx at this time.

I'm aware of it. I want to finish some fixes and cleanups, implement some missing features; and then create another non-draft PR to avoid force-push and preserve review comments. But thank you for helping to find the latest branch, very appreciated.

context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

context.RegisterCompilationStartAction((context) =>
Copy link
Member

Choose a reason for hiding this comment

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

nit: parentheses are not needed.

Suggested change
context.RegisterCompilationStartAction((context) =>
context.RegisterCompilationStartAction(context =>

Copy link
Author

Choose a reason for hiding this comment

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

Done.


context.RegisterCompilationStartAction((context) =>
{
INamedTypeSymbol? jsonConstructorAttributeNamedSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute);
Copy link
Member

Choose a reason for hiding this comment

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

nit: Use context.Compilation.TryGetOrCreateTypeByMetadataName

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 201 to 202
.SetItem(ReferencedFieldOrPropertyNameKey, prop.Name)
.SetItem(DiagnosticReasonKey, reason.ToString());
Copy link
Member

Choose a reason for hiding this comment

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

Since we know the two keys being added are unique.

Suggested change
.SetItem(ReferencedFieldOrPropertyNameKey, prop.Name)
.SetItem(DiagnosticReasonKey, reason.ToString());
.Add(ReferencedFieldOrPropertyNameKey, prop.Name)
.Add(DiagnosticReasonKey, reason.ToString());

Copy link
Member

Choose a reason for hiding this comment

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

Consider using constant strings instead of an enum, which avoids doing Enum.Parse in codefix, and also matches what's done in other analyzers.

diagnosticDescriptor,
reason == ParameterDiagnosticReason.PropertyInappropriateVisibility ? prop.Locations : ImmutableArray<Location>.Empty,
properties,
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat),
param.ContainingType.Name,

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Comment on lines +21 to +35
using System.Text.Json.Serialization;

public class C1
{
public int FirstProp { get; }

public object SecondProp { get; }

[JsonConstructor]
public C1(int [|firstDrop|], object secondProp)
{
this.FirstProp = firstDrop;
this.SecondProp = secondProp;
}
}",
Copy link
Member

Choose a reason for hiding this comment

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

Same for all strings.

Suggested change
using System.Text.Json.Serialization;
public class C1
{
public int FirstProp { get; }
public object SecondProp { get; }
[JsonConstructor]
public C1(int [|firstDrop|], object secondProp)
{
this.FirstProp = firstDrop;
this.SecondProp = secondProp;
}
}",
using System.Text.Json.Serialization;
public class C1
{
public int FirstProp { get; }
public object SecondProp { get; }
[JsonConstructor]
public C1(int [|firstDrop|], object secondProp)
{
this.FirstProp = firstDrop;
this.SecondProp = secondProp;
}
}",

Comment on lines +23 to +39
await VerifyCSharpAnalyzerAsync(@"
using System.Text.Json.Serialization;

public class C1
{
public int FirstProp { get; }
public object SecondProp { get; }

[JsonConstructor]
public C1(int {|#0:firstDrop|}, object {|#1:secondDrop|})
{
this.FirstProp = firstDrop;
this.SecondProp = secondDrop;
}
}",
CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstDrop", "FirstProp"),
CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondDrop", "SecondProp"));
Copy link
Member

Choose a reason for hiding this comment

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

As long as the source has diagnostics, codefix should be tested, even if it's not offered.


namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests
{
public class ConstructorParametersShouldMatchPropertyAndFieldNamesFixerTests
Copy link
Member

Choose a reason for hiding this comment

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

⚠️ ⚠️ There shouldn't be separate test files for analyzer and codefix.

Copy link
Author

@psxvoid psxvoid Jul 27, 2021

Choose a reason for hiding this comment

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

Why so? It seems like a lot of analyzers have separate files for an analyzer and a code fix. For example:

  • TestForEmptyStringsUsingStringLengthTests
    TestForEmptyStringsUsingStringLengthTests.Fixer
  • UseOrdinalStringComparisonTests
    UseOrdinalStringComparisonTests.Fixer
  • MarkAllNonSerializableFieldsTests
    MarkAllNonSerializableFieldsTests.Fixer
    etc.

Copy link
Member

Choose a reason for hiding this comment

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

For me it's OK to have separate files, especially for the scenarios where codefix is not suggested, testing no diagnostic /false positives scenarios.

Though for the scenarios that has a codefix do not need to repeat the test in the analyzers test and codefix test. For example I see you have a same test in analyzer and code fix:

        [Fact]
        public async Task CA1071_ClassPropsDoNotMatch_ConstructorParametersShouldMatchPropertyNames_CSharp()
        {
             await VerifyCSharpAnalyzerAsync(@"
                using System.Text.Json.Serialization;
                public class C1
                {
                    public int FirstProp { get; }
                    public object SecondProp { get; }
                    [JsonConstructor]
                    public C1(int {|#0:firstDrop|}, object {|#1:secondDrop|})
                    {
                        this.FirstProp = firstDrop;
                        this.SecondProp = secondDrop;
                    }
                }",
                CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstDrop", "FirstProp"),
                CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondDrop", "SecondProp"));
         }
```
And in code fix tests:
```cs
        [Fact]
        public async Task CA1071_ClassSinglePropDoesNotMatch_CSharp()
        {
            await VerifyCSharpCodeFixAsync(@"
                using System.Text.Json.Serialization;
                public class C1
                {
                    public int FirstProp { get; }
                    public object SecondProp { get; }
                    [JsonConstructor]
                    public C1(int [|firstDrop|], object secondProp)
                    {
                        this.FirstProp = firstDrop;
                        this.SecondProp = secondProp;
                    }
                }",
                @"
                using System.Text.Json.Serialization;
                public class C1
                {
                    public int FirstProp { get; }
                    public object SecondProp { get; }
                    [JsonConstructor]
                    public C1(int firstProp, object secondProp)
                    {
                        this.FirstProp = firstProp;
                        this.SecondProp = secondProp;
                    }
                }");
        }
```
These should be just in code fix tests

diagnosticDescriptor,
reason == ParameterDiagnosticReason.FieldInappropriateVisibility ? field.Locations : ImmutableArray<Location>.Empty,
properties,
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat),
param.ContainingType.Name,

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 185 to 186
.SetItem(ReferencedFieldOrPropertyNameKey, field.Name)
.SetItem(DiagnosticReasonKey, reason.ToString());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.SetItem(ReferencedFieldOrPropertyNameKey, field.Name)
.SetItem(DiagnosticReasonKey, reason.ToString());
.Add(ReferencedFieldOrPropertyNameKey, field.Name)
.Add(DiagnosticReasonKey, reason.ToString());

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 49 to 50
INamedTypeSymbol? jsonConstructorAttributeNamedSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute);
if (jsonConstructorAttributeNamedSymbol == null)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
INamedTypeSymbol? jsonConstructorAttributeNamedSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute);
if (jsonConstructorAttributeNamedSymbol == null)
if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute, out var jsonConstructorAttributeNamedSymbol))

Copy link
Author

Choose a reason for hiding this comment

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

Done.

@buyaa-n
Copy link
Member

buyaa-n commented May 8, 2023

Hey @psxvoid, what is left for this analyzer? Could you finish the remaining work?

Or if this is done and ready for review, please resolve conflicts and mark it as ready for review. Thanks

@psxvoid
Copy link
Author

psxvoid commented May 9, 2023

Hi @buyaa-n

As far as I remember, it was almost done but it's stuck on the code review phase. I haven't understood why There shouldn't be separate test files for analyzer and codefix while many other analyzers do have separate ones. And I've never got a reply.

@buyaa-n
Copy link
Member

buyaa-n commented May 9, 2023

I haven't understood why #4877 (comment) while many other analyzers do have separate ones.

It's OK to have a separate file, but please do not add repeated tests in both files, I

As far as I remember, it was almost done but it's stuck on the code review phase.

Please merge conflicts and mark it ready for review if its done, we don't normally review draft PRs

@psxvoid
Copy link
Author

psxvoid commented May 10, 2023

It's OK to have a separate file, but please do not add repeated tests in both files
Please merge conflicts and mark it ready for review if its done, we don't normally review draft PRs

Got it. Thanks a lot!

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.

Constructor parameters should match property names
6 participants