diff --git a/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs b/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs index 8385ee76..1b80e42d 100755 --- a/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs +++ b/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs @@ -5,6 +5,8 @@ namespace Meziantou.Analyzer.Internals; internal sealed class CultureSensitiveFormattingContext(Compilation compilation) { + private readonly HashSet _excludedMethods = CreateExcludedMethods(compilation); + public INamedTypeSymbol? CultureInsensitiveTypeAttributeSymbol { get; } = compilation.GetBestTypeByMetadataName("Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute"); public INamedTypeSymbol? FormatProviderSymbol { get; } = compilation.GetBestTypeByMetadataName("System.IFormatProvider"); public INamedTypeSymbol? CultureInfoSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Globalization.CultureInfo"); @@ -24,6 +26,26 @@ internal sealed class CultureSensitiveFormattingContext(Compilation compilation) public INamedTypeSymbol? SystemIFormattableSymbol { get; } = compilation.GetBestTypeByMetadataName("System.IFormattable"); public INamedTypeSymbol? SystemWindowsFontStretchSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Windows.FontStretch"); public INamedTypeSymbol? SystemWindowsMediaBrushSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Windows.Media.Brush"); + public INamedTypeSymbol? NuGetVersioningSemanticVersionSymbol { get; } = compilation.GetBestTypeByMetadataName("NuGet.Versioning.SemanticVersion"); + + private static HashSet CreateExcludedMethods(Compilation compilation) + { + var result = new HashSet(SymbolEqualityComparer.Default); + AddDocumentationId(result, compilation, "M:System.Convert.ToChar(System.String)"); + AddDocumentationId(result, compilation, "M:System.Convert.ToChar(System.Object)"); + AddDocumentationId(result, compilation, "M:System.Convert.ToBoolean(System.String)"); + AddDocumentationId(result, compilation, "M:System.Convert.ToBoolean(System.Object)"); + return result; + + static void AddDocumentationId(HashSet result, Compilation compilation, string id) + { + foreach (var item in DocumentationCommentId.GetSymbolsForDeclarationId(id, compilation)) + { + result.Add(item); + } + } + } + private static bool MustUnwrapNullableOfT(CultureSensitiveOptions options) { @@ -40,6 +62,9 @@ public bool IsCultureSensitiveOperation(IOperation operation, CultureSensitiveOp if (operation is IInvocationOperation invocation) { + if (_excludedMethods.Contains(invocation.TargetMethod)) + return false; + var methodName = invocation.TargetMethod.Name; if (methodName is "ToString") { @@ -49,7 +74,7 @@ public bool IsCultureSensitiveOperation(IOperation operation, CultureSensitiveOp { foreach (var arg in invocation.Arguments) { - if (arg.Value is { ConstantValue: { HasValue: true, Value: string } }) + if (arg.Value is { ConstantValue: { HasValue: true, Value: string } } or IConversionOperation { Type.SpecialType: SpecialType.System_String, ConstantValue: { HasValue: true, Value: null } }) { if (format is not null) { @@ -252,16 +277,19 @@ private bool IsCultureSensitiveType(ITypeSymbol? typeSymbol, CultureSensitiveOpt if (typeSymbol.IsOrInheritFrom(SystemWindowsMediaBrushSymbol)) return false; + if (typeSymbol.IsOrInheritFrom(NuGetVersioningSemanticVersionSymbol)) + return false; + if (!typeSymbol.Implements(SystemIFormattableSymbol)) return false; - if (!IsCultureSensitiveTypeUsingAttribute(typeSymbol, format: null)) + if (!IsCultureSensitiveTypeUsingAttribute(typeSymbol, hasFormat: false, format: null)) return false; return true; } - private bool IsCultureSensitiveTypeUsingAttribute(ITypeSymbol typeSymbol, string? format) + private bool IsCultureSensitiveTypeUsingAttribute(ITypeSymbol typeSymbol, bool hasFormat, string? format) { var attributes = typeSymbol.GetAttributes(CultureInsensitiveTypeAttributeSymbol); foreach (var attr in attributes) @@ -269,8 +297,11 @@ private bool IsCultureSensitiveTypeUsingAttribute(ITypeSymbol typeSymbol, string if (attr.ConstructorArguments.IsEmpty) return false; // no format is set, so the type is culture insensitive - var attrFormat = attr.ConstructorArguments[0].Value; - if (attrFormat is null || (attrFormat is string attrFormatValue && (string.IsNullOrEmpty(attrFormatValue) || attrFormatValue == format))) + if (!hasFormat) + continue; + + var attrFormat = attr.ConstructorArguments[0].Value as string; + if (attrFormat == format) return false; // no format is set, so the type is culture insensitive } @@ -284,8 +315,11 @@ private bool IsCultureSensitiveTypeUsingAttribute(ITypeSymbol typeSymbol, string if (attribute.ConstructorArguments.Length == 1) return false; - var attrFormat = attribute.ConstructorArguments[1].Value; - if (attrFormat is null || (attrFormat is string attrFormatValue && (string.IsNullOrEmpty(attrFormatValue) || attrFormatValue == format))) + if (!hasFormat) + continue; + + var attrFormat = attribute.ConstructorArguments[1].Value as string; + if (attrFormat == format) return false; // no format is set, so the type is culture insensitive } } @@ -298,6 +332,7 @@ private bool IsCultureSensitiveType(ITypeSymbol? symbol, IOperation? format, IOp if (!IsCultureSensitiveType(symbol, options)) return false; + var hasFormatString = format is { ConstantValue.HasValue: true }; var formatString = format?.ConstantValue.Value as string; if (instance is not null) @@ -320,7 +355,7 @@ private bool IsCultureSensitiveType(ITypeSymbol? symbol, IOperation? format, IOp return false; } - if (symbol is not null && !IsCultureSensitiveTypeUsingAttribute(symbol, formatString)) + if (symbol is not null && !IsCultureSensitiveTypeUsingAttribute(symbol, hasFormatString, formatString)) return false; return true; diff --git a/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs index c0ca6619..2223900f 100644 --- a/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs @@ -37,25 +37,6 @@ private sealed class AnalyzerContext(Compilation compilation) { private readonly CultureSensitiveFormattingContext _cultureSensitiveContext = new(compilation); private readonly OverloadFinder _overloadFinder = new(compilation); - private readonly HashSet _excludedMethods = CreateExcludedMethods(compilation); - - private static HashSet CreateExcludedMethods(Compilation compilation) - { - var result = new HashSet(SymbolEqualityComparer.Default); - AddDocumentationId(result, compilation, "M:System.Convert.ToChar(System.String)"); - AddDocumentationId(result, compilation, "M:System.Convert.ToChar(System.Object)"); - AddDocumentationId(result, compilation, "M:System.Convert.ToBoolean(System.String)"); - AddDocumentationId(result, compilation, "M:System.Convert.ToBoolean(System.Object)"); - return result; - - static void AddDocumentationId(HashSet result, Compilation compilation, string id) - { - foreach (var item in DocumentationCommentId.GetSymbolsForDeclarationId(id, compilation)) - { - result.Add(item); - } - } - } public void AnalyzeInvocation(OperationAnalysisContext context) { @@ -65,7 +46,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) if (IsExcludedMethod(context, operation)) return; - + var options = MustUnwrapNullableTypes(context, operation) ? CultureSensitiveOptions.UnwrapNullableOfT : CultureSensitiveOptions.None; if (!_cultureSensitiveContext.IsCultureSensitiveOperation(operation, options)) return; @@ -81,7 +62,11 @@ public void AnalyzeInvocation(OperationAnalysisContext context) var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, includeObsoleteMethods: false, allowOptionalParameters: false, _cultureSensitiveContext.FormatProviderSymbol); if (overload is not null) { - context.ReportDiagnostic(Rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); + if (_cultureSensitiveContext.IsCultureSensitiveOperation(operation, CultureSensitiveOptions.None)) + { + context.ReportDiagnostic(Rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); + } + return; } @@ -108,17 +93,18 @@ public void AnalyzeInvocation(OperationAnalysisContext context) var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, includeObsoleteMethods: false, allowOptionalParameters: false, _cultureSensitiveContext.CultureInfoSymbol); if (overload is not null) { - context.ReportDiagnostic(Rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.CultureInfoSymbol.ToDisplayString()); + if (_cultureSensitiveContext.IsCultureSensitiveOperation(operation, CultureSensitiveOptions.None)) + { + context.ReportDiagnostic(Rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.CultureInfoSymbol.ToDisplayString()); + } + return; } } } - private bool IsExcludedMethod(OperationAnalysisContext context, IInvocationOperation operation) + private static bool IsExcludedMethod(OperationAnalysisContext context, IInvocationOperation operation) { - if (_excludedMethods.Contains(operation.TargetMethod)) - return true; - // ToString show culture-sensitive data by default if (operation?.GetContainingMethod(context.CancellationToken)?.Name == "ToString") { diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs index 9b24b4ce..c26f983d 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs @@ -9,38 +9,24 @@ public sealed class UseIFormatProviderAnalyzerTests private static ProjectBuilder CreateProjectBuilder() { return new ProjectBuilder() - .WithAnalyzer(); + .WithAnalyzer() + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication) + .AddMeziantouAttributes(); } [Fact] public async Task Int32ToStringWithCultureInfo_ShouldNotReportDiagnostic() { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - 1.ToString(System.Globalization.CultureInfo.InvariantCulture); - } -}"; await CreateProjectBuilder() - .WithSourceCode(SourceCode) + .WithSourceCode("1.ToString(System.Globalization.CultureInfo.InvariantCulture);") .ValidateAsync(); } [Fact] public async Task Int32ToStringWithoutCultureInfo_ShouldReportDiagnostic() { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||](-1).ToString(); - } -}"; await CreateProjectBuilder() - .WithSourceCode(SourceCode) + .WithSourceCode("[||](-1).ToString();") .ShouldReportDiagnosticWithMessage("Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter") .ValidateAsync(); } @@ -48,86 +34,59 @@ await CreateProjectBuilder() [Fact] public async Task Int32_PositiveToStringWithoutCultureInfo_ShouldReportDiagnostic() { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - 1.ToString(); - } -}"; await CreateProjectBuilder() - .WithSourceCode(SourceCode) + .WithSourceCode("1.ToString();") .ValidateAsync(); } [Theory] - [InlineData("x")] - [InlineData("x8")] - [InlineData("X")] - [InlineData("X8")] - [InlineData("B")] - public async Task Int32_InvariantFormat(string format) - { - await CreateProjectBuilder() - .WithSourceCode($$""" - class TypeName - { - public void Test() - { - (-1).ToString("{{format}}"); - } - } - """) - .ValidateAsync(); - } - - [Fact] - public async Task BooleanToStringWithoutCultureInfo_ShouldNotReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - true.ToString(); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task SystemGuidToStringWithoutCultureInfo_ShouldNotReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - default(System.Guid).ToString(); - default(System.Guid).ToString(""D""); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task SystemTimeSpanToStringWithoutCultureInfo_ShouldNotReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - System.TimeSpan.Zero.ToString(); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) + [InlineData(""" (-1).ToString("x") """)] + [InlineData(""" (-1).ToString("x8") """)] + [InlineData(""" (-1).ToString("X" )""")] + [InlineData(""" (-1).ToString("X8") """)] + [InlineData(""" (-1).ToString("B") """)] + [InlineData(""" true.ToString() """)] + [InlineData(""" default(System.Guid).ToString() """)] + [InlineData(""" default(System.Guid).ToString("D") """)] + [InlineData(""" System.TimeSpan.Zero.ToString() """)] + [InlineData(""" System.TimeSpan.Zero.ToString("c") """)] + [InlineData(""" System.TimeSpan.Zero.ToString("T") """)] + [InlineData(""" [||]System.TimeSpan.Zero.ToString("G") """)] + [InlineData(""" ' '.ToString(); """)] + [InlineData(""" [||]System.DateTime.TryParse("", out _) """)] + [InlineData(""" [||]System.DateTimeOffset.TryParse("", out _) """)] + [InlineData(""" [||]"".ToLower() """)] + [InlineData(""" [||]new System.Text.StringBuilder().AppendFormat("{0}", 10) """)] + [InlineData(""" System.DayOfWeek.Monday.ToString() """)] + [InlineData(""" default(System.DateTime).ToString("o") """)] + [InlineData(""" default(System.DateTime).ToString("O") """)] + [InlineData(""" default(System.DateTime).ToString("r") """)] + [InlineData(""" default(System.DateTime).ToString("R") """)] + [InlineData(""" default(System.DateTime).ToString("s") """)] + [InlineData(""" default(System.DateTime).ToString("u") """)] + [InlineData(""" default(System.DateTimeOffset).ToString("o") """)] + [InlineData(""" default(System.DateTimeOffset).ToString("O") """)] + [InlineData(""" default(System.DateTimeOffset).ToString("r") """)] + [InlineData(""" default(System.DateTimeOffset).ToString("R") """)] + [InlineData(""" default(System.DateTimeOffset).ToString("s") """)] + [InlineData(""" default(System.DateTimeOffset).ToString("u") """)] + [InlineData(""" [||]default(System.DateTime).ToString("yyyy") """)] + [InlineData(""" System.Guid.Parse("o") """)] + [InlineData(""" System.Guid.TryParse("o", out _) """)] + [InlineData(""" ((int?)1)?.ToString(System.Globalization.CultureInfo.InvariantCulture) """)] + [InlineData(""" string.Format("", "test", 1, 'c') """)] + [InlineData(""" string.Format(default(System.IFormatProvider), "", -1) """)] + [InlineData(""" string.Format("") """)] + [InlineData(""" [||]string.Format("", -1) """)] + [InlineData(""" [||]string.Format("", 0, 0, 0, 0, 0, 0, -1, 0 ,0 ,0, 0) """)] + [InlineData(""" System.Convert.ToChar((object)null) """)] + [InlineData(""" System.Convert.ToChar("") """)] + [InlineData(""" System.Convert.ToBoolean((object)null) """)] + [InlineData(""" System.Convert.ToBoolean("") """)] + public async Task Tests(string expression) + { + await CreateProjectBuilder() + .WithSourceCode(expression + ";") .ValidateAsync(); } @@ -135,96 +94,21 @@ await CreateProjectBuilder() public async Task SystemTimeSpanImplicitToStringWithoutCultureInfo_InterpolatedString_ShouldNotReportDiagnostic() { const string SourceCode = """ - class TypeName - { - public void Test() - { - var timeSpan = System.TimeSpan.FromSeconds(1); - var myString = $"This is a test: {timeSpan}"; - } - } + var timeSpan = System.TimeSpan.FromSeconds(1); + var myString = $"This is a test: {timeSpan}"; """; await CreateProjectBuilder() .WithSourceCode(SourceCode) .ValidateAsync(); } - [Fact] - public async Task SystemTimeSpanToStringWithoutCultureInfo_FormatC_ShouldNotReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - System.TimeSpan.Zero.ToString(""c""); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task SystemTimeSpanToStringWithoutCultureInfo_FormatT_ShouldNotReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - System.TimeSpan.Zero.ToString(""T""); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task SystemTimeSpanToStringWithoutCultureInfo_FormatG_ShouldReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||]System.TimeSpan.Zero.ToString(""G""); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task SystemCharToStringWithoutCultureInfo_ShouldNotReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - ' '.ToString(); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - [Fact] public async Task Int32ParseWithoutCultureInfo_ShouldReportDiagnostic() { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||]int.Parse(""""); - [||]int.Parse("""", System.Globalization.NumberStyles.Any); - } -}"; + const string SourceCode = """ + [||]int.Parse(""); + [||]int.Parse("", System.Globalization.NumberStyles.Any); + """; await CreateProjectBuilder() .WithSourceCode(SourceCode) .ShouldReportDiagnosticWithMessage("Use an overload of 'Parse' that has a 'System.IFormatProvider' parameter") @@ -235,122 +119,22 @@ await CreateProjectBuilder() [Fact] public async Task SingleTryParseWithoutCultureInfo_ShouldReportDiagnostic() { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||]float.TryParse("""", out _); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ShouldReportDiagnosticWithMessage("Use an overload of 'TryParse' that has a 'System.IFormatProvider' parameter") - .ValidateAsync(); - } - - [Fact] - public async Task DateTimeTryParseWithoutCultureInfo_ShouldReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||]System.DateTime.TryParse("""", out _); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ShouldReportDiagnosticWithMessage("Use an overload of 'TryParse' that has a 'System.IFormatProvider' parameter") - .ValidateAsync(); - } - - [Fact] - public async Task DateTimeOffsetTryParseWithoutCultureInfo_ShouldReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||]System.DateTimeOffset.TryParse("""", out _); - } -}"; + const string SourceCode = """ + [||]float.TryParse("", out _); + """; await CreateProjectBuilder() .WithSourceCode(SourceCode) .ShouldReportDiagnosticWithMessage("Use an overload of 'TryParse' that has a 'System.IFormatProvider' parameter") .ValidateAsync(); } - [Fact] - public async Task StringToLower_ShouldReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||]"""".ToLower(); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ShouldReportDiagnosticWithMessage("Use an overload of 'ToLower' that has a 'System.Globalization.CultureInfo' parameter") - .ValidateAsync(); - } - - [Fact] - public async Task StringBuilderAppendFormat_ShouldReportDiagnostic() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - [||]new System.Text.StringBuilder().AppendFormat(""{0}"", 10); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ShouldReportDiagnosticWithMessage("Use an overload of 'AppendFormat' that has a 'System.IFormatProvider' parameter") - .ValidateAsync(); - } - - [Fact] - public async Task EnumValueToString() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - _ = A.Value1.ToString(); - } -} - -enum A -{ - Value1 -} -"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - [Fact] public async Task EnumToString() { - const string SourceCode = @" -class TypeName -{ - public void Test(System.Enum value) - { - _ = value.ToString(); - } -} -"; + const string SourceCode = """ + System.Enum value = default; + _ = value.ToString(); + """; await CreateProjectBuilder() .WithSourceCode(SourceCode) .ValidateAsync(); @@ -360,15 +144,10 @@ await CreateProjectBuilder() public async Task StringBuilder_AppendLine_AllStringParams() { const string SourceCode = """ -class TypeName -{ - public void Test(System.Text.StringBuilder sb) - { - var str = ""; - sb.AppendLine($"foo{str}var{str}"); - } -} -"""; + var sb = new System.Text.StringBuilder(); + var str = ""; + sb.AppendLine($"foo{str}var{str}"); + """; await CreateProjectBuilder() .WithSourceCode(SourceCode) .ValidateAsync(); @@ -378,16 +157,11 @@ await CreateProjectBuilder() public async Task StringBuilder_AppendLine_AllStringParams_Net7() { const string SourceCode = """ -using System; -class TypeName -{ - public void Test(System.Text.StringBuilder sb) - { - var str = ""; - sb.AppendLine($"foo{str}var{str}{'a'}{Guid.NewGuid()}"); - } -} -"""; + using System; + var sb = new System.Text.StringBuilder(); + var str = ""; + sb.AppendLine($"foo{str}var{str}{'a'}{Guid.NewGuid()}"); + """; await CreateProjectBuilder() .WithTargetFramework(TargetFramework.Net7_0) .WithSourceCode(SourceCode) @@ -399,15 +173,10 @@ await CreateProjectBuilder() public async Task StringBuilder_AppendLine_Int32Params_Net7() { const string SourceCode = """ -class TypeName -{ - public void Test(System.Text.StringBuilder sb) - { - int value = 0; - [||]sb.AppendLine($"foo{value}"); - } -} -"""; + var sb = new System.Text.StringBuilder(); + int value = 0; + [||]sb.AppendLine($"foo{value}"); + """; await CreateProjectBuilder() .WithTargetFramework(TargetFramework.Net7_0) .WithSourceCode(SourceCode) @@ -425,15 +194,10 @@ await CreateProjectBuilder() public async Task StringBuilder_AppendLine_DateTime_InvariantFormat_Net7(string format) { var sourceCode = $$""" -class TypeName -{ - public void Test(System.Text.StringBuilder sb) - { - System.DateTime value = default; - sb.AppendLine($"foo{value:{{format}}}"); - } -} -"""; + var sb = new System.Text.StringBuilder(); + System.DateTime value = default; + sb.AppendLine($"foo{value:{{format}}}"); + """; await CreateProjectBuilder() .WithTargetFramework(TargetFramework.Net7_0) .WithSourceCode(sourceCode) @@ -445,15 +209,10 @@ await CreateProjectBuilder() public async Task StringBuilder_AppendLine_DateTime_Net7() { var sourceCode = """ -class TypeName -{ - public void Test(System.Text.StringBuilder sb) - { - System.DateTime value = default; - [||]sb.AppendLine($"foo{value:yyyy}"); - } -} -"""; + var sb = new System.Text.StringBuilder(); + System.DateTime value = default; + [||]sb.AppendLine($"foo{value:yyyy}"); + """; await CreateProjectBuilder() .WithTargetFramework(TargetFramework.Net7_0) .WithSourceCode(sourceCode) @@ -461,96 +220,13 @@ await CreateProjectBuilder() } #endif - [Fact] - public async Task InvariantDateTimeFormat() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - _ = default(System.DateTime).ToString(""o""); - _ = default(System.DateTime).ToString(""O""); - _ = default(System.DateTime).ToString(""r""); - _ = default(System.DateTime).ToString(""R""); - _ = default(System.DateTime).ToString(""s""); - _ = default(System.DateTime).ToString(""u""); - _ = default(System.DateTimeOffset).ToString(""o""); - _ = default(System.DateTimeOffset).ToString(""O""); - _ = default(System.DateTimeOffset).ToString(""r""); - _ = default(System.DateTimeOffset).ToString(""R""); - _ = default(System.DateTimeOffset).ToString(""s""); - _ = default(System.DateTimeOffset).ToString(""u""); - } -} -"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - [Fact] - public async Task DateTimeToString() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - _ = [||]default(System.DateTime).ToString(""yyyy""); - } -} -"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task GuidParse() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - System.Guid.Parse(""o""); - System.Guid.TryParse(""o"", out _); - } -}"; - await CreateProjectBuilder() - .WithTargetFramework(TargetFramework.Net7_0) - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task NullableInt32ToStringWithCultureInfo() - { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - ((int?)1)?.ToString(System.Globalization.CultureInfo.InvariantCulture); - } -}"; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - [Fact] public async Task NullableInt32ToStringWithoutCultureInfo() { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - int? i = -1; - [||]i.ToString(); - } -}"; + const string SourceCode = """ + int? i = -1; + [||]i.ToString(); + """; await CreateProjectBuilder() .WithSourceCode(SourceCode) .ValidateAsync(); @@ -559,14 +235,9 @@ await CreateProjectBuilder() [Fact] public async Task NullableInt32ToStringWithoutCultureInfo_DisabledConfig() { - const string SourceCode = @" -class TypeName -{ - public void Test() - { - ((int?)1).ToString(); - } -}"; + const string SourceCode = """ + ((int?)1).ToString(); + """; await CreateProjectBuilder() .WithSourceCode(SourceCode) .AddAnalyzerConfiguration("MA0011.consider_nullable_types", "false") @@ -574,118 +245,12 @@ await CreateProjectBuilder() } [Fact] - public async Task StringFormat_ArgsAreNonCultureSensitive() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() - { - _ = string.Format("", "test", 1, 'c'); - } -} -"""; - await CreateProjectBuilder() - .WithSourceCode(sourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task StringFormat_AlreadyHasFormatProvider() + public async Task CultureInsensitiveTypeAttribute_Assembly() { - var sourceCode = $$""" -class TypeName -{ - public void Test() - { - _ = string.Format(default(System.IFormatProvider), "", -1); - } -} -"""; - await CreateProjectBuilder() - .WithSourceCode(sourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task StringFormat_NoArgument() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() - { - _ = string.Format(""); - } -} -"""; - await CreateProjectBuilder() - .WithSourceCode(sourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task StringFormat_Report() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() - { - _ = [||]string.Format("", -1); - } -} -"""; - await CreateProjectBuilder() - .WithSourceCode(sourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task StringFormat_ManyArgs_Report() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() - { - _ = [||]string.Format("", 0, 0, 0, 0, 0, 0, -1, 0 ,0 ,0, 0); - } -} -"""; - await CreateProjectBuilder() - .WithSourceCode(sourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task Convert_ToChar_Object() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() - { - _ = System.Convert.ToChar((object)null); - } -} -"""; - await CreateProjectBuilder() - .WithSourceCode(sourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task Convert_ToChar_String() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() - { - _ = System.Convert.ToChar(""); - } -} + var sourceCode = """ +[assembly: Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute(typeof(System.DateTime))] +_ = new System.DateTime().ToString(); +_ = new System.DateTime().ToString("whatever"); """; await CreateProjectBuilder() .WithSourceCode(sourceCode) @@ -693,16 +258,15 @@ await CreateProjectBuilder() } [Fact] - public async Task Convert_ToBoolean_Object() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() + public async Task CultureInsensitiveTypeAttribute_Assembly_Format() { - _ = System.Convert.ToBoolean((object)null); - } -} + var sourceCode = """ +[assembly: Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute(typeof(System.DateTime), "custom")] +[assembly: Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute(typeof(System.DateTime), "")] +[assembly: Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute(typeof(System.DateTime), null)] +_ = new System.DateTime().ToString("custom"); +_ = new System.DateTime().ToString(""); +_ = [|new System.DateTime().ToString("dummy")|]; """; await CreateProjectBuilder() .WithSourceCode(sourceCode) @@ -710,16 +274,12 @@ await CreateProjectBuilder() } [Fact] - public async Task Convert_ToBoolean_String() - { - var sourceCode = $$""" -class TypeName -{ - public void Test() + public async Task CultureInsensitiveTypeAttribute_Assembly_Format_null1() { - _ = System.Convert.ToBoolean(""); - } -} + var sourceCode = """ +[assembly: Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute(typeof(System.DateTime), null)] +_ = [|new System.DateTime().ToString("dummy")|]; +_ = new System.DateTime().ToString(format: null); """; await CreateProjectBuilder() .WithSourceCode(sourceCode)