-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
xunit/xunit#2120: Create analyzer for non-public constructors of type…
…s derived from FactAttribute (#175)
- Loading branch information
1 parent
2e69ef9
commit f38ec16
Showing
3 changed files
with
234 additions
and
1 deletion.
There are no files selected for viewing
166 changes: 166 additions & 0 deletions
166
...analyzers.tests/Analyzers/X1000/ConstructorsOnFactAttributeSubclassShouldBePublicTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Xunit; | ||
using Verify = CSharpVerifier<Xunit.Analyzers.ConstructorsOnFactAttributeSubclassShouldBePublic>; | ||
|
||
public class ConstructorsOnFactAttributeSubclassShouldBePublicTests | ||
{ | ||
[Fact] | ||
public async void DefaultConstructor_DoesNotTrigger() | ||
{ | ||
var source = @" | ||
using System; | ||
using Xunit; | ||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
internal sealed class CustomFactAttribute : FactAttribute { } | ||
public class Tests { | ||
[CustomFact] | ||
public void TestCustomFact() { } | ||
[Fact] | ||
public void TestFact() { } | ||
}"; | ||
|
||
await Verify.VerifyAnalyzer(source); | ||
} | ||
|
||
[Fact] | ||
public async void ParameterlessPublicConstructor_DoesNotTrigger() | ||
{ | ||
var source = @" | ||
using System; | ||
using Xunit; | ||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
internal sealed class CustomFactAttribute : FactAttribute { | ||
public CustomFactAttribute() { | ||
this.Skip = ""xxx""; | ||
} | ||
} | ||
public class Tests { | ||
[CustomFact] | ||
public void TestCustomFact() { } | ||
[Fact] | ||
public void TestFact() { } | ||
}"; | ||
|
||
await Verify.VerifyAnalyzer(source); | ||
} | ||
|
||
[Fact] | ||
public async void PublicConstructorWithParameters_DoesNotTrigger() | ||
{ | ||
var source = @" | ||
using System; | ||
using Xunit; | ||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
internal sealed class CustomFactAttribute : FactAttribute { | ||
public CustomFactAttribute(string skip) { | ||
this.Skip = skip; | ||
} | ||
} | ||
public class Tests { | ||
[CustomFact(""blah"")] | ||
public void TestCustomFact() { } | ||
[Fact] | ||
public void TestFact() { } | ||
}"; | ||
|
||
await Verify.VerifyAnalyzer(source); | ||
} | ||
|
||
[Fact] | ||
public async void PublicConstructorWithOtherConstructors_DoesNotTrigger() | ||
{ | ||
var source = @" | ||
using System; | ||
using Xunit; | ||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
internal sealed class CustomFactAttribute : FactAttribute { | ||
public CustomFactAttribute() { | ||
this.Skip = ""xxx""; | ||
} | ||
internal CustomFactAttribute(string skip) { | ||
this.Skip = skip; | ||
} | ||
} | ||
public class Tests { | ||
[CustomFact] | ||
public void TestCustomFact() { } | ||
[Fact] | ||
public void TestFact() { } | ||
}"; | ||
|
||
await Verify.VerifyAnalyzer(source); | ||
} | ||
|
||
[Fact] | ||
public async void InternalConstructor_Triggers() | ||
{ | ||
var source = @" | ||
using System; | ||
using Xunit; | ||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
internal sealed class CustomFactAttribute : FactAttribute { | ||
internal CustomFactAttribute(string skip, params int[] values) { } | ||
} | ||
public class Tests { | ||
[CustomFact(""Skip"", 42)] | ||
public void TestCustomFact() { } | ||
[Fact] | ||
public void TestFact() { } | ||
}"; | ||
var expected = | ||
Verify | ||
.Diagnostic() | ||
.WithSeverity(DiagnosticSeverity.Error) | ||
.WithSpan(11, 6, 11, 28) | ||
.WithArguments("CustomFactAttribute.CustomFactAttribute(string, params int[])"); | ||
|
||
await Verify.VerifyAnalyzer(source, expected); | ||
} | ||
|
||
[Fact] | ||
public async void ProtectedInternalConstructor_Triggers() | ||
{ | ||
var source = @" | ||
using System; | ||
using Xunit; | ||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
internal sealed class CustomFactAttribute : FactAttribute { | ||
protected internal CustomFactAttribute() { | ||
this.Skip = ""xxx""; | ||
} | ||
} | ||
public class Tests { | ||
[CustomFact] | ||
public void TestCustomFact() { } | ||
[Fact] | ||
public void TestFact() { } | ||
}"; | ||
var expected = | ||
Verify | ||
.Diagnostic() | ||
.WithSeverity(DiagnosticSeverity.Error) | ||
.WithSpan(13, 6, 13, 16) | ||
.WithArguments("CustomFactAttribute.CustomFactAttribute()"); | ||
|
||
await Verify.VerifyAnalyzer(source, expected); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
src/xunit.analyzers/X1000/ConstructorsOnFactAttributeSubclassShouldBePublic.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Xunit.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class ConstructorsOnFactAttributeSubclassShouldBePublic : XunitDiagnosticAnalyzer | ||
{ | ||
public ConstructorsOnFactAttributeSubclassShouldBePublic() : | ||
base(Descriptors.X1043_ConstructorOnFactAttributeSubclassShouldBePublic) | ||
{ } | ||
|
||
public override void AnalyzeCompilation( | ||
CompilationStartAnalysisContext context, | ||
XunitContext xunitContext) | ||
{ | ||
Guard.ArgumentNotNull(context); | ||
Guard.ArgumentNotNull(xunitContext); | ||
|
||
if (xunitContext.Core.FactAttributeType is null) | ||
return; | ||
|
||
context.RegisterSymbolAction(context => | ||
{ | ||
if (context.Symbol is not IMethodSymbol method) | ||
return; | ||
|
||
var attributes = method.GetAttributes(); | ||
foreach (var attribute in attributes) | ||
{ | ||
var attributeClass = attribute.AttributeClass; | ||
if (attributeClass is null) | ||
continue; | ||
|
||
if (!xunitContext.Core.FactAttributeType.IsAssignableFrom(attributeClass)) | ||
continue; | ||
|
||
var constructor = attribute.AttributeConstructor; | ||
if (constructor is null) | ||
continue; | ||
|
||
if (constructor.DeclaredAccessibility == Accessibility.ProtectedOrInternal | ||
|| constructor.DeclaredAccessibility == Accessibility.Internal) | ||
{ | ||
if (attribute.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken) is not AttributeSyntax attributeSyntax) | ||
return; | ||
|
||
context.ReportDiagnostic( | ||
Diagnostic.Create( | ||
Descriptors.X1043_ConstructorOnFactAttributeSubclassShouldBePublic, | ||
attributeSyntax.GetLocation(), | ||
constructor.ToDisplayString() | ||
) | ||
); | ||
} | ||
} | ||
}, SymbolKind.Method); | ||
} | ||
} |