Skip to content

Commit 3bcf283

Browse files
committed
Add tests for sealed analyzer
1 parent df386fa commit 3bcf283

File tree

4 files changed

+339
-5
lines changed

4 files changed

+339
-5
lines changed

tracer/src/Datadog.Trace.Tools.Analyzers.CodeFixes/SealedAnalyzer/SealedAnalyzerCodeFixProvider.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ namespace Datadog.Trace.Tools.Analyzers.SealedAnalyzer;
2525
[ExportCodeFixProvider(LanguageNames.CSharp)]
2626
public sealed class SealedAnalyzerCodeFixProvider : CodeFixProvider
2727
{
28+
private const string Title = "Seal class";
29+
2830
/// <inheritdoc/>
2931
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(Diagnostics.DiagnosticId);
3032

@@ -35,9 +37,9 @@ public sealed class SealedAnalyzerCodeFixProvider : CodeFixProvider
3537
public override Task RegisterCodeFixesAsync(CodeFixContext context)
3638
{
3739
var codeAction = CodeAction.Create(
38-
"Seal class",
40+
Title,
3941
SealClassDeclarationsAsync,
40-
"Seal class");
42+
Title);
4143
context.RegisterCodeFix(codeAction, context.Diagnostics);
4244
return Task.CompletedTask;
4345

tracer/src/Datadog.Trace.Tools.Analyzers/Helpers/WellKnownTypeProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private static ImmutableArray<string> GetNamespaceNamesFromFullTypeName(string f
118118
{
119119
if (fullTypeName[i] == '.')
120120
{
121-
namespaceNamesBuilder.Add(fullTypeName.Substring(prevStartIndex, i));
121+
namespaceNamesBuilder.Add(fullTypeName.Substring(prevStartIndex, i - prevStartIndex));
122122
prevStartIndex = i + 1;
123123
}
124124
else if (!IsIdentifierPartCharacter(fullTypeName[i]))

tracer/src/Datadog.Trace.Tools.Analyzers/SealedAnalyzer/SealedAnalyzer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ private static void OnCompilationStart(CompilationStartAnalysisContext context)
3636
var attributesToMatch = new[]
3737
{
3838
context.Compilation.GetOrCreateTypeByMetadataName("System.Runtime.InteropServices.ComImportAttribute"),
39-
context.Compilation.GetOrCreateTypeByMetadataName("Datadog.Trace.DuckTyping.DuckAttribute")
39+
context.Compilation.GetOrCreateTypeByMetadataName("Datadog.Trace.DuckTyping.DuckTypeAttribute")
4040
};
4141

4242
var candidateTypes = PooledConcurrentSet<INamedTypeSymbol>.GetInstance(SymbolEqualityComparer.Default);
@@ -51,7 +51,6 @@ private static void OnCompilationStart(CompilationStartAnalysisContext context)
5151
!type.IsAbstract &&
5252
!type.IsStatic &&
5353
!type.IsSealed &&
54-
!type.IsExternallyVisible() &&
5554
!type.HasAnyAttribute(attributesToMatch))
5655
{
5756
candidateTypes.Add(type);
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
// <copyright file="SealedAnalyzerTests.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
extern alias AnalyzerCodeFixes;
6+
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Datadog.Trace.Tools.Analyzers.SealedAnalyzer;
10+
using Microsoft.CodeAnalysis.Testing;
11+
using Xunit;
12+
using Test = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest<
13+
Datadog.Trace.Tools.Analyzers.SealedAnalyzer.SealedAnalyzer,
14+
AnalyzerCodeFixes::Datadog.Trace.Tools.Analyzers.SealedAnalyzer.SealedAnalyzerCodeFixProvider,
15+
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
16+
using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<
17+
Datadog.Trace.Tools.Analyzers.SealedAnalyzer.SealedAnalyzer,
18+
AnalyzerCodeFixes::Datadog.Trace.Tools.Analyzers.SealedAnalyzer.SealedAnalyzerCodeFixProvider,
19+
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
20+
21+
namespace Datadog.Trace.Tools.Analyzers.Tests.SealedAnalyzer;
22+
23+
public class SealedAnalyzerTests
24+
{
25+
public static readonly string[] NonPrivateModifiers = [string.Empty, "public ", "internal "];
26+
public static readonly string[] Modifiers = [..NonPrivateModifiers, "private ", "private protected ", "protected internal "];
27+
public static readonly string[] ClassTypes = ["class", "record"];
28+
29+
[Theory]
30+
[CombinatorialData]
31+
public async Task SealedClass_NoDiagnostic(
32+
[CombinatorialMemberData(nameof(Modifiers))] string modifier,
33+
[CombinatorialMemberData(nameof(ClassTypes))] string type)
34+
{
35+
var source = $$"""
36+
// Creating outer class so we can test private
37+
sealed class OuterClass
38+
{
39+
{{modifier}}sealed {{type}} TestClass
40+
{
41+
void TestMethod()
42+
{
43+
}
44+
}
45+
}
46+
""";
47+
48+
await Verifier.VerifyAnalyzerAsync(source); // no diagnostics expected
49+
}
50+
51+
[Theory]
52+
[CombinatorialData]
53+
public async Task NonClass_NoDiagnostic(
54+
[CombinatorialMemberData(nameof(Modifiers))] string modifier,
55+
[CombinatorialValues("struct", "enum", "static class", "interface")] string type)
56+
{
57+
var source = $$"""
58+
// Creating outer class so we can test private
59+
sealed class OuterClass
60+
{
61+
{{modifier}}{{type}} T
62+
{
63+
}
64+
}
65+
""";
66+
67+
await Verifier.VerifyAnalyzerAsync(source); // no diagnostics expected
68+
}
69+
70+
[Theory]
71+
[CombinatorialData]
72+
public async Task Interface_NoDiagnostic([CombinatorialMemberData(nameof(Modifiers))] string modifier)
73+
{
74+
var source = $$"""
75+
// Creating outer class so we can test private
76+
sealed class OuterClass
77+
{
78+
{{modifier}}interface TestInterface
79+
{
80+
void TestMethod()
81+
{
82+
}
83+
}
84+
}
85+
""";
86+
87+
await Verifier.VerifyAnalyzerAsync(source); // no diagnostics expected
88+
}
89+
90+
[Theory]
91+
[CombinatorialData]
92+
public async Task UnsealedClass_Diagnostic(
93+
[CombinatorialMemberData(nameof(NonPrivateModifiers))] string modifier,
94+
[CombinatorialMemberData(nameof(ClassTypes))] string type)
95+
{
96+
var source =
97+
$$"""
98+
99+
{{modifier}}{{type}} {|#0:C|}
100+
{
101+
private int _i;
102+
}
103+
""";
104+
105+
var fixedSource =
106+
$$"""
107+
108+
{{modifier}}sealed {{type}} C
109+
{
110+
private int _i;
111+
}
112+
""";
113+
var diagnostic = Verifier.Diagnostic(Diagnostics.DiagnosticId).WithArguments("C").WithLocation(0);
114+
await VerifyCodeFixAsync(source, diagnostic, fixedSource);
115+
}
116+
117+
[Theory]
118+
[CombinatorialData]
119+
public async Task UnsealedClassInNamespace_Diagnostic(
120+
[CombinatorialMemberData(nameof(NonPrivateModifiers))] string modifier,
121+
[CombinatorialMemberData(nameof(ClassTypes))] string type)
122+
{
123+
var source =
124+
$$"""
125+
namespace N
126+
{
127+
{{modifier}}{{type}} {|#0:C|}
128+
{
129+
private int _i;
130+
}
131+
}
132+
""";
133+
134+
var fixedSource =
135+
$$"""
136+
namespace N
137+
{
138+
{{modifier}}sealed {{type}} C
139+
{
140+
private int _i;
141+
}
142+
}
143+
""";
144+
var diagnostic = Verifier.Diagnostic(Diagnostics.DiagnosticId).WithArguments("C").WithLocation(0);
145+
await VerifyCodeFixAsync(source, diagnostic, fixedSource);
146+
}
147+
148+
[Theory]
149+
[CombinatorialData]
150+
public async Task UnsealedNestedClass_Diagnostic(
151+
[CombinatorialMemberData(nameof(NonPrivateModifiers))] string outerModifier,
152+
[CombinatorialMemberData(nameof(Modifiers))] string innerModifier,
153+
[CombinatorialMemberData(nameof(ClassTypes))] string type)
154+
{
155+
var source =
156+
$$"""
157+
{{outerModifier}}sealed {{type}} Outer
158+
{
159+
{{innerModifier}}{{type}} {|#0:C|}
160+
{
161+
}
162+
}
163+
""";
164+
165+
var fixedSource =
166+
$$"""
167+
{{outerModifier}}sealed {{type}} Outer
168+
{
169+
{{innerModifier}}sealed {{type}} C
170+
{
171+
}
172+
}
173+
""";
174+
var diagnostic = Verifier.Diagnostic(Diagnostics.DiagnosticId).WithArguments("C").WithLocation(0);
175+
await VerifyCodeFixAsync(source, diagnostic, fixedSource);
176+
}
177+
178+
[Theory]
179+
[CombinatorialData]
180+
public async Task UnsealedNestedClassTwoDeep_Diagnostic(
181+
[CombinatorialMemberData(nameof(NonPrivateModifiers))] string outerModifier,
182+
[CombinatorialMemberData(nameof(Modifiers))] string middleModifier,
183+
[CombinatorialMemberData(nameof(Modifiers))] string innerModifier,
184+
[CombinatorialMemberData(nameof(ClassTypes))] string type)
185+
{
186+
var source =
187+
$$"""
188+
{{outerModifier}}sealed {{type}} Outer
189+
{
190+
{{middleModifier}}sealed {{type}} Middle
191+
{
192+
{{innerModifier}}{{type}} {|#0:C|}
193+
{
194+
}
195+
}
196+
}
197+
""";
198+
199+
var fixedSource =
200+
$$"""
201+
{{outerModifier}}sealed {{type}} Outer
202+
{
203+
{{middleModifier}}sealed {{type}} Middle
204+
{
205+
{{innerModifier}}sealed {{type}} C
206+
{
207+
}
208+
}
209+
}
210+
""";
211+
var diagnostic = Verifier.Diagnostic(Diagnostics.DiagnosticId).WithArguments("C").WithLocation(0);
212+
await VerifyCodeFixAsync(source, diagnostic, fixedSource);
213+
}
214+
215+
[Theory]
216+
[CombinatorialData]
217+
public async Task DuckTypeAttributedType_NoDiagnostic(
218+
[CombinatorialMemberData(nameof(NonPrivateModifiers))] string modifier,
219+
[CombinatorialMemberData(nameof(ClassTypes))] string type)
220+
{
221+
var source =
222+
$$"""
223+
using System;
224+
using Datadog.Trace.DuckTyping;
225+
226+
[Datadog.Trace.DuckTyping.DuckType("Sometype", "SomeAssembly")]
227+
[DuckType("Sometype", "SomeAssembly")]
228+
{{modifier}}{{type}} C
229+
{
230+
}
231+
232+
namespace Datadog.Trace.DuckTyping
233+
{
234+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)]
235+
internal sealed class DuckTypeAttribute(string targetType, string targetAssembly) : Attribute
236+
{
237+
public string? TargetType { get; set; } = targetType;
238+
239+
public string? TargetAssembly { get; set; } = targetAssembly;
240+
}
241+
}
242+
""";
243+
244+
await Verifier.VerifyAnalyzerAsync(source); // no diagnostics expected
245+
}
246+
247+
[Theory]
248+
[CombinatorialData]
249+
public async Task ClassWithDerivedType_NoDiagnostic(
250+
[CombinatorialMemberData(nameof(NonPrivateModifiers))] string modifier,
251+
[CombinatorialMemberData(nameof(ClassTypes))] string type)
252+
{
253+
var source =
254+
$$"""
255+
{{modifier}}{{type}} B { }
256+
{{modifier}}sealed {{type}} D : B { }
257+
""";
258+
259+
await Verifier.VerifyAnalyzerAsync(source); // no diagnostics expected
260+
}
261+
262+
[Theory]
263+
[CombinatorialData]
264+
public async Task AbstractClass_NoDiagnostic([CombinatorialMemberData(nameof(NonPrivateModifiers))] string modifier)
265+
{
266+
var source =
267+
$$"""
268+
{{modifier}}abstract class B { }
269+
""";
270+
271+
await Verifier.VerifyAnalyzerAsync(source); // no diagnostics expected
272+
}
273+
274+
[Theory]
275+
[InlineData("B<T> { }", "D : B<int> { }")]
276+
[InlineData("B<T> { }", "D<T> : B<T> { }")]
277+
[InlineData("B<T, U> { }", "D<T> : B<T, int> { }")]
278+
public async Task GenericClass_WithSubclass_NoDiagnostic_CS(string baseClass, string derivedClass)
279+
{
280+
var source = $"""
281+
internal class {baseClass}
282+
internal sealed class {derivedClass}
283+
""";
284+
285+
await Verifier.VerifyAnalyzerAsync(source); // no diagnostics expected
286+
}
287+
288+
[Fact]
289+
public Task PartialClass_ReportedAndFixedAtAllLocations()
290+
{
291+
var test = new Test
292+
{
293+
CodeFixTestBehaviors = CodeFixTestBehaviors.SkipLocalDiagnosticCheck,
294+
TestState =
295+
{
296+
Sources =
297+
{
298+
@"internal class Base { }",
299+
@"internal partial class {|#0:Derived|} : Base { }",
300+
@"internal partial class {|#1:Derived|} : Base { }"
301+
},
302+
ExpectedDiagnostics =
303+
{
304+
Verifier.Diagnostic(Diagnostics.DiagnosticId).WithArguments("Derived").WithLocation(0).WithLocation(1),
305+
}
306+
},
307+
FixedState =
308+
{
309+
Sources =
310+
{
311+
@"internal class Base { }",
312+
@"internal sealed partial class Derived : Base { }",
313+
@"internal sealed partial class Derived : Base { }"
314+
}
315+
}
316+
};
317+
return test.RunAsync();
318+
}
319+
320+
private static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
321+
{
322+
// Code fixes are not yet supported for compilation end diagnostics,
323+
var test = new Test
324+
{
325+
CodeFixTestBehaviors = CodeFixTestBehaviors.SkipLocalDiagnosticCheck,
326+
TestCode = source,
327+
FixedCode = fixedSource,
328+
ExpectedDiagnostics = { expected },
329+
};
330+
331+
return test.RunAsync(CancellationToken.None);
332+
}
333+
}

0 commit comments

Comments
 (0)