Skip to content

Commit

Permalink
Add Meziantou.Analyzers (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
viceroypenguin authored Feb 2, 2025
1 parent 352953b commit 5ce226d
Show file tree
Hide file tree
Showing 44 changed files with 127 additions and 97 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,13 @@ dotnet_diagnostic.CA2000.severity = error # CA2000: Dispose object
dotnet_diagnostic.CA1515.severity = none # CA1515: Consider making public types internal
dotnet_diagnostic.CA1708.severity = none # CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1716.severity = none # CA1716: Identifiers should not match keywords

MA0053.public_class_should_be_sealed = true
MA0053.exceptions_should_be_sealed = true

dotnet_diagnostic.MA0004.severity = none
dotnet_diagnostic.MA0023.severity = none
dotnet_diagnostic.MA0048.severity = none
dotnet_diagnostic.MA0051.severity = none
dotnet_diagnostic.MA0053.severity = warning
dotnet_diagnostic.MA0071.severity = none
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@
<PackageVersion Include="Verify.TUnit" Version="28.9.0" />
<PackageVersion Include="xunit.v3.assert" Version="1.0.1" />
</ItemGroup>

<ItemGroup>
<GlobalPackageReference Include="Meziantou.Analyzer" Version="2.0.186" />
</ItemGroup>
</Project>
18 changes: 6 additions & 12 deletions src/Common/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,15 @@ public static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol? typ
}
}

public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type)
{
if (type is { TypeKind: TypeKind.Interface })
{
return type.AllInterfaces
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type) =>
type is { TypeKind: TypeKind.Interface }
? type
.AllInterfaces
.SelectMany(i => i.GetMembers())
.Concat(type.GetMembers());
}
else
{
return type
.Concat(type.GetMembers())
: type
.GetBaseTypesAndThis()
.SelectMany(t => t.GetMembers());
}
}

public static bool IsValidateAttribute([NotNullWhen(returnValue: true)] this INamedTypeSymbol? typeSymbol) =>
typeSymbol is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public override void ReportSuppressions(SuppressionAnalysisContext context)

if (symbol.BaseType.IsValidatorAttribute())
{
var suppression = SupportedSuppressions.First(s => s.SuppressedDiagnosticId == diagnostic.Id);
var suppression = SupportedSuppressions
.First(s => string.Equals(s.SuppressedDiagnosticId, diagnostic.Id, StringComparison.Ordinal));

context.ReportSuppression(
Suppression.Create(
Expand Down
11 changes: 7 additions & 4 deletions src/Immediate.Validations.Analyzers/ValidateClassAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ ITypeSymbol typeArgumentType
attributeProperties ??= attribute.AttributeClass!.GetMembers()
.OfType<IPropertySymbol>()
.ToList();
var property = attributeProperties.First(a => a.Name == name);

var property = attributeProperties
.First(a => string.Equals(a.Name, name, StringComparison.Ordinal));

ValidateArgument(
context,
Expand All @@ -370,7 +372,7 @@ ITypeSymbol typeArgumentType
{
for (var j = 0; j < attributeParameters.Length; j++)
{
if (attributeParameters[j].Name == name)
if (string.Equals(attributeParameters[j].Name, name, StringComparison.Ordinal))
{
ValidateArgument(
context,
Expand Down Expand Up @@ -422,12 +424,13 @@ List<ISymbol> members
if (!parameter.IsTargetTypeSymbol())
return;

var validateParameter = validateParameterSymbols.First(p => p.Name == parameter.Name);
var validateParameter = validateParameterSymbols
.First(p => string.Equals(p.Name, parameter.Name, StringComparison.Ordinal));

if (syntax.Expression.IsNameOfExpression(out var propertyName))
{
var member = members
.FirstOrDefault(p => p.Name == propertyName);
.Find(p => string.Equals(p.Name, propertyName, StringComparison.Ordinal));

if (member is null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,7 @@ IMethodSymbol methodSymbol
.GetMembers()
.OfType<IPropertySymbol>()
.Where(p =>
p.Name != "Message"
&& !p.IsStatic
p is { Name: not "Message", IsStatic: false }
)
.Cast<ISymbol>()
.ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Immediate.Validations.CodeFixes;

[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = "Add AdditionalValidations Method")]
public class AddAdditionalValidationsCodeRefactoringProvider : CodeRefactoringProvider
public sealed class AddAdditionalValidationsCodeRefactoringProvider : CodeRefactoringProvider
{
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Immediate.Validations.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp)]
public class AddValidateMethodCodefixProvider : CodeFixProvider
public sealed class AddValidateMethodCodefixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create([DiagnosticIds.IV0001ValidateMethodMustExist]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace Immediate.Validations.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp)]
public class CorrectValidatePropertyReturnTypeCodefixProvider : CodeFixProvider
public sealed class CorrectValidatePropertyReturnTypeCodefixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create([DiagnosticIds.IV0004ValidateMethodMustReturnValueTuple]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Immediate.Validations.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp)]
public class MakeValidatePropertyMethodStaticCodefixProvider : CodeFixProvider
public sealed class MakeValidatePropertyMethodStaticCodefixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create([DiagnosticIds.IV0002ValidateMethodMustBeStatic]);
Expand Down
7 changes: 6 additions & 1 deletion src/Immediate.Validations.Generators/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ public static Template GetTemplate(string name)
return Template.Parse(reader.ReadToEnd());
}

private static readonly Regex s_toTitleCaseRegex = new(@"(?<=[^A-Z])([A-Z])");
private static readonly Regex s_toTitleCaseRegex =
new(
@"(?<=[^A-Z])([A-Z])",
RegexOptions.Compiled,
matchTimeout: TimeSpan.FromMilliseconds(10)
);

public static string ToTitleCase(this string pascalCase) =>
s_toTitleCaseRegex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ ImmutableArray<AttributeData> attributes
var validateMessage = attributes
.FirstOrDefault(a => a.AttributeClass.IsNotNullAttribute())
?.NamedArguments
.FirstOrDefault(a => a.Key == "Message") is { Value: { Value: { } } value }
.FirstOrDefault(a => a.Key is "Message") is { Value: { Value: { } } value }
? value.ToCSharpString()
: null;

Expand Down Expand Up @@ -424,7 +424,8 @@ ITypeSymbol propertyType
attributeProperties ??= attribute.AttributeClass!.GetMembers()
.OfType<IPropertySymbol>()
.ToList();
var property = attributeProperties.First(a => a.Name == name);
var property = attributeProperties
.First(a => string.Equals(a.Name, name, StringComparison.Ordinal));

var parameterValue = BuildArgumentValue(
syntax,
Expand All @@ -441,7 +442,7 @@ ITypeSymbol propertyType
{
for (var j = 0; j < attributeParameters.Length; j++)
{
if (attributeParameters[j].Name == name)
if (string.Equals(attributeParameters[j].Name, name, StringComparison.Ordinal))
{
var parameterValue = BuildArgumentValue(
syntax,
Expand Down
4 changes: 2 additions & 2 deletions src/Immediate.Validations.Shared/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal static class ExpressionEvaluator
NewArrayExpression nae => GetValueFromNewArray(nae),
UnaryExpression ue => GetValueFromUnary(ue),
null => null,
_ => throw new NotSupportedException(/* TODO: Error Message */),
_ => throw new NotSupportedException("Unknown ExpressionType. Report an issue on the Immediate.Validations repository."),
};

[SuppressMessage(
Expand All @@ -35,7 +35,7 @@ internal static class ExpressionEvaluator
null,
parameters: [
GetValue(be.Left),
GetValue(be.Right)
GetValue(be.Right),
]
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Localization;

namespace Immediate.Validations.Shared.Localization;

internal sealed class LocalizationDictionary(Dictionary<string, string> localizations)
: Dictionary<string, LocalizedString>(localizations.ToDictionary(x => x.Key, x => new LocalizedString(x.Key, x.Value)));
: Dictionary<string, LocalizedString>(
localizations
.Select(kvp => KeyValuePair.Create(kvp.Key, new LocalizedString(kvp.Key, kvp.Value))),
StringComparer.Ordinal
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ namespace Immediate.Validations.Shared.Localization;

internal sealed partial class ValidatorLocalizer : IStringLocalizer
{
private static readonly Dictionary<string, Dictionary<string, LocalizedString>> s_localizations = new()
{
["en"] = En(),
["fr"] = Fr(),
};
private static readonly Dictionary<string, Dictionary<string, LocalizedString>> s_localizations =
new(StringComparer.Ordinal)
{
["en"] = En(),
["fr"] = Fr(),
};

public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
{
Expand All @@ -29,7 +30,7 @@ public LocalizedString this[string name]
if (s_localizations[currentCulture].TryGetValue(name, out var value))
return value;

return new LocalizedString(name, name, true);
return new LocalizedString(name, name, resourceNotFound: true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal sealed partial class ValidatorLocalizer : IStringLocalizer
{
[ExcludeFromCodeCoverage]
private static Dictionary<string, LocalizedString> En() =>
new LocalizationDictionary(new()
new LocalizationDictionary(new(StringComparer.Ordinal)
{
[nameof(EmptyAttribute)] = "'{PropertyName}' must be empty.",
[nameof(EnumValueAttribute)] = "'{PropertyName}' has a range of values which does not include '{PropertyValue}'.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal sealed partial class ValidatorLocalizer : IStringLocalizer
{
[ExcludeFromCodeCoverage]
private static Dictionary<string, LocalizedString> Fr() =>
new LocalizationDictionary(new()
new LocalizationDictionary(new(StringComparer.Ordinal)
{
[nameof(EmptyAttribute)] = "'{PropertyName}' doit être vide.",
[nameof(EnumValueAttribute)] = "'{PropertyName}' a une plage de valeurs qui n'inclut pas '{PropertyValue}'.",
Expand Down
7 changes: 4 additions & 3 deletions src/Immediate.Validations.Shared/ValidationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Immediate.Validations.Shared;
/// </summary>
public sealed partial class ValidationResult : IEnumerable<ValidationError>
{
[GeneratedRegex("{([^{}:]+)(?::([^{}]+))?}")]
[GeneratedRegex("{([^{}:]+)(?::([^{}]+))?}", RegexOptions.None, matchTimeoutMilliseconds: 50)]
private static partial Regex FormatRegex();

private List<ValidationError>? _errors;
Expand Down Expand Up @@ -81,6 +81,7 @@ IEnumerable<ValidationError> errors
/// <param name="arguments">
/// The values which can be used with <paramref name="messageTemplate"/> to build the final validation message.
/// </param>
[SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation")]
public void Add(
string propertyName,
string messageTemplate,
Expand Down Expand Up @@ -162,7 +163,7 @@ public void Add(

var message = overrideMessage ?? GetValidationMessage(method.DeclaringType);

var arguments = new Dictionary<string, object?>
var arguments = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
{
["PropertyValue"] = argumentValues[0],
["PropertyName"] = GetMemberName(targetPropertyExpression, targetObject),
Expand Down Expand Up @@ -243,7 +244,7 @@ private enum MemberIndex { None = 0, Member, Index, Method }
_ => null,
};

[GeneratedRegex(@"(?<=[^A-Z])([A-Z])")]
[GeneratedRegex(@"(?<=[^A-Z])([A-Z])", RegexOptions.None, matchTimeoutMilliseconds: 10)]
private static partial Regex FirstCharInWord();

private static string GetMemberName(MemberInfo member)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ internal static bool IsEmpty<T>(T value) =>
null => true,
string s when string.IsNullOrWhiteSpace(s) => true,
ICollection { Count: 0 } => true,
_ => EqualityComparer<T>.Default.Equals(value, default)
_ => EqualityComparer<T>.Default.Equals(value, default),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ public static bool ValidateProperty(string target, Regex? regex = null, [StringS
if (expr is null)
ThrowInvalidArgumentsException();

regex = new Regex(expr);
regex = new Regex(expr, RegexOptions.None, TimeSpan.FromMilliseconds(100));
}

return regex.IsMatch(target);
}

[DoesNotReturn]
[SuppressMessage("Usage", "MA0015:Specify the parameter name in ArgumentException", Justification = "Argument Exception applies to multiple properties")]
private static void ThrowInvalidArgumentsException() =>
throw new ArgumentException("Both `regex` and `expr` are `null`. At least one must be provided.");

Expand Down
3 changes: 3 additions & 0 deletions tests/Immediate.Validations.FunctionalTests/.editorconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[*.cs]

dotnet_diagnostic.CA1822.severity = none # CA1822: Mark members as static

dotnet_diagnostic.MA0009.severity = none
dotnet_diagnostic.MA0016.severity = none
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void InvalidRecordNullProperty()
{
PropertyName = "Strings",
ErrorMessage = "'Strings' must not be null.",
}
},
],
errors
);
Expand Down Expand Up @@ -158,7 +158,7 @@ public void InvalidCommandNullProperty()
{
PropertyName = "Commands",
ErrorMessage = "'Commands' must not be null.",
}
},
],
errors
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Immediate.Validations.FunctionalTests.IntegrationTests;

public partial class DuplicateTypeVisitTests
public sealed partial class DuplicateTypeVisitTests
{
[Validate]
public partial interface IBaseInterface : IValidationTarget<IBaseInterface>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void ShortIdHasErrors()
{
PropertyName = "Id",
ErrorMessage = "'Id' must be more than 4 characters.",
}
},
],
errors
);
Expand All @@ -78,7 +78,7 @@ public void EmptyDescriptionHasErrors()
{
PropertyName = "Description",
ErrorMessage = "'Description' must not be empty.",
}
},
],
errors
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void SkipSelfOperatesCorrectly()
{
PropertyName = nameof(BaseClass.BaseString),
ErrorMessage = "'Base String' must not be null.",
}
},
],
errors
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void InvalidRecordStructNullProperty()
{
PropertyName = "StringProperty",
ErrorMessage = "'String Property' must not be null.",
}
},
],
errors
);
Expand Down Expand Up @@ -68,7 +68,7 @@ public void InvalidStructNullProperty()
{
PropertyName = "StringProperty",
ErrorMessage = "'String Property' must not be null.",
}
},
],
errors
);
Expand Down
Loading

0 comments on commit 5ce226d

Please sign in to comment.