-
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?
Changes from 1 commit
3dedbb2
6dc3ef9
6b71577
52360bb
d9308a4
700c4ac
d3769d6
5c82803
43ad9b1
41d44b2
e3db946
e9bc939
481f5ed
93c0429
3efee13
2b49119
c2051bf
e9aa041
9579d93
5a62547
33dccdd
57437ba
727423b
6c83c6e
2482c4c
d4bec8b
9089e0a
5aff394
5fba883
cd373f6
1fb8c27
da07af5
0c3e2a9
2309cb0
7285658
52dce8e
c603b66
afb760a
4d30c8f
641721d
65ea1ad
defb3c2
d4345d5
2008be6
c0687c6
313bdfd
fa0fd41
4d4a1ec
e059159
a95f803
1e62744
1e1d382
9a727e4
8dacfcb
ab492ca
b1deb98
2008bfe
194c3a2
a487621
f38390d
051d067
f86bf43
35b2346
aafe0de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
; Please do not edit this file manually, it should only be updated through code fix application. | ||
### New Rules | ||
Rule ID | Category | Severity | Notes | ||
--------|----------|----------|------- | ||
CA1071 | Design | Warning | ConstructorParametersShouldMatchPropertyNamesAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1071) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Immutable; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq; | ||
using Analyzer.Utilities; | ||
using Analyzer.Utilities.Extensions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Microsoft.NetCore.Analyzers.Runtime | ||
{ | ||
/// <summary> | ||
/// CA1071: Constructor parameters should match property and field names | ||
/// </summary> | ||
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] | ||
public sealed class ConstructorParametersShouldMatchPropertyNamesAnalyzer : DiagnosticAnalyzer | ||
psxvoid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
internal const string RuleId = "CA1071"; | ||
|
||
private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ConstructorParametersShouldMatchPropertyNamesTitle), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); | ||
|
||
private static readonly LocalizableString s_localizableMessageProperty = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ConstructorParameterShouldMatchPropertyName), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); | ||
private static readonly LocalizableString s_localizableMessageField = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ConstructorParameterShouldMatchFieldName), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); | ||
private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ConstructorParametersShouldMatchPropertyNamesDescription), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); | ||
|
||
internal static DiagnosticDescriptor PropertyRule = DiagnosticDescriptorHelper.Create(RuleId, | ||
s_localizableTitle, | ||
s_localizableMessageProperty, | ||
DiagnosticCategory.Design, | ||
RuleLevel.BuildWarning, | ||
buyaa-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
description: s_localizableDescription, | ||
isPortedFxCopRule: true, | ||
buyaa-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
isDataflowRule: false); | ||
internal static DiagnosticDescriptor FieldRule = DiagnosticDescriptorHelper.Create(RuleId, | ||
s_localizableTitle, | ||
s_localizableMessageField, | ||
DiagnosticCategory.Design, | ||
RuleLevel.BuildWarning, | ||
description: s_localizableDescription, | ||
isPortedFxCopRule: true, | ||
isDataflowRule: false); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(PropertyRule, FieldRule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
|
||
context.RegisterCompilationStartAction( | ||
(compilationStartContext) => | ||
{ | ||
INamedTypeSymbol? jsonConstructorAttributeNamedSymbol = compilationStartContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute); | ||
if (jsonConstructorAttributeNamedSymbol == null) | ||
{ | ||
return; | ||
} | ||
|
||
var paramAnalyzer = new ParameterAnalyzer(jsonConstructorAttributeNamedSymbol); | ||
|
||
compilationStartContext.RegisterOperationBlockStartAction( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if we need to analyze each operation block for checking a constructor, probably better to register symbol action for named types There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tried it before using the current approach. The idea was to reduce the overall amount of checks/calls the analyzer should do before matching a correct type (to have as low an impact on the performance as possible). I thought that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks, that was my concert too, by my understanding operation,
I am not sure if it is counted as a combination of actions, i would assume first Instead with the |
||
(startOperationBlockContext) => | ||
{ | ||
if (startOperationBlockContext.OwningSymbol is IMethodSymbol method | ||
&& !paramAnalyzer.ShouldAnalyzeMethod(method)) | ||
{ | ||
return; | ||
} | ||
|
||
startOperationBlockContext.RegisterOperationAction( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You will not need to register another action as you can access all parameters from the |
||
context => ParameterAnalyzer.AnalyzeOperationAndReport(context), | ||
OperationKind.ParameterReference); | ||
}); | ||
}); | ||
} | ||
|
||
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 commentThe 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. |
||
|
||
public static void AnalyzeOperationAndReport(OperationAnalysisContext context) | ||
{ | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. This is likely to fail for tuple assignments (deconstruction assignment) like There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Done. Though, I'm not sure whether it's possible in VB. |
||
{ | ||
return; | ||
} | ||
|
||
IParameterSymbol param = operation.Parameter; | ||
ISymbol? referencedSymbol = assignment.Target.GetReferencedMemberOrLocalOrParameter(); | ||
|
||
if (referencedSymbol == null) | ||
{ | ||
return; | ||
} | ||
|
||
var field = referencedSymbol as IFieldSymbol; | ||
var prop = referencedSymbol as IPropertySymbol; | ||
|
||
if (field == null && prop == null) | ||
{ | ||
return; | ||
} | ||
|
||
// Only process instance fields and properties | ||
if (referencedSymbol.IsStatic) | ||
{ | ||
return; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be combined with the null check above as follows There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
if (IsSupportedField(field) && !IsParamMatchFieldName(param, field)) | ||
{ | ||
context.ReportDiagnostic( | ||
param.CreateDiagnostic( | ||
FieldRule, | ||
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat), | ||
param.Name, | ||
field.Name)); | ||
} | ||
|
||
if (IsSupportedProp(prop) && !IsParamMatchPropName(param, prop)) | ||
{ | ||
context.ReportDiagnostic( | ||
param.CreateDiagnostic( | ||
PropertyRule, | ||
param.ContainingType.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat), | ||
param.Name, | ||
prop.Name)); | ||
} | ||
} | ||
|
||
private static bool IsSupportedProp([NotNullWhen(true)] IPropertySymbol? prop) | ||
{ | ||
if (prop == null) | ||
{ | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static bool IsSupportedField([NotNullWhen(true)] IFieldSymbol? field) | ||
{ | ||
if (field == null) | ||
{ | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static bool IsParamMatchFieldName(IParameterSymbol param, IFieldSymbol field) | ||
{ | ||
var paramWords = WordParser.Parse(param.Name, WordParserOptions.SplitCompoundWords); | ||
var fieldWords = WordParser.Parse(field.Name, WordParserOptions.SplitCompoundWords).ToImmutableArray(); | ||
|
||
return paramWords.All(x => WordParser.ContainsWord(x, WordParserOptions.SplitCompoundWords, fieldWords)); | ||
} | ||
|
||
private static bool IsParamMatchPropName(IParameterSymbol param, IPropertySymbol prop) | ||
{ | ||
var paramWords = WordParser.Parse(param.Name, WordParserOptions.SplitCompoundWords); | ||
var propWords = WordParser.Parse(prop.Name, WordParserOptions.SplitCompoundWords).ToImmutableArray(); | ||
|
||
return paramWords.All(x => WordParser.ContainsWord(x, WordParserOptions.SplitCompoundWords, propWords)); | ||
} | ||
|
||
public bool ShouldAnalyzeMethod(IMethodSymbol method) | ||
{ | ||
// We only care about constructors with parameters. | ||
if (method.Parameters.IsEmpty) | ||
{ | ||
return false; | ||
} | ||
|
||
// We only care about constructors that are marked with JsonConstructor attribute. | ||
if (!method.IsJsonConstructor(_jsonConstructorAttributeInfoType)) | ||
{ | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
} | ||
} |
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.
To Do: remove