diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProvider.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProvider.cs index c11bf27ff6..10507152b1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProvider.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProvider.cs @@ -97,16 +97,19 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context var boolType = context.Compilation.GetSpecialType(SpecialType.System_Boolean); var guidType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemGuid); - var builder = ImmutableHashSet.CreateBuilder(); - builder.AddIfNotNull(charType); - builder.AddIfNotNull(boolType); - builder.AddIfNotNull(stringType); - builder.AddIfNotNull(guidType); - var invariantToStringTypes = builder.ToImmutableHashSet(); + var nullableT = context.Compilation.GetSpecialType(SpecialType.System_Nullable_T); + var invariantToStringMethodsBuilder = ImmutableHashSet.CreateBuilder(); + AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, charType); + AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, boolType); + AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, stringType); + AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, guidType); + var invariantToStringMethods = invariantToStringMethodsBuilder.ToImmutable(); - var dateTimeType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTime); - var dateTimeOffsetType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTimeOffset); - var timeSpanType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTimeSpan); + var dateTimeToStringFormatMethod = GetToStringWithFormatStringParameter(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTime)); + + var dateTimeOffsetToStringFormatMethod = GetToStringWithFormatStringParameter(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTimeOffset)); + + var timeSpanToStringFormatMethod = GetToStringWithFormatStringParameter(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTimeSpan)); var stringFormatMembers = stringType.GetMembers("Format").OfType(); @@ -145,6 +148,9 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context var installedUICulturePropertyOfComputerInfoType = computerInfoType?.GetMembers("InstalledUICulture").OfType().FirstOrDefault(); var obsoleteAttributeType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemObsoleteAttribute); + + var guidParseMethods = guidType?.GetMembers("Parse") ?? ImmutableArray.Empty; + #endregion context.RegisterOperationAction(oaContext => @@ -157,8 +163,8 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context targetMethod.ContainingType.IsErrorType() || (activatorType != null && activatorType.Equals(targetMethod.ContainingType)) || (resourceManagerType != null && resourceManagerType.Equals(targetMethod.ContainingType)) || - IsValidToStringCall(invocationExpression, invariantToStringTypes, dateTimeType, dateTimeOffsetType, timeSpanType) || - IsValidParseCall(invocationExpression, guidType)) + IsValidToStringCall(invocationExpression, invariantToStringMethods, dateTimeToStringFormatMethod, dateTimeOffsetToStringFormatMethod, timeSpanToStringFormatMethod) || + IsValidParseCall(invocationExpression, guidParseMethods)) { return; } @@ -280,6 +286,25 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context }, OperationKind.Invocation); } + private static IMethodSymbol? GetToStringWithFormatStringParameter(INamedTypeSymbol? type) + { + return type?.GetMembers("ToString").OfType().FirstOrDefault(s => s.Parameters is [{ Type.SpecialType: SpecialType.System_String }]); + } + + private static void AddValidToStringMethods(ImmutableHashSet.Builder validToStringMethodsBuilder, INamedTypeSymbol nullableT, INamedTypeSymbol? type) + { + if (type is null) + { + return; + } + + validToStringMethodsBuilder.AddRange(GetToStringMethods(type)); + validToStringMethodsBuilder.AddRange(GetToStringMethods(nullableT.Construct(type))); + + static IEnumerable GetToStringMethods(INamedTypeSymbol namedTypeSymbol) + => namedTypeSymbol.GetMembers("ToString").OfType().WhereNotNull(); + } + private static IEnumerable GetIndexesOfParameterType(IMethodSymbol targetMethod, INamedTypeSymbol formatProviderType) { return targetMethod.Parameters @@ -293,17 +318,11 @@ private static ParameterInfo GetParameterInfo(INamedTypeSymbol type, bool isArra return ParameterInfo.GetParameterInfo(type, isArray, arrayRank, isParams); } - private static bool IsValidToStringCall(IInvocationOperation invocationOperation, ImmutableHashSet invariantToStringTypes, - INamedTypeSymbol? dateTimeType, INamedTypeSymbol? dateTimeOffsetType, INamedTypeSymbol? timeSpanType) + private static bool IsValidToStringCall(IInvocationOperation invocationOperation, ImmutableHashSet validToStringMethods, + IMethodSymbol? dateTimeToStringFormatMethod, IMethodSymbol? dateTimeOffsetToStringFormatMethod, IMethodSymbol? timeSpanToStringFormatMethod) { var targetMethod = invocationOperation.TargetMethod; - - if (targetMethod.Name != "ToString") - { - return false; - } - - if (invariantToStringTypes.Contains(UnwrapNullableValueTypes(targetMethod.ContainingType))) + if (validToStringMethods.Contains(targetMethod)) { return true; } @@ -316,44 +335,20 @@ private static bool IsValidToStringCall(IInvocationOperation invocationOperation } // Handle invariant format specifiers, see https://github.com/dotnet/roslyn-analyzers/issues/3507 - if ((dateTimeType != null && targetMethod.ContainingType.Equals(dateTimeType)) || - (dateTimeOffsetType != null && targetMethod.ContainingType.Equals(dateTimeOffsetType))) + if (targetMethod.Equals(dateTimeToStringFormatMethod, SymbolEqualityComparer.Default) || targetMethod.Equals(dateTimeOffsetToStringFormatMethod, SymbolEqualityComparer.Default)) { return s_dateInvariantFormats.Contains(format); } - if (timeSpanType != null && targetMethod.ContainingType.Equals(timeSpanType)) + if (targetMethod.Equals(timeSpanToStringFormatMethod, SymbolEqualityComparer.Default)) { return format == "c"; } return false; - - // Local functions - - static INamedTypeSymbol UnwrapNullableValueTypes(INamedTypeSymbol typeSymbol) - { - if (typeSymbol.IsNullableValueType() && typeSymbol.TypeArguments[0] is INamedTypeSymbol nullableTypeArgument) - return nullableTypeArgument; - return typeSymbol; - } } - private static bool IsValidParseCall(IInvocationOperation invocationOperation, INamedTypeSymbol? guidType) - { - var targetMethod = invocationOperation.TargetMethod; - - if (targetMethod.Name != "Parse") - { - return false; - } - - if (targetMethod.ContainingType.Equals(guidType)) - { - return true; - } - - return false; - } + private static bool IsValidParseCall(IInvocationOperation invocationOperation, ImmutableArray guidParseMethods) + => guidParseMethods.Contains(invocationOperation.TargetMethod); } } diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProviderTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProviderTests.cs index 716559c0a2..8d561abcbf 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProviderTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/SpecifyIFormatProviderTests.cs @@ -952,25 +952,8 @@ public async Task CA1305_GuidParse_NoDiagnosticsAsync() { await new VerifyCS.Test { - ReferenceAssemblies = new ReferenceAssemblies(""), // workaround for lack of .NET 7 Preview 4 reference assemblies + ReferenceAssemblies = ReferenceAssemblies.Net.Net70, TestCode = @" -namespace System -{ - public class Object { } - public abstract class ValueType { } - public struct Void { } - public class String { } - public interface IFormatProvider { } - public struct Guid - { - public static Guid Parse(string s) => default; - public static Guid Parse(string s, IFormatProvider provider) => default; - } -} -namespace System.Globalization -{ - public class CultureInfo : IFormatProvider { } -} namespace Test { using System;