Skip to content

Commit fd5ce65

Browse files
authored
MSTEST0036: Add analyzer for when a test member is shadowing another member (#3589)
1 parent e546766 commit fd5ce65

20 files changed

+533
-0
lines changed

src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Rule ID | Category | Severity | Notes
77
MSTEST0018 | Usage | Warning | DynamicDataShouldBeValidAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0018)
88
MSTEST0034 | Usage | Info | UseClassCleanupBehaviorEndOfClassAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0034)
99
MSTEST0035 | Usage | Info | UseDeploymentItemWithTestMethodOrTestClassAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0035)
10+
MSTEST0036 | Design | Warning | DoNotUseShadowingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0036)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Immutable;
5+
6+
using Analyzer.Utilities.Extensions;
7+
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
11+
using MSTest.Analyzers.Helpers;
12+
13+
namespace MSTest.Analyzers;
14+
15+
/// <summary>
16+
/// MSTEST0036: <inheritdoc cref="Resources.DoNotUseShadowingTitle"/>.
17+
/// </summary>
18+
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
19+
public sealed class DoNotUseShadowingAnalyzer : DiagnosticAnalyzer
20+
{
21+
private static readonly LocalizableResourceString Title = new(nameof(Resources.DoNotUseShadowingTitle), Resources.ResourceManager, typeof(Resources));
22+
private static readonly LocalizableResourceString Description = new(nameof(Resources.DoNotUseShadowingDescription), Resources.ResourceManager, typeof(Resources));
23+
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.DoNotUseShadowingMessageFormat), Resources.ResourceManager, typeof(Resources));
24+
25+
internal static readonly DiagnosticDescriptor DoNotUseShadowingRule = DiagnosticDescriptorHelper.Create(
26+
DiagnosticIds.DoNotUseShadowingRuleId,
27+
Title,
28+
MessageFormat,
29+
Description,
30+
Category.Design,
31+
DiagnosticSeverity.Warning,
32+
isEnabledByDefault: true);
33+
34+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
35+
= ImmutableArray.Create(DoNotUseShadowingRule);
36+
37+
public override void Initialize(AnalysisContext context)
38+
{
39+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
40+
context.EnableConcurrentExecution();
41+
42+
context.RegisterCompilationStartAction(context =>
43+
{
44+
if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestClassAttribute, out INamedTypeSymbol? testClassAttributeSymbol))
45+
{
46+
context.RegisterSymbolAction(
47+
context => AnalyzeSymbol(context, testClassAttributeSymbol),
48+
SymbolKind.NamedType);
49+
}
50+
});
51+
}
52+
53+
private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol testClassAttributeSymbol)
54+
{
55+
var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
56+
if (namedTypeSymbol.TypeKind != TypeKind.Class
57+
|| !namedTypeSymbol.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, testClassAttributeSymbol)))
58+
{
59+
return;
60+
}
61+
62+
Dictionary<string, List<ISymbol>> symbolGroups = GetBaseMembers(namedTypeSymbol);
63+
foreach (ISymbol member in namedTypeSymbol.GetMembers())
64+
{
65+
foreach (ISymbol baseMember in symbolGroups.GetValueOrDefault(member.Name, new List<ISymbol>()))
66+
{
67+
// Check if the member is shadowing a base class member
68+
if (IsMemberShadowing(member, baseMember))
69+
{
70+
context.ReportDiagnostic(member.CreateDiagnostic(DoNotUseShadowingRule, member.Name));
71+
}
72+
}
73+
}
74+
}
75+
76+
private static Dictionary<string, List<ISymbol>> GetBaseMembers(INamedTypeSymbol namedTypeSymbol)
77+
{
78+
Dictionary<string, List<ISymbol>> symbolGroups = new();
79+
INamedTypeSymbol? currentType = namedTypeSymbol.BaseType;
80+
while (currentType is not null)
81+
{
82+
foreach (ISymbol member in currentType.GetMembers())
83+
{
84+
if ((member is IMethodSymbol methodSymbol && (methodSymbol.MethodKind == MethodKind.PropertyGet || methodSymbol.MethodKind == MethodKind.PropertySet))
85+
|| member.IsOverride || member.Name == ".ctor")
86+
{
87+
continue;
88+
}
89+
90+
if (!symbolGroups.TryGetValue(member.Name, out List<ISymbol>? members))
91+
{
92+
members = new List<ISymbol>();
93+
symbolGroups[member.Name] = members;
94+
}
95+
96+
// Add the member to the list
97+
members.Add(member);
98+
}
99+
100+
currentType = currentType.BaseType;
101+
}
102+
103+
return symbolGroups;
104+
}
105+
106+
private static bool IsMemberShadowing(ISymbol member, ISymbol baseMember)
107+
{
108+
// Ensure both members are of the same kind (Method, Property, etc.)
109+
if (member.Kind != baseMember.Kind || member.IsOverride)
110+
{
111+
return false;
112+
}
113+
114+
// Compare methods
115+
if (member is IMethodSymbol methodSymbol && baseMember is IMethodSymbol baseMethodSymbol)
116+
{
117+
return methodSymbol.Name == baseMethodSymbol.Name &&
118+
methodSymbol.Parameters.Length == baseMethodSymbol.Parameters.Length &&
119+
methodSymbol.Parameters.Zip(baseMethodSymbol.Parameters, (p1, p2) =>
120+
SymbolEqualityComparer.Default.Equals(p1.Type, p2.Type)).All(equal => equal);
121+
}
122+
123+
// Compare properties
124+
else if (member is IPropertySymbol propertySymbol && baseMember is IPropertySymbol basePropertySymbol)
125+
{
126+
return propertySymbol.Name == basePropertySymbol.Name &&
127+
SymbolEqualityComparer.Default.Equals(propertySymbol.Type, basePropertySymbol.Type);
128+
}
129+
130+
return false;
131+
}
132+
}

src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs

+1
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ internal static class DiagnosticIds
4040
public const string NonNullableReferenceNotInitializedSuppressorRuleId = "MSTEST0033";
4141
public const string UseClassCleanupBehaviorEndOfClassRuleId = "MSTEST0034";
4242
public const string UseDeploymentItemWithTestMethodOrTestClassRuleId = "MSTEST0035";
43+
public const string DoNotUseShadowingRuleId = "MSTEST0036";
4344
}

src/Analyzers/MSTest.Analyzers/PublicAPI.Unshipped.txt

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#nullable enable
2+
MSTest.Analyzers.DoNotUseShadowingAnalyzer
3+
MSTest.Analyzers.DoNotUseShadowingAnalyzer.DoNotUseShadowingAnalyzer() -> void
24
MSTest.Analyzers.DynamicDataShouldBeValidAnalyzer
35
MSTest.Analyzers.DynamicDataShouldBeValidAnalyzer.DynamicDataShouldBeValidAnalyzer() -> void
46
MSTest.Analyzers.NonNullableReferenceNotInitializedSuppressor
@@ -7,6 +9,8 @@ MSTest.Analyzers.UseClassCleanupBehaviorEndOfClassAnalyzer
79
MSTest.Analyzers.UseClassCleanupBehaviorEndOfClassAnalyzer.UseClassCleanupBehaviorEndOfClassAnalyzer() -> void
810
MSTest.Analyzers.UseDeploymentItemWithTestMethodOrTestClassAnalyzer
911
MSTest.Analyzers.UseDeploymentItemWithTestMethodOrTestClassAnalyzer.UseDeploymentItemWithTestMethodOrTestClassAnalyzer() -> void
12+
override MSTest.Analyzers.DoNotUseShadowingAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext! context) -> void
13+
override MSTest.Analyzers.DoNotUseShadowingAnalyzer.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DiagnosticDescriptor!>
1014
override MSTest.Analyzers.DynamicDataShouldBeValidAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext! context) -> void
1115
override MSTest.Analyzers.DynamicDataShouldBeValidAnalyzer.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DiagnosticDescriptor!>
1216
override MSTest.Analyzers.NonNullableReferenceNotInitializedSuppressor.ReportSuppressions(Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext context) -> void

src/Analyzers/MSTest.Analyzers/Resources.Designer.cs

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Analyzers/MSTest.Analyzers/Resources.resx

+9
Original file line numberDiff line numberDiff line change
@@ -520,4 +520,13 @@ The type declaring these methods should also respect the following rules:
520520
<data name="TestClassShouldBeValidMessageFormat" xml:space="preserve">
521521
<value>Test class '{0}' should be valid</value>
522522
</data>
523+
<data name="DoNotUseShadowingDescription" xml:space="preserve">
524+
<value>Shadowing test members could cause testing issues (such as NRE).</value>
525+
</data>
526+
<data name="DoNotUseShadowingMessageFormat" xml:space="preserve">
527+
<value>Member '{0}' is already exist in the base class</value>
528+
</data>
529+
<data name="DoNotUseShadowingTitle" xml:space="preserve">
530+
<value>Do not use shadowing</value>
531+
</data>
523532
</root>

src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf

+15
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
281281
<target state="translated">Neukládejte TestContext ve statickém členu</target>
282282
<note />
283283
</trans-unit>
284+
<trans-unit id="DoNotUseShadowingDescription">
285+
<source>Shadowing test members could cause testing issues (such as NRE).</source>
286+
<target state="new">Shadowing test members could cause testing issues (such as NRE).</target>
287+
<note />
288+
</trans-unit>
289+
<trans-unit id="DoNotUseShadowingMessageFormat">
290+
<source>Member '{0}' is already exist in the base class</source>
291+
<target state="new">Member '{0}' is already exist in the base class</target>
292+
<note />
293+
</trans-unit>
294+
<trans-unit id="DoNotUseShadowingTitle">
295+
<source>Do not use shadowing</source>
296+
<target state="new">Do not use shadowing</target>
297+
<note />
298+
</trans-unit>
284299
<trans-unit id="DoNotUseSystemDescriptionAttributeDescription">
285300
<source>'System.ComponentModel.DescriptionAttribute' has no effect in the context of tests and you likely wanted to use 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute' instead.</source>
286301
<target state="translated">System.ComponentModel.DescriptionAttribute nemá v kontextu testů žádný účinek a pravděpodobně jste místo toho chtěli použít Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute.</target>

src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf

+15
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,21 @@ Der Typ, der diese Methoden deklariert, sollte auch die folgenden Regeln beachte
283283
<target state="translated">TestContext nicht in einem statischen Member speichern</target>
284284
<note />
285285
</trans-unit>
286+
<trans-unit id="DoNotUseShadowingDescription">
287+
<source>Shadowing test members could cause testing issues (such as NRE).</source>
288+
<target state="new">Shadowing test members could cause testing issues (such as NRE).</target>
289+
<note />
290+
</trans-unit>
291+
<trans-unit id="DoNotUseShadowingMessageFormat">
292+
<source>Member '{0}' is already exist in the base class</source>
293+
<target state="new">Member '{0}' is already exist in the base class</target>
294+
<note />
295+
</trans-unit>
296+
<trans-unit id="DoNotUseShadowingTitle">
297+
<source>Do not use shadowing</source>
298+
<target state="new">Do not use shadowing</target>
299+
<note />
300+
</trans-unit>
286301
<trans-unit id="DoNotUseSystemDescriptionAttributeDescription">
287302
<source>'System.ComponentModel.DescriptionAttribute' has no effect in the context of tests and you likely wanted to use 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute' instead.</source>
288303
<target state="translated">"System.ComponentModel.DescriptionAttribute" hat im Kontext von Tests keine Auswirkungen, und Sie wollten wahrscheinlich stattdessen "Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute" verwenden.</target>

src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf

+15
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ El tipo que declara estos métodos también debe respetar las reglas siguientes:
281281
<target state="translated">No almacenar TestContext en un miembro estático</target>
282282
<note />
283283
</trans-unit>
284+
<trans-unit id="DoNotUseShadowingDescription">
285+
<source>Shadowing test members could cause testing issues (such as NRE).</source>
286+
<target state="new">Shadowing test members could cause testing issues (such as NRE).</target>
287+
<note />
288+
</trans-unit>
289+
<trans-unit id="DoNotUseShadowingMessageFormat">
290+
<source>Member '{0}' is already exist in the base class</source>
291+
<target state="new">Member '{0}' is already exist in the base class</target>
292+
<note />
293+
</trans-unit>
294+
<trans-unit id="DoNotUseShadowingTitle">
295+
<source>Do not use shadowing</source>
296+
<target state="new">Do not use shadowing</target>
297+
<note />
298+
</trans-unit>
284299
<trans-unit id="DoNotUseSystemDescriptionAttributeDescription">
285300
<source>'System.ComponentModel.DescriptionAttribute' has no effect in the context of tests and you likely wanted to use 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute' instead.</source>
286301
<target state="translated">"System.ComponentModel.DescriptionAttribute" no tiene ningún efecto en el contexto de las pruebas y es probable que quiera usar "Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute" en su lugar.</target>

src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf

+15
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ Le type doit être une classe
281281
<target state="translated">Ne pas stocker TestContext dans un membre statique</target>
282282
<note />
283283
</trans-unit>
284+
<trans-unit id="DoNotUseShadowingDescription">
285+
<source>Shadowing test members could cause testing issues (such as NRE).</source>
286+
<target state="new">Shadowing test members could cause testing issues (such as NRE).</target>
287+
<note />
288+
</trans-unit>
289+
<trans-unit id="DoNotUseShadowingMessageFormat">
290+
<source>Member '{0}' is already exist in the base class</source>
291+
<target state="new">Member '{0}' is already exist in the base class</target>
292+
<note />
293+
</trans-unit>
294+
<trans-unit id="DoNotUseShadowingTitle">
295+
<source>Do not use shadowing</source>
296+
<target state="new">Do not use shadowing</target>
297+
<note />
298+
</trans-unit>
284299
<trans-unit id="DoNotUseSystemDescriptionAttributeDescription">
285300
<source>'System.ComponentModel.DescriptionAttribute' has no effect in the context of tests and you likely wanted to use 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute' instead.</source>
286301
<target state="translated">« System.ComponentModel.DescriptionAttribute » n’a aucun effet dans le contexte des tests et vous vouliez probablement utiliser « Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute » à la place.</target>

src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf

+15
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ Anche il tipo che dichiara questi metodi deve rispettare le regole seguenti:
281281
<target state="translated">Non archiviare TestContext in un membro statico</target>
282282
<note />
283283
</trans-unit>
284+
<trans-unit id="DoNotUseShadowingDescription">
285+
<source>Shadowing test members could cause testing issues (such as NRE).</source>
286+
<target state="new">Shadowing test members could cause testing issues (such as NRE).</target>
287+
<note />
288+
</trans-unit>
289+
<trans-unit id="DoNotUseShadowingMessageFormat">
290+
<source>Member '{0}' is already exist in the base class</source>
291+
<target state="new">Member '{0}' is already exist in the base class</target>
292+
<note />
293+
</trans-unit>
294+
<trans-unit id="DoNotUseShadowingTitle">
295+
<source>Do not use shadowing</source>
296+
<target state="new">Do not use shadowing</target>
297+
<note />
298+
</trans-unit>
284299
<trans-unit id="DoNotUseSystemDescriptionAttributeDescription">
285300
<source>'System.ComponentModel.DescriptionAttribute' has no effect in the context of tests and you likely wanted to use 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute' instead.</source>
286301
<target state="translated">'System.ComponentModel.DescriptionAttribute' non ha alcun effetto in un contesto di test ed è probabile che si intendesse usare 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute'.</target>

src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf

+15
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ The type declaring these methods should also respect the following rules:
281281
<target state="translated">静的メンバーに TestContext を格納しない</target>
282282
<note />
283283
</trans-unit>
284+
<trans-unit id="DoNotUseShadowingDescription">
285+
<source>Shadowing test members could cause testing issues (such as NRE).</source>
286+
<target state="new">Shadowing test members could cause testing issues (such as NRE).</target>
287+
<note />
288+
</trans-unit>
289+
<trans-unit id="DoNotUseShadowingMessageFormat">
290+
<source>Member '{0}' is already exist in the base class</source>
291+
<target state="new">Member '{0}' is already exist in the base class</target>
292+
<note />
293+
</trans-unit>
294+
<trans-unit id="DoNotUseShadowingTitle">
295+
<source>Do not use shadowing</source>
296+
<target state="new">Do not use shadowing</target>
297+
<note />
298+
</trans-unit>
284299
<trans-unit id="DoNotUseSystemDescriptionAttributeDescription">
285300
<source>'System.ComponentModel.DescriptionAttribute' has no effect in the context of tests and you likely wanted to use 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute' instead.</source>
286301
<target state="translated">'System.ComponentModel.DescriptionAttribute' はテストのコンテキストでは効果がないため、代わりに 'Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute' を使用する必要がある可能性があります。</target>

0 commit comments

Comments
 (0)