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
Draft
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3dedbb2
Add ConstructorParamShouldMatchPropNames analyzer
psxvoid Feb 11, 2021
6dc3ef9
Fix - remove unused package reference
psxvoid Feb 24, 2021
6b71577
Fix json-ctor order in well-known-names
psxvoid Feb 24, 2021
52360bb
Fix code alignment in tests
psxvoid Feb 25, 2021
d9308a4
Mark the diagnostic as non-fx-cop-ported
psxvoid Feb 25, 2021
700c4ac
Add missing msbuild autogenerated assets
psxvoid Feb 25, 2021
d3769d6
Fix - remove unused package reference, part 2
psxvoid Feb 25, 2021
5c82803
Update rule's message
psxvoid Feb 25, 2021
43ad9b1
Rollback unrelated analyzer to the original state
psxvoid Feb 25, 2021
41d44b2
Update the description message for the analyzer
psxvoid Feb 26, 2021
e3db946
Fix misplaced comma and update the message
psxvoid Feb 26, 2021
e9bc939
Fix incorrect helper test method name prefix
psxvoid Feb 26, 2021
481f5ed
Update the message title with the recommended one
psxvoid Feb 26, 2021
93c0429
Use net50 references instead netstandard20 in tests
psxvoid Feb 26, 2021
3efee13
Replace IsJsonCtor extension-method with private method
psxvoid Feb 26, 2021
2b49119
Rename the analyzer - add "field" in its name
psxvoid Feb 26, 2021
c2051bf
Remove unused using in tests
psxvoid Feb 26, 2021
e9aa041
Replace banned-API with non-banned in tests
psxvoid Feb 27, 2021
9579d93
Add a code fix provider, partially tested with C#
psxvoid Feb 27, 2021
5a62547
[POC] Verify tuple assignment is working with CA1071
psxvoid Mar 1, 2021
33dccdd
Revert "[POC] Verify tuple assignment is working with CA1071"
psxvoid Mar 1, 2021
57437ba
Add C# tests for classes with fields and props
psxvoid Mar 1, 2021
727423b
Add VB tests for classes with fields and props
psxvoid Mar 1, 2021
6c83c6e
The analyzer - ensure bound properties are public
psxvoid Mar 3, 2021
2482c4c
Fix minor dis-alignment in tests
psxvoid Mar 3, 2021
d4bec8b
Fix tuple assignment reference not detected
psxvoid Mar 4, 2021
9089e0a
Simplify member reference retrieval
psxvoid Mar 4, 2021
5aff394
Merge reference symbol null and static checks
psxvoid Mar 4, 2021
5fba883
Rename local variable in member reference retrival
psxvoid Mar 4, 2021
cd373f6
Add tuple assignment test for records
psxvoid Mar 4, 2021
1fb8c27
Analyzer - group tests for records below tests for classes
psxvoid Mar 7, 2021
da07af5
Analyzer - sort tests for classes
psxvoid Mar 7, 2021
0c3e2a9
Analyzer - ensure bound fields are public
psxvoid Mar 7, 2021
2309cb0
Fix parameter alignment in analyzer tests
psxvoid Mar 7, 2021
7285658
Analyzer - add tests for private fields with JsonInclude
psxvoid Mar 8, 2021
52dce8e
Analyzer - add tests with JsonPropertyName
psxvoid Mar 8, 2021
c603b66
Analyzer - report unreferenced params (no messages)
psxvoid Mar 8, 2021
afb760a
Analyzer - add messages for unreferenced parameters
psxvoid Mar 8, 2021
4d30c8f
Analyzer - free unused parameter set on symbol-end
psxvoid Mar 8, 2021
641721d
Analyzer - sort methods by visibility and logically
psxvoid Mar 8, 2021
65ea1ad
Analyzer - fix reversed words match (should not)
psxvoid Mar 9, 2021
defb3c2
Analyzer - add regions, cleanup and organize tests
psxvoid Mar 9, 2021
d4345d5
Analyzer - simplify return in ShouldAnalyzerMethod
psxvoid Mar 9, 2021
2008be6
Analyzer - simplify supported members matching
psxvoid Mar 9, 2021
c0687c6
Add TODO - referenced members are not static diagnostic
psxvoid Mar 9, 2021
313bdfd
Analyzer - format compilation start action registration
psxvoid Mar 9, 2021
fa0fd41
Analyzer - merge name match logic into a single method
psxvoid Mar 9, 2021
4d4a1ec
Add "key" postfix to diagnostic parameter keys
psxvoid Mar 9, 2021
e059159
Analyzer - remove unused parameter analysis
psxvoid Jun 23, 2021
a95f803
Analyzer - init using scoped context variable
psxvoid Jun 23, 2021
1e62744
Analyzer - fix build: invalid whitespace in tests
psxvoid Jun 23, 2021
1e1d382
Analyzer - inline ExportFixProvider and Shared attributes
psxvoid Jun 28, 2021
9a727e4
Revert "Analyzer - inline ExportFixProvider and Shared attributes"
psxvoid Jun 28, 2021
8dacfcb
Fixer - inline ExportFixProvider and Shared attributes (fixed)
psxvoid Jun 28, 2021
ab492ca
Analyzer - use single diagnostic rule per analyzer
psxvoid Jun 28, 2021
b1deb98
CodeFix - make bound field public (no message, not aligned)
psxvoid Jul 8, 2021
2008bfe
Analyzer - remove unreferenced parameter diagnostic reason
psxvoid Jul 8, 2021
194c3a2
Analyzer - fix broken tests due to additional diagnostic location
psxvoid Jul 13, 2021
a487621
CodeFix - add tests for single private property
psxvoid Jul 13, 2021
f38390d
CodeFix - add tests for multiple private fields and props
psxvoid Jul 13, 2021
051d067
Analyzer - use "Add" instead of "SetItem"
psxvoid Jul 27, 2021
f86bf43
Analyzer - init jsonConstructorAttribute symbol using out var
psxvoid Jul 27, 2021
35b2346
Analyzer - Use ContainingType.Name instead of ToDisplayString
psxvoid Jul 27, 2021
aafe0de
Analyzer - Remove parentheses from lambda param while passing context
psxvoid Jul 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
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
Expand Up @@ -196,6 +196,7 @@ private bool ShouldAnalyzeMethod(
}

// Ignore primary constructor (body-less) of positional records.
// TODO: I have to handle a similar situation
Copy link
Author

Choose a reason for hiding this comment

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

To Do: remove

if (IsPositionalRecordPrimaryConstructor(method))
{
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema

Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple

There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -1519,4 +1519,13 @@
<value>and all other platforms</value>
<comment>This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms</comment>
</data>
<data name="ConstructorParameterShouldMatchPropertyOrFieldName" xml:space="preserve">
<value>For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}'</value>
</data>
<data name="ConstructorParametersShouldMatchPropertyOrFieldNamesDescription" xml:space="preserve">
<value>For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization.</value>
</data>
<data name="ConstructorParametersShouldMatchPropertyOrFieldNamesTitle" xml:space="preserve">
<value>Constructor parameter names should match the bound property or field names</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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.Composition;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines;

namespace Microsoft.NetCore.Analyzers.Runtime
{
/// <summary>
/// CA1071: Constructor parameters should match property and field names.
/// Based on <see cref="ParameterNamesShouldMatchBaseDeclarationFixer"/>.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
public sealed class ConstructorParametersShouldMatchPropertyAndFieldNamesFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer.RuleId);

public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);

foreach (Diagnostic diagnostic in context.Diagnostics)
{
SyntaxNode node = syntaxRoot.FindNode(context.Span);
ISymbol declaredSymbol = semanticModel.GetDeclaredSymbol(node, context.CancellationToken);

if (declaredSymbol.Kind != SymbolKind.Parameter)
{
continue;
}

// This approach is very naive. Most likely we want to support NamingStyleOptions, available in Roslyn.
Copy link
Author

Choose a reason for hiding this comment

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

To Do: remove

string newName = LowerFirstLetter(diagnostic.Properties[ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer.ReferencedFieldOrPropertyNameKey]);

context.RegisterCodeFix(
CodeAction.Create(
string.Format(CultureInfo.CurrentCulture, MicrosoftNetCoreAnalyzersResources.ConstructorParametersShouldMatchPropertyOrFieldNamesTitle, newName),
cancellationToken => GetUpdatedDocumentForParameterRenameAsync(context.Document, declaredSymbol, newName, cancellationToken),
nameof(ConstructorParametersShouldMatchPropertyAndFieldNamesFixer)),
diagnostic);
}
}

private static string LowerFirstLetter(string targetName)
{
return $"{targetName[0].ToString().ToLower(CultureInfo.CurrentCulture)}{targetName[1..]}";
}

private static async Task<Document> GetUpdatedDocumentForParameterRenameAsync(Document document, ISymbol parameter, string newName, CancellationToken cancellationToken)
Copy link
Author

Choose a reason for hiding this comment

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

Because it duplicates a method of ParameterNamesShouldMatchBaseDeclarationFixer, it can be extracted to a shared internal helper method. But I'm not completely sure if it follows repository contribution guidelines (do not make changes to unrelated analyzers).

{
Solution newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, parameter, newName, null, cancellationToken).ConfigureAwait(false);
return newSolution.GetDocument(document.Id)!;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// 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;
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 ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer : DiagnosticAnalyzer
{
internal const string RuleId = "CA1071";

internal const string ReferencedFieldOrPropertyNameKey = "ReferencedPropertyOrFieldName";
internal const string DiagnosticReasonKey = "DiagnosticReason";

private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ConstructorParametersShouldMatchPropertyOrFieldNamesTitle), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));

private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ConstructorParameterShouldMatchPropertyOrFieldName), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));
private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ConstructorParametersShouldMatchPropertyOrFieldNamesDescription), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));

internal static DiagnosticDescriptor PropertyOrFieldNameRule = DiagnosticDescriptorHelper.Create(RuleId,
s_localizableTitle,
s_localizableMessage,
DiagnosticCategory.Design,
RuleLevel.BuildWarning,
description: s_localizableDescription,
isPortedFxCopRule: false,
isDataflowRule: false);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(PropertyOrFieldNameRule);

public override void Initialize(AnalysisContext context)
{
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.

{
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.

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.

{
return;
}

var paramAnalyzer = new ParameterAnalyzer(jsonConstructorAttributeNamedSymbol);
Copy link
Member

Choose a reason for hiding this comment

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

Is the initialization is necessary? seems all references/methods of the this type can be static

Copy link
Author

Choose a reason for hiding this comment

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

It's not necessary, but it may improve the readability. Because without it we have to pass json constructor attribute to two methods:

public static bool ShouldAnalyzeMethod(IMethodSymbol method, INamedTypeSymbol jsonConstructorAttribute)
{
    // We only care about constructors with parameters.
    if (method.Parameters.IsEmpty)
    {
        return false;
    }

    // We only care about constructors that are marked with JsonConstructor attribute.
    return ParameterAnalyzer.IsJsonConstructor(method, jsonConstructorAttribute);
}

It means developers should figure out where to get jsonConstructorAttribute if they have to deal with those methods. And it may not be obvious why those methods require it. Where providing jsonConstructorAttribute during the class initialization follows IoC principle (constructor injection) and also simplifies both methods. IMHO. And it doesn't seem to have a performance impact here, but I may miss something.

Anyway, if you still insist, I've prepared a commit with those changes and can apply it at any time.

Copy link
Author

Choose a reason for hiding this comment

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


context.RegisterSymbolStartAction((context) =>
{
var constructors = ((INamedTypeSymbol)context.Symbol).InstanceConstructors;

foreach (var ctor in constructors)
{
if (paramAnalyzer.ShouldAnalyzeMethod(ctor))
{
context.RegisterOperationAction(
context => ParameterAnalyzer.AnalyzeOperationAndReport(context),
OperationKind.ParameterReference);
}
}
}, SymbolKind.NamedType);
});
}

internal enum ParameterDiagnosticReason
{
NameMismatch,
PropertyInappropriateVisibility,
FieldInappropriateVisibility,
UnreferencedParameter,
}

private sealed class ParameterAnalyzer
{
private readonly INamedTypeSymbol _jsonConstructorAttributeInfoType;

public ParameterAnalyzer(INamedTypeSymbol jsonConstructorAttributeInfoType)
{
_jsonConstructorAttributeInfoType = jsonConstructorAttributeInfoType;
}

public static void AnalyzeOperationAndReport(OperationAnalysisContext context)
{
var operation = (IParameterReferenceOperation)context.Operation;

IMemberReferenceOperation? memberReferenceOperation = TryGetMemberReferenceOperation(operation);
ISymbol? referencedSymbol = memberReferenceOperation?.GetReferencedMemberOrLocalOrParameter();

// TODO: convert "IsStatic" to a separate diagnostic
if (referencedSymbol == null || referencedSymbol.IsStatic)
{
return;
}

IParameterSymbol param = operation.Parameter;

if (referencedSymbol is IFieldSymbol field)
{
if (!IsParamMatchesReferencedMemberName(param, field))
{
ReportFieldDiagnostic(context, PropertyOrFieldNameRule, ParameterDiagnosticReason.NameMismatch, param, field);
}

if (!field.IsPublic())
{
ReportFieldDiagnostic(context, PropertyOrFieldNameRule, ParameterDiagnosticReason.FieldInappropriateVisibility, param, field);
}
}
else if (referencedSymbol is IPropertySymbol prop)
{
if (!IsParamMatchesReferencedMemberName(param, prop))
{
ReportPropertyDiagnostic(context, PropertyOrFieldNameRule, ParameterDiagnosticReason.NameMismatch, param, prop);
}

if (!prop.IsPublic())
{
ReportPropertyDiagnostic(context, PropertyOrFieldNameRule, ParameterDiagnosticReason.PropertyInappropriateVisibility, param, prop);
}
}
}

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.
return this.IsJsonConstructor(method);
}

private static bool IsParamMatchesReferencedMemberName(IParameterSymbol param, ISymbol referencedMember)
{
if (param.Name.Length != referencedMember.Name.Length)
{
return false;
}

var paramWords = WordParser.Parse(param.Name, WordParserOptions.SplitCompoundWords);
var memberWords = WordParser.Parse(referencedMember.Name, WordParserOptions.SplitCompoundWords);

return paramWords.SequenceEqual(memberWords, StringComparer.OrdinalIgnoreCase);
}

private bool IsJsonConstructor([NotNullWhen(returnValue: true)] IMethodSymbol? method)
=> method.IsConstructor() &&
method.HasAttribute(this._jsonConstructorAttributeInfoType);

private static IMemberReferenceOperation? TryGetMemberReferenceOperation(IParameterReferenceOperation paramOperation)
{
if (paramOperation.Parent is IAssignmentOperation assignmentOperation
&& assignmentOperation.Target is IMemberReferenceOperation assignmentTarget)
{
return assignmentTarget;
}

if (paramOperation.Parent is ITupleOperation sourceTuple
&& sourceTuple.Parent is IConversionOperation conversion
&& conversion.Parent is IDeconstructionAssignmentOperation deconstruction
&& deconstruction.Target is ITupleOperation targetTuple)
{
var paramIndexInTuple = sourceTuple.Elements.IndexOf(paramOperation);

return targetTuple.Elements[paramIndexInTuple] as IMemberReferenceOperation;
}

return null;
}

private static void ReportFieldDiagnostic(OperationAnalysisContext context, DiagnosticDescriptor diagnosticDescriptor, ParameterDiagnosticReason reason, IParameterSymbol param, IFieldSymbol field)
{
var properties = ImmutableDictionary<string, string?>.Empty
.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.


context.ReportDiagnostic(
param.CreateDiagnostic(
diagnosticDescriptor,
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.

param.Name,
field.Name));
}

private static void ReportPropertyDiagnostic(OperationAnalysisContext context, DiagnosticDescriptor diagnosticDescriptor, ParameterDiagnosticReason reason, IParameterSymbol param, IPropertySymbol prop)
{
var properties = ImmutableDictionary<string, string?>.Empty
.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.


context.ReportDiagnostic(
param.CreateDiagnostic(
diagnosticDescriptor,
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.

param.Name,
prop.Name));
}
}
}
}
Loading