diff --git a/ChangeLog.md b/ChangeLog.md index f9fb09ca3d..38a52973de 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1163](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1163) ([PR](https://github.com/dotnet/roslynator/pull/1280)) - Fix analyzer [RCS1203](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1203) ([PR](https://github.com/dotnet/roslynator/pull/1282)) - Fix analyzer [RCS1046](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1046) ([PR](https://github.com/dotnet/roslynator/pull/1283)) +- Fix analyzer [RCS1158](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1158) ([PR](https://github.com/dotnet/roslynator/pull/1288)) ## [4.6.4] - 2023-11-24 diff --git a/src/Analyzers/CSharp/Analysis/StaticMemberInGenericTypeShouldUseTypeParameterAnalyzer.cs b/src/Analyzers/CSharp/Analysis/StaticMemberInGenericTypeShouldUseTypeParameterAnalyzer.cs index 0d18ee8a87..56654183c8 100644 --- a/src/Analyzers/CSharp/Analysis/StaticMemberInGenericTypeShouldUseTypeParameterAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/StaticMemberInGenericTypeShouldUseTypeParameterAnalyzer.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Roslynator.CSharp.Analysis; @@ -84,8 +85,13 @@ private static void AnalyzeNamedType(SymbolAnalysisContext context) if (typeParameters.IsDefault) typeParameters = namedType.TypeParameters; - if (!ContainsAnyTypeParameter(typeParameters, fieldSymbol.Type)) + if (!ContainsAnyTypeParameter(typeParameters, fieldSymbol.Type) + && !IsTypeParameterReferenced( + typeParameters, + (fieldSymbol.GetSyntax(context.CancellationToken) as VariableDeclaratorSyntax)?.Initializer?.Value)) + { ReportDiagnostic(context, fieldSymbol); + } break; } @@ -116,8 +122,13 @@ private static void AnalyzeNamedType(SymbolAnalysisContext context) if (typeParameters.IsDefault) typeParameters = namedType.TypeParameters; - if (!ContainsAnyTypeParameter(typeParameters, propertySymbol.Type)) + if (!ContainsAnyTypeParameter(typeParameters, propertySymbol.Type) + && !IsTypeParameterReferenced( + typeParameters, + (propertySymbol.GetSyntax(context.CancellationToken) as PropertyDeclarationSyntax)?.Initializer?.Value)) + { ReportDiagnostic(context, propertySymbol); + } } break; @@ -126,6 +137,41 @@ private static void AnalyzeNamedType(SymbolAnalysisContext context) } } + private static bool IsTypeParameterReferenced(ImmutableArray typeParameters, ExpressionSyntax value) + { + if (value.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + { + var memberAccess = (MemberAccessExpressionSyntax)value; + + if (memberAccess.Expression is IdentifierNameSyntax identifierName) + { + if (IsTypeParameter(identifierName, typeParameters)) + return true; + } + else if (memberAccess.Expression is TypeOfExpressionSyntax typeOfExpression) + { + if (typeOfExpression.Type is IdentifierNameSyntax identifierName2 + && IsTypeParameter(identifierName2, typeParameters)) + { + return true; + } + } + } + + return false; + + static bool IsTypeParameter(IdentifierNameSyntax identifierName, ImmutableArray typeParameters) + { + foreach (ITypeParameterSymbol typeParameter in typeParameters) + { + if (typeParameter.Name == identifierName.Identifier.ValueText) + return true; + } + + return false; + } + } + private static bool ContainsAnyTypeParameter(ImmutableArray typeParameters, ImmutableArray parameters) { foreach (IParameterSymbol parameter in parameters) diff --git a/src/Tests/Analyzers.Tests/RCS1158StaticMemberInGenericTypeShouldUseTypeParameterTests.cs b/src/Tests/Analyzers.Tests/RCS1158StaticMemberInGenericTypeShouldUseTypeParameterTests.cs new file mode 100644 index 0000000000..28eebc6825 --- /dev/null +++ b/src/Tests/Analyzers.Tests/RCS1158StaticMemberInGenericTypeShouldUseTypeParameterTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation and Contributors. 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; +using Roslynator.Testing; +using Roslynator.Testing.CSharp; +using Xunit; + +namespace Roslynator.CSharp.Analysis.Tests; + +public class RCS1158StaticMemberInGenericTypeShouldUseTypeParameterTests : AbstractCSharpDiagnosticVerifier +{ + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.StaticMemberInGenericTypeShouldUseTypeParameter; + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.StaticMemberInGenericTypeShouldUseTypeParameter)] + public async Task TestNoDiagnostic_Property() + { + await VerifyNoDiagnosticAsync(@" +public sealed class C where T : IFoo +{ + public static T2 Name { get; } = T.Name; +} + +public interface IFoo +{ + public static abstract T Name { get; } +}"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.StaticMemberInGenericTypeShouldUseTypeParameter)] + public async Task TestNoDiagnostic_Field() + { + await VerifyNoDiagnosticAsync(@" +public sealed class C where T : IFoo +{ + public static string Name = typeof(T).Name; +} + +public interface IFoo +{ + public static abstract string Name { get; } +}"); + } +}