diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index cdf4f1397e..6d655dfe18 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -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) \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs index 3174fdf251..ae99dd9445 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/ReviewUnusedParameters.cs @@ -196,6 +196,7 @@ private bool ShouldAnalyzeMethod( } // Ignore primary constructor (body-less) of positional records. + // TODO: I have to handle a similar situation if (IsPositionalRecordPrimaryConstructor(method)) { return false; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index e240378b84..2003b16d95 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1,17 +1,17 @@  - @@ -1519,4 +1519,13 @@ and all other platforms This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + Constructor parameter names should match the bound property or field names + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNames.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNames.Fixer.cs new file mode 100644 index 0000000000..3602ea5418 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNames.Fixer.cs @@ -0,0 +1,112 @@ +// 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.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.Editing; +using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines; +using static Microsoft.NetCore.Analyzers.Runtime.ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + /// + /// CA1071: Constructor parameters should match property and field names. + /// Based on . + /// + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class ConstructorParametersShouldMatchPropertyAndFieldNamesFixer : CodeFixProvider + { + private static readonly Type DiagnosticReasonEnumType = typeof(ParameterDiagnosticReason); + + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(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; + } + + var diagnosticReason = (ParameterDiagnosticReason)Enum.Parse(DiagnosticReasonEnumType, diagnostic.Properties[DiagnosticReasonKey]); + + switch (diagnosticReason) + { + case ParameterDiagnosticReason.NameMismatch: + RegisterParameterRenameCodeFix(context, diagnostic, declaredSymbol); + break; + case ParameterDiagnosticReason.FieldInappropriateVisibility: + case ParameterDiagnosticReason.PropertyInappropriateVisibility: + SyntaxNode fieldOrProperty = syntaxRoot.FindNode(diagnostic.AdditionalLocations[0].SourceSpan); + RegisterMakeFieldOrPropertyPublicCodeFix(context, diagnostic, fieldOrProperty); + break; + default: + throw new InvalidOperationException(); + }; + } + } + + private static void RegisterParameterRenameCodeFix(CodeFixContext context, Diagnostic diagnostic, ISymbol declaredSymbol) + { + // This approach is very naive. Most likely we want to support NamingStyleOptions, available in Roslyn. + string newName = LowerFirstLetter(diagnostic.Properties[ReferencedFieldOrPropertyNameKey]); + + context.RegisterCodeFix( + CodeAction.Create( + string.Format(CultureInfo.CurrentCulture, MicrosoftNetCoreAnalyzersResources.ConstructorParametersShouldMatchPropertyOrFieldNamesTitle, newName), + cancellationToken => GetUpdatedDocumentForParameterRenameAsync(context.Document, declaredSymbol, newName, cancellationToken), + nameof(ConstructorParametersShouldMatchPropertyAndFieldNamesFixer)), + diagnostic); + } + + private static void RegisterMakeFieldOrPropertyPublicCodeFix(CodeFixContext context, Diagnostic diagnostic, SyntaxNode fieldOrProperty) + { + context.RegisterCodeFix( + CodeAction.Create( + string.Format(CultureInfo.CurrentCulture, MicrosoftNetCoreAnalyzersResources.ConstructorParametersShouldMatchPropertyOrFieldNamesTitle, fieldOrProperty), + cancellationToken => GetUpdatedDocumentForMakingFieldOrPropertyPublicAsync(context.Document, fieldOrProperty, cancellationToken), + nameof(ConstructorParametersShouldMatchPropertyAndFieldNamesFixer)), + diagnostic); + } + + private static string LowerFirstLetter(string targetName) + { + return $"{targetName[0].ToString().ToLower(CultureInfo.CurrentCulture)}{targetName[1..]}"; + } + + private static async Task GetUpdatedDocumentForParameterRenameAsync(Document document, ISymbol parameter, string newName, CancellationToken cancellationToken) + { + Solution newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, parameter, newName, null, cancellationToken).ConfigureAwait(false); + return newSolution.GetDocument(document.Id)!; + } + + private static async Task GetUpdatedDocumentForMakingFieldOrPropertyPublicAsync(Document document, SyntaxNode fieldOrProperty, CancellationToken cancellationToken) + { + DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + editor.SetAccessibility(fieldOrProperty, Accessibility.Public); + + return editor.GetChangedDocument(); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNames.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNames.cs new file mode 100644 index 0000000000..9e7155e4f1 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNames.cs @@ -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 +{ + /// + /// CA1071: Constructor parameters should match property and field names + /// + [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 SupportedDiagnostics => ImmutableArray.Create(PropertyOrFieldNameRule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(context => + { + if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonSerializationJsonConstructorAttribute, out var jsonConstructorAttributeNamedSymbol)) + { + return; + } + + var paramAnalyzer = new ParameterAnalyzer(jsonConstructorAttributeNamedSymbol); + + 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, + } + + 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.Empty + .Add(ReferencedFieldOrPropertyNameKey, field.Name) + .Add(DiagnosticReasonKey, reason.ToString()); + + context.ReportDiagnostic( + param.Locations[0].CreateDiagnostic( + diagnosticDescriptor, + reason == ParameterDiagnosticReason.FieldInappropriateVisibility ? field.Locations : ImmutableArray.Empty, + properties, + param.ContainingType.Name, + param.Name, + field.Name)); + } + + private static void ReportPropertyDiagnostic(OperationAnalysisContext context, DiagnosticDescriptor diagnosticDescriptor, ParameterDiagnosticReason reason, IParameterSymbol param, IPropertySymbol prop) + { + var properties = ImmutableDictionary.Empty + .Add(ReferencedFieldOrPropertyNameKey, prop.Name) + .Add(DiagnosticReasonKey, reason.ToString()); + + context.ReportDiagnostic( + param.Locations[0].CreateDiagnostic( + diagnosticDescriptor, + reason == ParameterDiagnosticReason.PropertyInappropriateVisibility ? prop.Locations : ImmutableArray.Empty, + properties, + param.ContainingType.Name, + param.Name, + prop.Name)); + } + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index be063a9c1a..2cde552336 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Při deserializaci nedůvěryhodného vstupu není deserializace objektu {0} bezpečná. Objekt {1} je buď objektem {0}, nebo je z tohoto objektu odvozený. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 52ca3a058f..4bc070dbe5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Beim Deserialisieren einer nicht vertrauenswürdigen Eingabe ist die Deserialisierung eines {0}-Objekts unsicher. "{1}" ist entweder "{0}" oder davon abgeleitet. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 18668ee783..b3e21020e9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Cuando se deserializa una entrada que no es de confianza, no es segura la deserialización de un objeto {0}. "{1}" es {0} o se deriva de este. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index d90f9ec3af..e8bbcce859 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Quand vous désérialisez une entrée non fiable, la désérialisation d'un objet {0} n'est pas une action sécurisée. '{1}' est égal à ou dérive de {0} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 09c16a9f6e..f98f093036 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Quando si deserializza input non attendibile, la deserializzazione di un oggetto {0} non è sicura. '{1}' è {0} o deriva da esso diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 6937c685a2..428f07cd4d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 信頼されていない入力を逆シリアル化する場合、{0} オブジェクトの逆シリアル化は安全ではありません。'{1}' は {0} であるか、それから派生しています diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index d329428df3..8027b0b925 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 신뢰할 수 없는 입력을 역직렬화하는 경우 {0} 개체를 역직렬화하는 것은 안전하지 않습니다. '{1}'은(는) {0}이거나 이 항목에서 파생됩니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0131fbd52a..f50abdcfc3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Deserializowanie obiektu {0} podczas deserializacji niezaufanych danych wejściowych nie jest bezpieczne. Element „{1}” jest elementem {0} lub pochodzi od niego diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 9e63b6fc06..0aaf4f3c97 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Ao desserializar uma entrada não confiável, a desserialização de um objeto {0} não é segura. '{1}' é {0} ou deriva-se dele diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 6dbafe9c23..052bd8cd4b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} При десериализации недоверенных входных данных десериализация объекта {0} является небезопасной. Объект "{1}" является объектом {0} или производным от него объектом. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index c4dd09e3bd..271827a4fa 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Güvenilmeyen giriş seri durumdan çıkarılırken, {0} nesnesinin seri durumdan çıkarılması güvenli değildir. '{1}', {0} nesnesidir veya bu nesneden türetilmiştir diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 1a161cab86..a8e4219a60 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 对不受信任的输入进行反序列化处理时,反序列化 {0} 对象是不安全的。“{1}”是或派生自 {0} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 7bd5957db2..29fbf8801c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -177,6 +177,21 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + For proper deserialization, change the name of parameter '{1}' in the constructor of '{0}' to match the bound property or field '{2}' + + + + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + + + + Constructor parameter names should match the bound property or field names + Constructor parameter names should match the bound property or field names + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 還原序列化不受信任的輸入時,將 {0} 物件還原序列化並不安全。'{1}' 屬於或衍生自 {0} diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 1dff4237dc..18aff4b97d 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -576,6 +576,18 @@ Do not declare virtual events in a base class. Overridden events in a derived cl |CodeFix|False| --- +## [CA1071](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1071): Constructor parameter names should match the bound property or field names + +For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization. + +|Item|Value| +|-|-| +|Category|Design| +|Enabled|True| +|Severity|Warning| +|CodeFix|True| +--- + ## [CA1200](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1200): Avoid using cref tags with a prefix Use of cref tags with prefixes should be avoided, since it prevents the compiler from verifying references and the IDE from updating references during refactorings. It is permissible to suppress this error at a single documentation site if the cref must use a prefix because the type being mentioned is not findable by the compiler. For example, if a cref is mentioning a special attribute in the full framework but you're in a file that compiles against the portable framework, or if you want to reference a type at higher layer of Roslyn, you should suppress the error. You should not suppress the error just because you want to take a shortcut and avoid using the full syntax. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 17057589dd..550df5b6ee 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -1346,6 +1346,26 @@ ] } }, + "CA1071": { + "id": "CA1071", + "shortDescription": "Constructor parameter names should match the bound property or field names", + "fullDescription": "For a constructor having [JsonConstructor] attribute each parameter name must match with a public property or field name for proper deserialization.", + "defaultLevel": "warning", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1071", + "properties": { + "category": "Design", + "isEnabledByDefault": true, + "typeName": "ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA1303": { "id": "CA1303", "shortDescription": "Do not pass literals as localized parameters", diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNamesTests.Fixer.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNamesTests.Fixer.cs new file mode 100644 index 0000000000..48b2aadf8d --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNamesTests.Fixer.cs @@ -0,0 +1,590 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer, + Microsoft.NetCore.Analyzers.Runtime.ConstructorParametersShouldMatchPropertyAndFieldNamesFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer, + Microsoft.NetCore.Analyzers.Runtime.ConstructorParametersShouldMatchPropertyAndFieldNamesFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class ConstructorParametersShouldMatchPropertyAndFieldNamesFixerTests + { + [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; + } + }"); + } + + [Fact] + public async Task CA1071_ClassSinglePropDoesNotMatch_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Property FirstProp() As Integer + Property SecondProp() as Object + + + Public Sub New([|firstDrop|] as Integer, secondProp as Object) + Me.FirstProp = firstDrop + Me.SecondProp = secondProp + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Property FirstProp() As Integer + Property SecondProp() as Object + + + Public Sub New(firstProp as Integer, secondProp as Object) + Me.FirstProp = firstProp + Me.SecondProp = secondProp + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatch_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 [|secondDrop|]) + { + this.FirstProp = firstDrop; + this.SecondProp = secondDrop; + } + }", + @" + 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; + } + }"); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatch_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Property FirstProp() As Integer + Property SecondProp() as Object + + + Public Sub New([|firstDrop|] as Integer, [|secondDrop|] as Object) + Me.FirstProp = firstDrop + Me.SecondProp = secondDrop + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Property FirstProp() As Integer + Property SecondProp() as Object + + + Public Sub New(firstProp as Integer, secondProp as Object) + Me.FirstProp = firstProp + Me.SecondProp = secondProp + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassSingleFieldDoesNotMatch_CSharp() + { + await VerifyCSharpCodeFixAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField { get; } + + public object secondField { get; } + + [JsonConstructor] + public C1(int [|firstIField|], object secondField) + { + this.firstField = firstIField; + this.secondField = secondField; + } + }", + @" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField { get; } + + public object secondField { get; } + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassSingleFieldDoesNotMatch_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField As Integer + Public secondField as Object + + + Public Sub New([|firstIField|] as Integer, secondField as Object) + Me.firstField = firstIField + Me.secondField = secondField + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField As Integer + Public secondField as Object + + + Public Sub New(firstField as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatch_CSharp() + { + await VerifyCSharpCodeFixAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField { get; } + + public object secondField { get; } + + [JsonConstructor] + public C1(int [|firstIField|], object [|secondIField|]) + { + this.firstField = firstIField; + this.secondField = secondIField; + } + }", + @" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField { get; } + + public object secondField { get; } + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatch_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField As Integer + Public secondField as Object + + + Public Sub New([|firstIField|] as Integer, [|secondIField|] as Object) + Me.firstField = firstIField + Me.secondField = secondIField + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField As Integer + Public secondField as Object + + + Public Sub New(firstField as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassSingleFieldPrivate_CSharp() + { + await VerifyCSharpCodeFixAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + private int firstField { get; } + + public object secondField { get; } + + [JsonConstructor] + public C1(int [|firstField|], object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }", + @" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField { get; } + + public object secondField { get; } + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassSingleFieldPrivate_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Private firstField As Integer + Public secondField as Object + + + Public Sub New([|firstField|] as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField As Integer + + Public secondField as Object + + + Public Sub New(firstField as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassSinglePropertyPrivate_CSharp() + { + await VerifyCSharpCodeFixAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + private int FirstProp { get; } + + public object SecondProp { get; } + + [JsonConstructor] + public C1(int [|firstProp|], object secondProp) + { + this.FirstProp = firstProp; + 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; + } + }"); + } + + [Fact] + public async Task CA1071_ClassSinglePropertyPrivate_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Private Property FirstProp() As Integer + Property SecondProp() as Object + + + Public Sub New([|firstProp|] as Integer, secondProp as Object) + Me.FirstProp = firstProp + Me.SecondProp = secondProp + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Public Property FirstProp() As Integer + Property SecondProp() as Object + + + Public Sub New(firstProp as Integer, secondProp as Object) + Me.FirstProp = firstProp + Me.SecondProp = secondProp + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassMultipleFieldsPrivate_CSharp() + { + await VerifyCSharpCodeFixAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + private int firstField { get; } + + private object secondField { get; } + + [JsonConstructor] + public C1(int [|firstField|], object [|secondField|]) + { + this.firstField = firstField; + this.secondField = secondField; + } + }", + @" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField { get; } + + public object secondField { get; } + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassMultipleFieldsPrivate_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Private firstField As Integer + Private secondField as Object + + + Public Sub New([|firstField|] as Integer, [|secondField|] as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField As Integer + + Public secondField as Object + + + + Public Sub New(firstField as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassMultiplePropertiesPrivate_CSharp() + { + await VerifyCSharpCodeFixAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + private int FirstProp { get; } + + private object SecondProp { get; } + + [JsonConstructor] + public C1(int [|firstProp|], object [|secondProp|]) + { + this.FirstProp = firstProp; + 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; + } + }"); + } + + [Fact] + public async Task CA1071_ClassMultiplePropertiesPrivate_Basic() + { + await VerifyBasicCodeFixAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Private Property FirstProp() As Integer + Private Property SecondProp() as Object + + + Public Sub New([|firstProp|] as Integer, [|secondProp|] as Object) + Me.FirstProp = firstProp + Me.SecondProp = secondProp + End Sub + End Class", + @" + Imports System.Text.Json.Serialization + + Public Class C1 + Public Property FirstProp() As Integer + Public Property SecondProp() as Object + + + Public Sub New(firstProp as Integer, secondProp as Object) + Me.FirstProp = firstProp + Me.SecondProp = secondProp + End Sub + End Class"); + } + + private static async Task VerifyCSharpCodeFixAsync(string source, string expected) + { + var csharpTest = new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = source, + FixedCode = expected, + MarkupOptions = MarkupOptions.UseFirstDescriptor + }; + + await csharpTest.RunAsync(); + } + + private static async Task VerifyBasicCodeFixAsync(string source, string expected) + { + var basicTest = new VerifyVB.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = source, + FixedCode = expected, + MarkupOptions = MarkupOptions.UseFirstDescriptor + }; + + await basicTest.RunAsync(); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNamesTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNamesTests.cs new file mode 100644 index 0000000000..5abac3f171 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ConstructorParametersShouldMatchPropertyAndFieldNamesTests.cs @@ -0,0 +1,1139 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class ConstructorParametersShouldMatchPropertyAndFieldNamesTests + { + #region Class Props Do Not Match Referenced Parameter Names + + [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")); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatch_ConstructorParametersShouldMatchPropertyNames_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public Property FirstProp() As Integer + Public Property SecondProp() as Object + + + Public Sub New({|#0:firstDrop|} as Integer, {|#1:secondDrop|} as Object) + Me.FirstProp = firstDrop + Me.SecondProp = secondDrop + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, "C1", "firstDrop", "FirstProp"), + CA1071BasicPropertyOrFieldResultAt(1, "C1", "secondDrop", "SecondProp")); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatchNotJsonCtor_NoDiagnostics_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + public class C1 + { + public int FirstProp { get; } + public object SecondProp { get; } + + public C1(int firstDrop, object secondDrop) + { + this.FirstProp = firstDrop; + this.SecondProp = secondDrop; + } + }"); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatchNotJsonCtor_NoDiagnostics_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Public Class C1 + Public Property firstProp() As Integer + Public Property secondProp() as Object + + Public Sub New(firstDrop as Integer, secondDrop as Object) + Me.firstProp = firstDrop + Me.secondProp = secondDrop + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatchReversedWords_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:dropFirst|}, object {|#1:dropSecond|}) + { + this.FirstProp = dropFirst; + this.SecondProp = dropSecond; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "dropFirst", "FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "dropSecond", "SecondProp")); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatchReversedWords_ConstructorParametersShouldMatchPropertyNames_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public Property FirstProp() As Integer + Public Property SecondProp() as Object + + + Public Sub New({|#0:dropFirst|} as Integer, {|#1:dropSecond|} as Object) + Me.FirstProp = dropFirst + Me.SecondProp = dropSecond + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, "C1", "dropFirst", "FirstProp"), + CA1071BasicPropertyOrFieldResultAt(1, "C1", "dropSecond", "SecondProp")); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatchAndTupleAssignment_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:firstProp|}, object {|#1:secondProp|}) + { + (this._FirstProp, this._SecondProp) = (firstProp, secondProp); + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstProp", "_FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondProp", "_SecondProp")); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatchButMatchWithJsonPropertyName_ConstructorParametersShouldMatchPropertyNames_CSharp() + { + // This is the current behavior on deserialization - JsonPropertyName is ignored by the JsonConstructor's logic. + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + [JsonPropertyName(""FirstProp"")] + public int _FirstProp { get; } + + [JsonPropertyName(""SecondProp"")] + public object _SecondProp { get; } + + [JsonConstructor] + public C1(int {|#0:firstProp|}, object {|#1:secondProp|}) + { + this._FirstProp = firstProp; + this._SecondProp = secondProp; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstProp", "_FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondProp", "_SecondProp")); + } + + [Fact] + public async Task CA1071_ClassPropsDoNotMatchButMatchWithJsonPropertyName_ConstructorParametersShouldMatchPropertyNames_Basic() + { + // This is the current behavior on deserialization - JsonPropertyName is ignored by the JsonConstructor's logic. + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + + Public Property _FirstProp() As Integer + + + Public Property _SecondProp() as Object + + + Public Sub New({|#0:firstProp|} as Integer, {|#1:secondProp|} as Object) + Me._FirstProp = firstProp + Me._SecondProp = secondProp + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, "C1", "firstProp", "_FirstProp"), + CA1071BasicPropertyOrFieldResultAt(1, "C1", "secondProp", "_SecondProp")); + } + + #endregion + + #region Class Props Match Referenced Parameter Names + + [Fact] + public async Task CA1071_ClassPropsMatch_NoDiagnostics_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + 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; + } + }"); + } + + [Fact] + public async Task CA1071_ClassPropsMatch_NoDiagnostics_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public Property firstProp() As Integer + Public Property secondProp() as Object + + + Public Sub New(firstProp as Integer, secondProp as Object) + Me.firstProp = firstProp + Me.secondProp = secondProp + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassPropsMatchButPrivate_ConstructorParametersShouldShouldMatchPublicProperties_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + private int {|#1:FirstProp|} { get; } + private object {|#3:SecondProp|} { get; } + + [JsonConstructor] + public C1(int {|#0:firstProp|}, object {|#2:secondProp|}) + { + this.FirstProp = firstProp; + this.SecondProp = secondProp; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, 1, "C1", "firstProp", "FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(2, 3, "C1", "secondProp", "SecondProp")); + } + + [Fact] + public async Task CA1071_ClassPropsMatchButPrivate_ConstructorParametersShouldMatchPublicProperties_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Private Property {|#1:FirstProp|}() As Integer + Private Property {|#3:SecondProp|}() as Object + + + Public Sub New({|#0:firstProp|} as Integer, {|#2:secondProp|} as Object) + Me.FirstProp = firstProp + Me.SecondProp = secondProp + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, 1, "C1", "firstProp", "FirstProp"), + CA1071BasicPropertyOrFieldResultAt(2, 3, "C1", "secondProp", "SecondProp")); + } + + #endregion + + #region Class Fields Do Not Match Referenced Parameter Names + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatch_ConstructorParametersShouldMatchFieldNames_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField; + public object secondField; + + [JsonConstructor] + public C1(int {|#0:firstIField|}, object {|#1:secondIField|}) + { + this.firstField = firstIField; + this.secondField = secondIField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstIField", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondIField", "secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatch_ConstructorParametersShouldMatchFieldNames_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField as Integer + Public secondField as Object + + + Public Sub New({|#0:firstIField|} as Integer, {|#1:secondIField|} as Object) + Me.firstField = firstIField + Me.secondField = secondIField + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, "C1", "firstIField", "firstField"), + CA1071BasicPropertyOrFieldResultAt(1, "C1", "secondIField", "secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatchNotJsonCtor_NoDiagnostics_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + public class C1 + { + public int firstField; + public object secondField; + + public C1(int firstIField, object secondIField) + { + this.firstField = firstIField; + this.secondField = secondIField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatchNotJsonCtor_NoDiagnostics_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Public Class C1 + Public firstField as Integer + Public secondField as Object + + Public Sub New(firstIField as Integer, secondIField as Object) + Me.firstField = firstIField + Me.secondField = secondIField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatchReversedWords_ConstructorParametersShouldMatchFieldNames_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField; + public object secondField; + + [JsonConstructor] + public C1(int {|#0:fieldFirst|}, object {|#1:fieldSecond|}) + { + this.firstField = fieldFirst; + this.secondField = fieldSecond; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "fieldFirst", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "fieldSecond", "secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatchReversedWords_ConstructorParametersShouldMatchFieldNames_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField as Integer + Public secondField as Object + + + Public Sub New({|#0:fieldFirst|} as Integer, {|#1:fieldSecond|} as Object) + Me.firstField = fieldFirst + Me.secondField = fieldSecond + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, "C1", "fieldFirst", "firstField"), + CA1071BasicPropertyOrFieldResultAt(1, "C1", "fieldSecond", "secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatchAndTupleAssignment_ConstructorParametersShouldMatchFieldNames_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + public int _firstField; + public object _secondField; + + [JsonConstructor] + public C1(int {|#0:firstField|}, object {|#1:secondField|}) + { + (_firstField, _secondField) = (firstField, secondField); + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstField", "_firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondField", "_secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatchButMatchWithJsonPropertyName_ConstructorParametersShouldMatchFieldNames_CSharp() + { + // This is the current behavior on deserialization - JsonPropertyName is ignored by the JsonConstructor's logic. + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + [JsonPropertyName(""firstField"")] + public int _firstField; + + [JsonPropertyName(""secondField"")] + public object _secondField; + + [JsonConstructor] + public C1(int {|#0:firstField|}, object {|#1:secondField|}) + { + this._firstField = firstField; + this._secondField = secondField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstField", "_firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondField", "_secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsDoNotMatchButMatchWithJsonPropertyName_ConstructorParametersShouldMatchFieldNames_Basic() + { + // This is the current behavior on deserialization - JsonFieldertyName is ignored by the JsonConstructor's logic. + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + + Public _firstField As Integer + + + Public _secondField as Object + + + Public Sub New({|#0:firstField|} as Integer, {|#1:secondField|} as Object) + Me._firstField = firstField + Me._secondField = secondField + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, "C1", "firstField", "_firstField"), + CA1071BasicPropertyOrFieldResultAt(1, "C1", "secondField", "_secondField")); + } + + #endregion + + #region Class Fields Match Referenced Parameter Names + + [Fact] + public async Task CA1071_ClassFieldsMatchNoJsonInclude_NoDiagnostics_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + public int firstField; + public object secondField; + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchNoJsonInclude_NoDiagnostics_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Public firstField as Integer + Public secondField as Object + + + Public Sub New(firstField as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchHasJsonInclude_NoDiagnostics_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + [JsonInclude] + public int firstField; + + [JsonInclude] + public object secondField; + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchHasJsonInclude_NoDiagnostics_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + + Public firstField as Integer + + + Public secondField as Object + + + Public Sub New(firstField as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchButPrivateNotJsonCtor_NoDiagnostics_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + public class C1 + { + private int firstField; + private object secondField; + + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchButPrivateNotJsonCtor_NoDiagnostics_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Public Class C1 + Private firstField as Integer + Private secondField as Object + + Public Sub New(firstField as Integer, secondField as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class"); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchButPrivateNoJsonInclude_ConstructorParametersShouldMatchPublicFields_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + private int {|#1:firstField|}; + private object {|#3:secondField|}; + + [JsonConstructor] + public C1(int {|#0:firstField|}, object {|#2:secondField|}) + { + this.firstField = firstField; + this.secondField = secondField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, 1, "C1", "firstField", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(2, 3, "C1", "secondField", "secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchButPrivateNoJsonInclude_ConstructorParametersShouldMatchPublicFields_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + Private {|#1:firstField|} as Integer + Private {|#3:secondField|} as Object + + + Public Sub New({|#0:firstField|} as Integer, {|#2:secondField|} as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, 1, "C1", "firstField", "firstField"), + CA1071BasicPropertyOrFieldResultAt(2, 3, "C1", "secondField", "secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchButPrivateHasJsonInclude_ConstructorParametersShouldMatchPublicFields_CSharp() + { + await VerifyCSharpAnalyzerAsync(@" + using System.Text.Json.Serialization; + + public class C1 + { + [JsonInclude] + private int {|#1:firstField|}; + + [JsonInclude] + private object {|#3:secondField|}; + + [JsonConstructor] + public C1(int {|#0:firstField|}, object {|#2:secondField|}) + { + this.firstField = firstField; + this.secondField = secondField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, 1, "C1", "firstField", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(2, 3, "C1", "secondField", "secondField")); + } + + [Fact] + public async Task CA1071_ClassFieldsMatchButPrivateHasJsonInclude_ConstructorParametersShouldMatchPublicFields_Basic() + { + await VerifyBasicAnalyzerAsync(@" + Imports System.Text.Json.Serialization + + Public Class C1 + + Private {|#1:firstField|} as Integer + + + Private {|#3:secondField|} as Object + + + Public Sub New({|#0:firstField|} as Integer, {|#2:secondField|} as Object) + Me.firstField = firstField + Me.secondField = secondField + End Sub + End Class", + CA1071BasicPropertyOrFieldResultAt(0, 1, "C1", "firstField", "firstField"), + CA1071BasicPropertyOrFieldResultAt(2, 3, "C1", "secondField", "secondField")); + } + + #endregion + + #region Record Props Do Not Match Referenced Parameter Names + + [Fact] + public async Task CA1071_RecordPropsDoNotMatch_ConstructorParametersShouldMatchPropertyNames_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record 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")); + } + + [Fact] + public async Task CA1071_RecordPropsDoNotMatchNotJsonCtor_NoDiagnostics_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + public record C1 + { + public int FirstProp { get; } + public object SecondProp { get; } + + public C1(int firstDrop, object secondDrop) + { + this.FirstProp = firstDrop; + this.SecondProp = secondDrop; + } + }"); + } + + [Fact] + public async Task CA1071_RecordPropsDoNotMatchReversedWords_ConstructorParametersShouldMatchPropertyNames_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + public int FirstProp { get; } + public object SecondProp { get; } + + [JsonConstructor] + public C1(int {|#0:dropFirst|}, object {|#1:dropSecond|}) + { + this.FirstProp = dropFirst; + this.SecondProp = dropSecond; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "dropFirst", "FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "dropSecond", "SecondProp")); + } + + [Fact] + public async Task CA1071_RecordPropsDoNotMatchAndTupleAssignment_ConstructorParametersShouldMatchPropertyNames_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + public int FirstProp { get; } + public object SecondProp { get; } + + [JsonConstructor] + public C1(int {|#0:firstDrop|}, object {|#1:secondDrop|}) + { + (this.FirstProp, this.SecondProp) = (firstDrop, secondDrop); + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstDrop", "FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondDrop", "SecondProp")); + } + + [Fact] + public async Task CA1071_RecordPropsDoNotMatchButMatchWithJsonPropertyName_ConstructorParametersShouldMatchPropertyNames_CSharp() + { + // This is the current behavior on deserialization - JsonPropertyName is ignored by the JsonConstructor's logic. + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + [JsonPropertyName(""FirstProp"")] + public int _FirstProp { get; } + + [JsonPropertyName(""SecondProp"")] + public object _SecondProp { get; } + + [JsonConstructor] + public C1(int {|#0:firstProp|}, object {|#1:secondProp|}) + { + this._FirstProp = firstProp; + this._SecondProp = secondProp; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstProp", "_FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondProp", "_SecondProp")); + } + + #endregion + + #region Record Props Match Referenced Parameter Names + + [Fact] + public async Task CA1071_RecordPropsMatch_NoDiagnostics_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + public int FirstProp { get; } + public object SecondProp { get; } + + [JsonConstructor] + public C1(int firstProp, object secondProp) + { + this.FirstProp = firstProp; + this.SecondProp = secondProp; + } + }"); + } + + [Fact] + public async Task CA1071_RecordPropsMatchButPrivate_ConstructorParametersShouldShouldMatchPublicProperties_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + private int {|#1:FirstProp|} { get; } + private object {|#3:SecondProp|} { get; } + + [JsonConstructor] + public C1(int {|#0:firstProp|}, object {|#2:secondProp|}) + { + this.FirstProp = firstProp; + this.SecondProp = secondProp; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, 1, "C1", "firstProp", "FirstProp"), + CA1071CSharpPropertyOrFieldResultAt(2, 3, "C1", "secondProp", "SecondProp")); + } + + #endregion + + #region Record Fields Do Not Match Referenced Parameter Names + + [Fact] + public async Task CA1071_RecordFieldsDoNotMatch_ConstructorParametersShouldMatchFieldNames_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + public int firstField; + public object secondField; + + [JsonConstructor] + public C1(int {|#0:firstIField|}, object {|#1:secondIField|}) + { + this.firstField = firstIField; + this.secondField = secondIField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstIField", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondIField", "secondField")); + } + + [Fact] + public async Task CA1071_RecordFieldsDoNotMatchNotJsonCtor_NoDiagnostics_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + public record C1 + { + public int firstField; + public object secondField; + + public C1(int firstIField, object secondIField) + { + this.firstField = firstIField; + this.secondField = secondIField; + } + }"); + } + + [Fact] + public async Task CA1071_RecordFieldsDoNotMatchReversedWords_ConstructorParametersShouldMatchFieldNames_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + public int firstField; + public object secondField; + + [JsonConstructor] + public C1(int {|#0:fieldFirst|}, object {|#1:fieldSecond|}) + { + this.firstField = fieldFirst; + this.secondField = fieldSecond; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "fieldFirst", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "fieldSecond", "secondField")); + } + + [Fact] + public async Task CA1071_RecordFieldsDoNotMatchAndTupleAssignment_ConstructorParametersShouldMatchFieldNames_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + public int firstField; + public object secondField; + + [JsonConstructor] + public C1(int {|#0:firstIField|}, object {|#1:secondIField|}) + { + (this.firstField, this.secondField) = (firstIField, secondIField); + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstIField", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondIField", "secondField")); + } + + [Fact] + public async Task CA1071_RecordFieldsDoNotMatchButMatchWithJsonPropertyName_ConstructorParametersShouldMatchFieldNames_CSharp() + { + // This is the current behavior on deserialization - JsonPropertyName is ignored by the JsonConstructor's logic. + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + [JsonPropertyName(""firstField"")] + public int _firstField; + + [JsonPropertyName(""secondField"")] + public object _secondField; + + [JsonConstructor] + public C1(int {|#0:firstField|}, object {|#1:secondField|}) + { + this._firstField = firstField; + this._secondField = secondField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, "C1", "firstField", "_firstField"), + CA1071CSharpPropertyOrFieldResultAt(1, "C1", "secondField", "_secondField")); + } + + #endregion + + #region Record Fields Match Referenced Parameter Names + + [Fact] + public async Task CA1071_RecordFieldsMatchNoJsonInclude_NoDiagnostics_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + public int firstField; + public object secondField; + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_RecordFieldsMatchHasJsonInclude_NoDiagnostics_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + [JsonInclude] + public int firstField; + + [JsonInclude] + public object secondField; + + [JsonConstructor] + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_RecordFieldsMatchButPrivateNotJsonCtor_NoDiagnostics_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + public record C1 + { + private int firstField; + private object secondField; + + public C1(int firstField, object secondField) + { + this.firstField = firstField; + this.secondField = secondField; + } + }"); + } + + [Fact] + public async Task CA1071_RecordFieldsMatchButPrivateNoJsonInclude_ConstructorParametersShouldMatchPublicFields_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + private int {|#1:firstField|}; + private object {|#3:secondField|}; + + [JsonConstructor] + public C1(int {|#0:firstField|}, object {|#2:secondField|}) + { + this.firstField = firstField; + this.secondField = secondField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, 1, "C1", "firstField", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(2, 3, "C1", "secondField", "secondField")); + } + + [Fact] + public async Task CA1071_RecordFieldsMatchButPrivateHasJsonInclude_ConstructorParametersShouldMatchPublicFields_CSharp() + { + await VerifyCSharp9AnalyzerAsync(@" + using System.Text.Json.Serialization; + + public record C1 + { + [JsonInclude] + private int {|#1:firstField|}; + + [JsonInclude] + private object {|#3:secondField|}; + + [JsonConstructor] + public C1(int {|#0:firstField|}, object {|#2:secondField|}) + { + this.firstField = firstField; + this.secondField = secondField; + } + }", + CA1071CSharpPropertyOrFieldResultAt(0, 1, "C1", "firstField", "firstField"), + CA1071CSharpPropertyOrFieldResultAt(2, 3, "C1", "secondField", "secondField")); + } + + #endregion + + #region Test Helpers + + private static async Task VerifyCSharpAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var csharpTest = new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = source, + }; + + csharpTest.ExpectedDiagnostics.AddRange(expected); + + await csharpTest.RunAsync(); + } + + private static async Task VerifyCSharp9AnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var csharpTest = new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp9, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = source, + }; + + csharpTest.ExpectedDiagnostics.AddRange(expected); + + await csharpTest.RunAsync(); + } + + private static async Task VerifyBasicAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var basicTest = new VerifyVB.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = source, + }; + + basicTest.ExpectedDiagnostics.AddRange(expected); + + await basicTest.RunAsync(); + } + + private DiagnosticResult CA1071CSharpPropertyOrFieldResultAt(int markupKey, params string[] arguments) + => VerifyCS.Diagnostic(ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer.PropertyOrFieldNameRule) + .WithLocation(markupKey) + .WithArguments(arguments); + + private DiagnosticResult CA1071CSharpPropertyOrFieldResultAt(int markupKeyParam, int markupKeyFieldOrProp, params string[] arguments) + => VerifyCS.Diagnostic(ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer.PropertyOrFieldNameRule) + .WithLocation(markupKeyParam) + .WithLocation(markupKeyFieldOrProp) + .WithArguments(arguments); + + private DiagnosticResult CA1071BasicPropertyOrFieldResultAt(int markupKey, params string[] arguments) + => VerifyVB.Diagnostic(ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer.PropertyOrFieldNameRule) + .WithLocation(markupKey) + .WithArguments(arguments); + + private DiagnosticResult CA1071BasicPropertyOrFieldResultAt(int markupKey, int markupKeyFieldOrProp, params string[] arguments) + => VerifyVB.Diagnostic(ConstructorParametersShouldMatchPropertyAndFieldNamesAnalyzer.PropertyOrFieldNameRule) + .WithLocation(markupKey) + .WithLocation(markupKeyFieldOrProp) + .WithArguments(arguments); + + #endregion + } +} \ No newline at end of file diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 793ed1c657..0602d8880e 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -9,7 +9,7 @@ # 1. Choose the rule ID immediately following the range end. # 2. Update the range end to the chosen rule ID. # -Design: CA2210, CA1000-CA1070 +Design: CA2210, CA1000-CA1071 Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 Performance: HA, CA1800-CA1838 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index ca67f5eec2..a9b867e5f6 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -331,6 +331,7 @@ internal static class WellKnownTypeNames public const string SystemStringComparison = "System.StringComparison"; public const string SystemSystemException = "System.SystemException"; public const string SystemTextEncoding = "System.Text.Encoding"; + public const string SystemTextJsonSerializationJsonConstructorAttribute = "System.Text.Json.Serialization.JsonConstructorAttribute"; public const string SystemTextRegularExpressionsRegex = "System.Text.RegularExpressions.Regex"; public const string SystemTextStringBuilder = "System.Text.StringBuilder"; public const string SystemThreadingCancellationToken = "System.Threading.CancellationToken";