Skip to content

Commit f7fb121

Browse files
committed
feat: Add diagnostics for source generators
1 parent 8323b3a commit f7fb121

File tree

3 files changed

+61
-9
lines changed

3 files changed

+61
-9
lines changed

docs/site/docs/extensions/bunit-generators.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,11 @@ internal partial class ThirdPartyStub { }
8282
```
8383

8484
Current limitations of this approach is that he stubbed type is not allowed to be nested inside the test class.
85+
86+
## Reported Diagnostics
87+
The generators will report a range of diagnostics to help understanding what issue is present. The following table shows the diagnostics that are reported.
88+
89+
| Diagnostic ID | Error | Severity | Description |
90+
| ------------- | ----------------------------------------------- | -------- | ---------------------------------------------------------------------------- |
91+
| BUNIT0001 | Stubbing nested classes ({0}) is not supported. | Warning | Types annotated with `ComponentStub` can not be nested inside another class. |
92+
| BUNIT0002 | Class ({0}) is not partial. | Warning | Types annotated with `ComponentStub` must be partial. |

src/bunit.generators/Web.Stubs/AttributeStubGenerator/ComponentStubAttributeGenerator.cs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Linq;
22
using System.Text;
33
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.CSharp;
45
using Microsoft.CodeAnalysis.CSharp.Syntax;
56
using Microsoft.CodeAnalysis.Text;
67

@@ -49,6 +50,7 @@ private static StubClassInfo GetStubClassInfo(GeneratorAttributeSyntaxContext co
4950
var namespaceName = stubbedType.ContainingNamespace.ToDisplayString();
5051
var className = context.TargetSymbol.Name;
5152
var visibility = context.TargetSymbol.DeclaredAccessibility.ToString().ToLower();
53+
var isPartial = ((ClassDeclarationSyntax)context.TargetNode).Modifiers.Any(SyntaxKind.PartialKeyword);
5254

5355
var originalTypeToStub = attribute.AttributeClass?.TypeArguments.FirstOrDefault();
5456
if (originalTypeToStub is null)
@@ -61,7 +63,9 @@ private static StubClassInfo GetStubClassInfo(GeneratorAttributeSyntaxContext co
6163
ClassName = className,
6264
Namespace = namespaceName,
6365
TargetType = originalTypeToStub,
64-
Visibility = visibility
66+
Visibility = visibility,
67+
IsNestedClass = context.TargetSymbol.ContainingType is not null,
68+
IsPartial = isPartial,
6569
};
6670
}
6771

@@ -70,6 +74,11 @@ private static StubClassInfo GetStubClassInfo(GeneratorAttributeSyntaxContext co
7074

7175
private static void Execute(StubClassInfo classInfo, SourceProductionContext context)
7276
{
77+
if (CheckDiagnostics(classInfo, context))
78+
{
79+
return;
80+
}
81+
7382
var hasSomethingToStub = false;
7483
var targetTypeSymbol = (INamedTypeSymbol)classInfo!.TargetType;
7584
var sourceBuilder = new StringBuilder(1000);
@@ -97,11 +106,7 @@ private static void Execute(StubClassInfo classInfo, SourceProductionContext con
97106
var propertyType = member.Type.ToDisplayString();
98107
var propertyName = member.Name;
99108

100-
var isParameterAttribute = member.GetAttributes().Any(attr =>
101-
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute");
102-
var attributeLine = isParameterAttribute
103-
? "\t[global::Microsoft.AspNetCore.Components.Parameter]"
104-
: "\t[global::Microsoft.AspNetCore.Components.CascadingParameter]";
109+
var attributeLine = GetAttributeLineForMember(member);
105110

106111
sourceBuilder.AppendLine(attributeLine);
107112
sourceBuilder.AppendLine($"\tpublic {propertyType} {propertyName} {{ get; set; }} = default!;");
@@ -113,6 +118,45 @@ private static void Execute(StubClassInfo classInfo, SourceProductionContext con
113118
{
114119
context.AddSource($"{classInfo.ClassName}.g.cs", sourceBuilder.ToString());
115120
}
121+
122+
static string GetAttributeLineForMember(ISymbol member)
123+
{
124+
var isParameterAttribute = member.GetAttributes().Any(attr =>
125+
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute");
126+
var attributeLine = isParameterAttribute
127+
? "\t[global::Microsoft.AspNetCore.Components.Parameter]"
128+
: "\t[global::Microsoft.AspNetCore.Components.CascadingParameter]";
129+
return attributeLine;
130+
}
131+
}
132+
133+
private static bool CheckDiagnostics(StubClassInfo classInfo, SourceProductionContext context)
134+
{
135+
if (classInfo.IsNestedClass)
136+
{
137+
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
138+
"BUNIT0001",
139+
"Stubbing nested classes is not supported",
140+
"Stubbing nested classes ({0}) is not supported.",
141+
"Bunit", DiagnosticSeverity.Warning, true),
142+
Location.None,
143+
classInfo.TargetType.ToDisplayString()));
144+
return true;
145+
}
146+
147+
if (!classInfo.IsPartial)
148+
{
149+
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
150+
"BUNIT0002",
151+
"Stubbing non-partial classes is not supported",
152+
"Class ({0}) is not partial.",
153+
"Bunit", DiagnosticSeverity.Warning, true),
154+
Location.None,
155+
classInfo.TargetType.ToDisplayString()));
156+
return true;
157+
}
158+
159+
return false;
116160
}
117161
}
118162

@@ -122,4 +166,6 @@ internal sealed class StubClassInfo
122166
public string Namespace { get; set; }
123167
public ITypeSymbol TargetType { get; set; }
124168
public string Visibility { get; set; }
169+
public bool IsNestedClass { get; set; }
170+
public bool IsPartial { get; set; }
125171
}

tests/bunit.generators.tests/Web.Stub/AddStubGeneratorTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,4 @@ public void Generated_stub_via_attribute_has_same_parameters()
6464
}
6565

6666
[ComponentStub<ButtonComponent>]
67-
public partial class ButtonComponentStub
68-
{
69-
}
67+
public partial class ButtonComponentStub;

0 commit comments

Comments
 (0)