-
Notifications
You must be signed in to change notification settings - Fork 470
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
base: main
Are you sure you want to change the base?
Add ConstructorParamShouldMatchPropNames analyzer #4877
Conversation
.WithArguments(arguments); | ||
|
||
private DiagnosticResult CA2243BasicFieldResultAt(int line, int column, params string[] arguments) | ||
#pragma warning disable RS0030 // Do not used banned APIs |
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.
New tests shouldn't use banned APIs.
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'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.
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.
@psxvoid Did you add the span where the diagnostic is expected between {|#0:
and |}
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.
Yep, you are right, I was missing those. Thank you, fixed.
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.
New analyzers shouldn't target master branch. Please rebase on 6.0.1xx-preview2
branch.
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.
Thank you @psxvoid, overall looks you are solving it well, left some comments
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
...rs/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyNames.cs
Outdated
Show resolved
Hide resolved
...etAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/SerializationRulesDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...ts/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyNamesTests.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/UnitTests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj
Outdated
Show resolved
Hide resolved
Codecov Report
@@ 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 |
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
Public Sub New(firstIField as Integer, secondIField as Object) | ||
Me.firstField = firstIField | ||
Me.secondField = secondIField |
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.
Consider adding another test without 'Me' to ensure proper codefix behavior when it's implemented.
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.
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.
...rs/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyNames.cs
Outdated
Show resolved
Hide resolved
private sealed class ParameterAnalyzer | ||
{ | ||
private readonly INamedTypeSymbol _jsonConstructorAttributeInfoType; | ||
|
||
public ParameterAnalyzer(INamedTypeSymbol jsonConstructorAttributeInfoType) | ||
{ | ||
_jsonConstructorAttributeInfoType = jsonConstructorAttributeInfoType; | ||
} |
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.
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) |
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 is likely to fail for tuple assignments (deconstruction assignment) like (_x, _y) = (x, y);
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 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.
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.
@psxvoid The tests in that commit are no diagnostic tests. I meant to confirm that diagnostic cases are working correctly for tuple assignments.
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.
Yep, sorry, it doesn't work with tuple assignments. I'll come back to it a bit later.
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.
Done. Though, I'm not sure whether it's possible in VB.
You're still targeting |
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) => |
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.
nit: parentheses are not needed.
context.RegisterCompilationStartAction((context) => | |
context.RegisterCompilationStartAction(context => |
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.
Done.
|
||
context.RegisterCompilationStartAction((context) => | ||
{ | ||
INamedTypeSymbol? jsonConstructorAttributeNamedSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute); |
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.
nit: Use context.Compilation.TryGetOrCreateTypeByMetadataName
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.
Done.
.SetItem(ReferencedFieldOrPropertyNameKey, prop.Name) | ||
.SetItem(DiagnosticReasonKey, reason.ToString()); |
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.
Since we know the two keys being added are unique.
.SetItem(ReferencedFieldOrPropertyNameKey, prop.Name) | |
.SetItem(DiagnosticReasonKey, reason.ToString()); | |
.Add(ReferencedFieldOrPropertyNameKey, prop.Name) | |
.Add(DiagnosticReasonKey, reason.ToString()); |
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.
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), |
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.
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat), | |
param.ContainingType.Name, |
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.
Done.
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; | ||
} | ||
}", |
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.
Same for all strings.
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; | |
} | |
}", |
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")); |
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.
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 |
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.
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.
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.
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.
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), |
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.
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat), | |
param.ContainingType.Name, |
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.
Done.
.SetItem(ReferencedFieldOrPropertyNameKey, field.Name) | ||
.SetItem(DiagnosticReasonKey, reason.ToString()); |
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.
.SetItem(ReferencedFieldOrPropertyNameKey, field.Name) | |
.SetItem(DiagnosticReasonKey, reason.ToString()); | |
.Add(ReferencedFieldOrPropertyNameKey, field.Name) | |
.Add(DiagnosticReasonKey, reason.ToString()); |
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.
Done.
INamedTypeSymbol? jsonConstructorAttributeNamedSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute); | ||
if (jsonConstructorAttributeNamedSymbol == null) |
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.
INamedTypeSymbol? jsonConstructorAttributeNamedSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute); | |
if (jsonConstructorAttributeNamedSymbol == null) | |
if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute, out var jsonConstructorAttributeNamedSymbol)) |
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.
Done.
...etCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNamesTests.Fixer.cs
Show resolved
Hide resolved
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 |
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. |
It's OK to have a separate file, but please do not add repeated tests in both files, I
Please merge conflicts and mark it ready for review if its done, we don't normally review draft PRs |
Got it. Thanks a lot! |
Partial implementation of #33796: Constructor parameters should match property names
Notes/Issues/Questions:
Microsoft.NetCore.Analyzers/Runtime
. Should it be placed inMicrosoft.CodeQuality.Analyzers/ApiDesignGuidelines
instead?@mavasani Most likely we have to port
NamingStyleOptions
fromRoslyn
repository because naming styles may be set to user-defined prefixes in.editorconfig
for properties and fields. ButNamingStyleOptions
areinternal
toRoslyn
repo. Is my assumption correct? If yes, could you please help define the approach to portNamingStyleOptions
toRoslynAnalyzers
?Fixes dotnet/runtime#33796