Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public sealed class AvoidUnderPosting : SonarDiagnosticAnalyzer
KnownType.Microsoft_AspNetCore_Http_IFormCollection,
KnownType.Microsoft_AspNetCore_Http_IFormFile,
KnownType.Microsoft_AspNetCore_Http_IFormFileCollection);
private static readonly ImmutableArray<KnownType> IgnoredAttributes = ImmutableArray.Create(
KnownType.System_Text_Json_Serialization_JsonIgnoreAttribute,
KnownType.System_Text_Json_Serialization_JsonRequiredAttribute,
KnownType.Newtonsoft_Json_JsonIgnoreAttribute,
KnownType.Newtonsoft_Json_JsonRequiredAttribute,
KnownType.System_ComponentModel_DataAnnotations_RangeAttribute);

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

Expand Down Expand Up @@ -76,13 +82,24 @@ private static void CheckInvalidProperties(INamedTypeSymbol parameterType, Sonar
var declaredProperties = new List<IPropertySymbol>();
GetAllDeclaredProperties(parameterType, examinedTypes, declaredProperties);
var invalidProperties = declaredProperties
.Where(x => !CanBeNull(x.Type) && !x.HasAttribute(KnownType.System_Text_Json_Serialization_JsonRequiredAttribute) && !x.IsRequired())
.Where(x => !IsExcluded(x))
.Select(x => x.GetFirstSyntaxRef())
.Where(x => !IsInitialized(x));
foreach (var property in invalidProperties)
{
context.ReportIssue(Rule, property.GetIdentifier()?.GetLocation());
}

static bool IsExcluded(IPropertySymbol property) =>
CanBeNull(property.Type)
|| property.HasAnyAttribute(IgnoredAttributes)
|| IsNewtonsoftJsonPropertyRequired(property)
|| property.IsRequired();

static bool IsNewtonsoftJsonPropertyRequired(IPropertySymbol property) =>
property.GetAttributes(KnownType.Newtonsoft_Json_JsonPropertyAttribute).FirstOrDefault() is { } attribute
&& attribute.TryGetAttributeValue("Required", out int required)
&& (required is 1 or 2); // Required.AllowNull = 1, Required.Always = 2, https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Required.htm
}

private static bool IgnoreType(ITypeSymbol type) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ namespace SonarAnalyzer.Extensions;

public static class ISymbolExtensions
{
public static bool HasAnyAttribute(this ISymbol symbol, ImmutableArray<KnownType> types) =>
symbol.GetAttributes(types).Any();

public static bool HasAttribute(this ISymbol symbol, KnownType type) =>
symbol.GetAttributes(type).Any();

Expand Down
5 changes: 5 additions & 0 deletions analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ public sealed partial class KnownType
public static readonly KnownType NLog_LogFactory = new("NLog.LogFactory");
public static readonly KnownType NLog_LogManager = new("NLog.LogManager");
public static readonly KnownType NLog_Logger = new("NLog.Logger");
public static readonly KnownType Newtonsoft_Json_JsonPropertyAttribute = new("Newtonsoft.Json.JsonPropertyAttribute");
public static readonly KnownType Newtonsoft_Json_JsonRequiredAttribute = new("Newtonsoft.Json.JsonRequiredAttribute");
public static readonly KnownType Newtonsoft_Json_JsonIgnoreAttribute = new("Newtonsoft.Json.JsonIgnoreAttribute");
public static readonly KnownType NUnit_Framework_Assert = new("NUnit.Framework.Assert");
public static readonly KnownType NUnit_Framework_AssertionException = new("NUnit.Framework.AssertionException");
public static readonly KnownType NUnit_Framework_ExpectedExceptionAttribute = new("NUnit.Framework.ExpectedExceptionAttribute");
Expand Down Expand Up @@ -321,6 +324,7 @@ public sealed partial class KnownType
public static readonly KnownType System_ComponentModel_Composition_PartCreationPolicyAttribute = new("System.ComponentModel.Composition.PartCreationPolicyAttribute");
public static readonly KnownType System_ComponentModel_DataAnnotations_KeyAttribute = new("System.ComponentModel.DataAnnotations.KeyAttribute");
public static readonly KnownType System_ComponentModel_DataAnnotations_RegularExpressionAttribute = new("System.ComponentModel.DataAnnotations.RegularExpressionAttribute");
public static readonly KnownType System_ComponentModel_DataAnnotations_RangeAttribute = new("System.ComponentModel.DataAnnotations.RangeAttribute");
public static readonly KnownType System_ComponentModel_DataAnnotations_IValidatableObject = new("System.ComponentModel.DataAnnotations.IValidatableObject");
public static readonly KnownType System_ComponentModel_DataAnnotations_RequiredAttribute = new("System.ComponentModel.DataAnnotations.RequiredAttribute");
public static readonly KnownType System_ComponentModel_DataAnnotations_ValidationAttribute = new("System.ComponentModel.DataAnnotations.ValidationAttribute");
Expand Down Expand Up @@ -561,6 +565,7 @@ public sealed partial class KnownType
public static readonly KnownType System_StringComparison = new("System.StringComparison");
public static readonly KnownType System_SystemException = new("System.SystemException");
public static readonly KnownType System_Text_Encoding = new("System.Text.Encoding");
public static readonly KnownType System_Text_Json_Serialization_JsonIgnoreAttribute = new("System.Text.Json.Serialization.JsonIgnoreAttribute");
public static readonly KnownType System_Text_Json_Serialization_JsonRequiredAttribute = new("System.Text.Json.Serialization.JsonRequiredAttribute");
public static readonly KnownType System_Text_RegularExpressions_Regex = new("System.Text.RegularExpressions.Regex");
public static readonly KnownType System_Text_RegularExpressions_RegexOptions = new("System.Text.RegularExpressions.RegexOptions");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class AvoidUnderPostingTest

private readonly VerifierBuilder builder = new VerifierBuilder<AvoidUnderPosting>()
.WithBasePath("AspNet")
.AddReferences([..AspNetReferences, .. NuGetMetadataReference.SystemTextJson("7.0.4")]);
.AddReferences([..AspNetReferences, .. NuGetMetadataReference.SystemTextJson("7.0.4"), ..NuGetMetadataReference.NewtonsoftJson("13.0.3")]);

[TestMethod]
public void AvoidUnderPosting_CSharp() =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
Expand All @@ -17,8 +19,18 @@ public class ModelUsedInController
// ^^^^^^^^^^^^^
public int? NullableValueProperty { get; set; }
[Required] public int RequiredValueProperty { get; set; } // Noncompliant, RequiredAttribute has no effect on value types
[Range(0, 10)] public int ValuePropertyWithRangeValidation { get; set; } // Noncompliant
[Range(0, 10)] public int ValuePropertyWithRangeValidation { get; set; } // Compliant
[Required] public int? RequiredNullableValueProperty { get; set; }
[JsonProperty(Required = Required.Always)] public int JsonRequiredValuePropertyAlways { get; set; } // Compliant
[JsonProperty(Required = Required.AllowNull)] public int JsonRequiredValuePropertyAllowNull { get; set; } // Compliant
[JsonProperty(Required = Required.DisallowNull)] public int JsonRequiredValuePropertyDisallowNull { get; set; } // Noncompliant
[JsonProperty] public int JsonRequiredValuePropertyDefault { get; set; } // Noncompliant
[Newtonsoft.Json.JsonIgnore] public int JsonIgnoredProperty { get; set; } // Compliant
[Newtonsoft.Json.JsonRequired] public int JsonRequiredNewtonsoftValueProperty { get; set; } // Compliant
[System.Text.Json.Serialization.JsonRequired] public int JsonRequiredValueProperty { get; set; } // Compliant
[System.Text.Json.Serialization.JsonIgnore] public int JsonIgnoreValueProperty { get; set; } // Compliant
[JsonProperty(Required = Required.AllowNull)] [FromQuery] public int PropertyWithMultipleAttributesCompliant { get; set; } // Compliant
[Required] [FromQuery] public int PropertyWithMultipleAttributesNonCompliant { get; set; } // Noncompliant
public int PropertyWithPrivateSetter { get; private set; }
protected int ProtectedProperty { get; set; }
internal int InternalProperty { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public static References MongoDBDriver(string packageVersion = Constants.NuGetLa
public static References NHibernate(string packageVersion = "5.2.2") => Create("NHibernate", packageVersion);
public static References NpgsqlEntityFrameworkCorePostgreSQL(string packageVersion) => Create("Npgsql.EntityFrameworkCore.PostgreSQL", packageVersion);
public static References NSubstitute(string packageVersion) => Create("NSubstitute", packageVersion);
public static References NewtonsoftJson(string packageVersion) => Create("Newtonsoft.Json", packageVersion);
public static References NUnit(string packageVersion) => Create("NUnit", packageVersion);
public static References NUnitLite(string packageVersion) => Create("NUnitLite", packageVersion);
public static References OracleEntityFrameworkCore(string packageVersion) => Create("Oracle.EntityFrameworkCore", packageVersion);
Expand Down