Skip to content

Commit 1599911

Browse files
committed
Exclude classes from assembly-level discovery
1 parent f8d33c1 commit 1599911

7 files changed

+87
-50
lines changed

Generators/Internal/KnownTypes.cs

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ public static class NamedParameters {
1515
}
1616
}
1717

18+
public static class GenerateMocksForTypesAttribute {
19+
public const string Name = "GenerateMocksForTypesAttribute";
20+
public const string NameWithoutAttribute = "GenerateMocksForTypes";
21+
22+
public static bool NamespaceMatches(INamespaceSymbol? @namespace) => @namespace is {
23+
Name: "SourceMock", ContainingNamespace: { IsGlobalNamespace: true }
24+
};
25+
}
26+
1827
public static class IMock {
1928
public const string FullName = "SourceMock.IMock";
2029
}

Generators/Internal/MockTargetDiscovery.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public IEnumerable<MockTargetMember> GetMembersToMock(MockTarget target) {
1717
#pragma warning restore HAA0502
1818

1919
var memberId = 1;
20-
foreach (var member in target.PotentiallyLoadedMembers ?? target.Type.GetMembers()) {
20+
foreach (var member in target.Type.GetMembers()) {
2121
seen.Add(member.Name);
2222

2323
if (GetTargetMember(member, memberId) is not {} discovered)
+1-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Immutable;
21
using Microsoft.CodeAnalysis;
32
using Roslyn.Utilities;
43

@@ -7,17 +6,14 @@ internal readonly struct MockTarget {
76
[PerformanceSensitive("")]
87
public MockTarget(
98
INamedTypeSymbol targetType,
10-
string targetTypeQualifiedName,
11-
ImmutableArray<ISymbol>? potentiallyLoadedMembers
9+
string targetTypeQualifiedName
1210
)
1311
{
1412
Type = targetType;
1513
FullTypeName = targetTypeQualifiedName;
16-
PotentiallyLoadedMembers = potentiallyLoadedMembers;
1714
}
1815

1916
public INamedTypeSymbol Type { get; }
2017
public string FullTypeName { get; }
21-
public ImmutableArray<ISymbol>? PotentiallyLoadedMembers { get; }
2218
}
2319
}

Generators/MockGenerator.cs

+47-38
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ internal class MockGenerator : ISourceGenerator, IDisposable {
1515
private static class DiagnosticDescriptors {
1616
#pragma warning disable RS2008 // Enable analyzer release tracking
1717
public static readonly DiagnosticDescriptor SingleMockFailedToGenerate = new(
18-
"SM0001", "Failed to generate a single mock", "Failed to generate mock for {0}: {1}",
18+
"SourceMock001", "Failed to generate a single mock", "Failed to generate mock for {0}: {1}",
1919
"Generation", DiagnosticSeverity.Warning, isEnabledByDefault: true
2020
);
2121
public static readonly DiagnosticDescriptor RegexPatternFailedToParse = new(
22-
"SM0011", "Regex pattern is not a valid regex", "Regex pattern \"{0}\" cannot be parsed: {1}",
22+
"SourceMock002", "Regex pattern is not a valid regex", "Regex pattern \"{0}\" cannot be parsed: {1}",
2323
"Generation", DiagnosticSeverity.Error, isEnabledByDefault: true
2424
);
2525
#pragma warning restore RS2008 // Enable analyzer release tracking
@@ -44,13 +44,7 @@ public void Execute(GeneratorExecutionContext context) {
4444
var attributes = context.Compilation.Assembly.GetAttributes();
4545
GeneratorLog.Log("Get attributes finished");
4646
foreach (var attribute in attributes) {
47-
if (attribute.AttributeClass is not { Name: KnownTypes.GenerateMocksForAssemblyOfAttribute.Name } attributeClass)
48-
continue;
49-
50-
if (!KnownTypes.GenerateMocksForAssemblyOfAttribute.NamespaceMatches(attributeClass.ContainingNamespace))
51-
continue;
52-
53-
GenerateMocksForAttributeTargetAssembly(attribute, context);
47+
ProcessAssemblyAttribute(attribute, context);
5448
}
5549
}
5650
catch (Exception ex) {
@@ -60,6 +54,33 @@ public void Execute(GeneratorExecutionContext context) {
6054
GeneratorLog.Log("MockGenerator.Execute finished");
6155
}
6256

57+
private void ProcessAssemblyAttribute(AttributeData attribute, in GeneratorExecutionContext context) {
58+
if (attribute.AttributeClass is not {} attributeClass)
59+
return;
60+
61+
switch (attributeClass.Name) {
62+
case KnownTypes.GenerateMocksForAssemblyOfAttribute.Name:
63+
if (!KnownTypes.GenerateMocksForAssemblyOfAttribute.NamespaceMatches(attributeClass.ContainingNamespace))
64+
return;
65+
GenerateMocksForAttributeTargetAssembly(attribute, context);
66+
break;
67+
68+
case KnownTypes.GenerateMocksForTypesAttribute.Name:
69+
if (!KnownTypes.GenerateMocksForTypesAttribute.NamespaceMatches(attributeClass.ContainingNamespace))
70+
return;
71+
72+
if (attribute.ConstructorArguments.ElementAtOrDefault(0) is not { Kind: TypedConstantKind.Array, Values: var typeofConstants })
73+
return;
74+
75+
foreach (var typeofConstant in typeofConstants) {
76+
if (typeofConstant is not { Value: INamedTypeSymbol type })
77+
continue;
78+
GenerateMockForType(new MockTarget(type, GetFullTypeName(type)), assemblyCacheBuilder: null, context);
79+
}
80+
break;
81+
}
82+
}
83+
6384
[PerformanceSensitive("")]
6485
private void GenerateMocksForAttributeTargetAssembly(AttributeData attribute, in GeneratorExecutionContext context) {
6586
// intermediate code state? just in case
@@ -118,7 +139,9 @@ in GeneratorExecutionContext context
118139
#pragma warning restore HAA0401
119140
switch (member) {
120141
case INamedTypeSymbol type:
121-
GenerateMockForTypeIfApplicable(type, excludeRegex, assemblyCacheBuilder, context);
142+
if (!ShouldIncludeInMocksForAssembly(type, excludeRegex, out var fullName, context))
143+
continue;
144+
GenerateMockForType(new MockTarget(type, fullName!), assemblyCacheBuilder, context);
122145
break;
123146

124147
case INamespaceSymbol nested:
@@ -129,59 +152,45 @@ in GeneratorExecutionContext context
129152
}
130153

131154
[PerformanceSensitive("")]
132-
private void GenerateMockForTypeIfApplicable(
155+
private bool ShouldIncludeInMocksForAssembly(
133156
INamedTypeSymbol type,
134157
Regex? excludeRegex,
135-
ImmutableArray<(string, SourceText)>.Builder assemblyCacheBuilder,
158+
out string? fullName,
136159
in GeneratorExecutionContext context
137160
) {
138-
if (!ShouldGenerateMockForTypeKind(type, out var potentiallyLoadedMembers))
139-
return;
161+
fullName = null;
162+
if (type.TypeKind != TypeKind.Interface)
163+
return false;
140164

141165
if (type.DeclaredAccessibility != Accessibility.Public) {
142166
var isVisibleInternal = type.DeclaredAccessibility == Accessibility.Internal
143167
&& type.ContainingAssembly.GivesAccessTo(context.Compilation.Assembly);
144168
if (!isVisibleInternal)
145-
return;
169+
return false;
146170
}
147171

148-
var fullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
172+
fullName = GetFullTypeName(type);
149173
if (excludeRegex != null && excludeRegex.IsMatch(fullName))
150-
return;
174+
return false;
151175

152-
GenerateMockForType(new MockTarget(type, fullName, potentiallyLoadedMembers), assemblyCacheBuilder, context);
176+
return true;
153177
}
154178

155179
[PerformanceSensitive("")]
156-
private bool ShouldGenerateMockForTypeKind(INamedTypeSymbol type, out ImmutableArray<ISymbol>? members) {
157-
members = null;
158-
if (type.TypeKind == TypeKind.Interface)
159-
return true;
160-
161-
if (type.TypeKind == TypeKind.Class) {
162-
if (type.IsSealed)
163-
return false;
164-
165-
members = type.GetMembers();
166-
foreach (var member in members) {
167-
if (member.IsVirtual || member.IsAbstract)
168-
return true;
169-
}
170-
}
171-
172-
return false;
180+
private static string GetFullTypeName(INamedTypeSymbol type) {
181+
return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
173182
}
174183

175184
[PerformanceSensitive("")]
176185
private void GenerateMockForType(
177186
MockTarget target,
178-
ImmutableArray<(string, SourceText)>.Builder assemblyCacheBuilder,
187+
ImmutableArray<(string, SourceText)>.Builder? assemblyCacheBuilder,
179188
in GeneratorExecutionContext context
180189
) {
181190
if (_mockedTypeCache.TryGetValue(target.Type, out var cached)) {
182191
GeneratorLog.Log("Using cached mock for type " + target.FullTypeName);
183192
context.AddSource(cached.name, cached.source);
184-
assemblyCacheBuilder.Add(cached);
193+
assemblyCacheBuilder?.Add(cached);
185194
return;
186195
}
187196

@@ -204,7 +213,7 @@ in GeneratorExecutionContext context
204213
var mockSource = SourceText.From(mockContent, Encoding.UTF8);
205214
context.AddSource(mockFileName, mockSource);
206215
_mockedTypeCache.TryAdd(target.Type, (mockFileName, mockSource));
207-
assemblyCacheBuilder.Add((mockFileName, mockSource));
216+
assemblyCacheBuilder?.Add((mockFileName, mockSource));
208217
}
209218

210219
public void Dispose() {

Tests/MockManifest.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using SourceMock;
22
using SourceMock.Tests.Interfaces;
33

4-
[assembly: GenerateMocksForAssemblyOf(typeof(IMockable), ExcludeRegex = "ExcludedInterface")]
4+
[assembly: GenerateMocksForAssemblyOf(typeof(IMockable), ExcludeRegex = "ExcludedInterface")]
5+
[assembly: GenerateMocksForTypes(typeof(AbstractClass))]

[Main]/GenerateMocksForAssemblyOfAttribute.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
using System.Text.RegularExpressions;
33

44
namespace SourceMock {
5-
/// <summary>Requests mock generation for a specific assembly.</summary>
5+
/// <summary>
6+
/// Requests mock generation for all interfaces in the specified assembly.
7+
/// </summary>
68
/// <remarks>
7-
/// When this attribute is used in a test assembly, SourceMock will generate
8-
/// mocks for all interfaces in the target assembly and include those into
9-
/// the test assembly.
9+
/// When this attribute is used in a test project, SourceMock will generate
10+
/// mocks for all interfaces in the target assembly, and include those into
11+
/// the test project.
1012
/// </remarks>
1113
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
12-
public class GenerateMocksForAssemblyOfAttribute : Attribute {
14+
public class GenerateMocksForAssemblyOfAttribute
15+
: Attribute {
1316
/// <summary>
1417
/// Initializes a new instance of the <see cref="GenerateMocksForAssemblyOfAttribute" /> class.
1518
/// </summary>
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
3+
namespace SourceMock {
4+
/// <summary>
5+
/// Requests mock generation for specified classes or interfaces.
6+
/// </summary>
7+
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
8+
public class GenerateMocksForTypesAttribute : Attribute {
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="GenerateMocksForTypesAttribute" /> class.
11+
/// </summary>
12+
/// <param name="types">Types to generate mocks for.</param>
13+
public GenerateMocksForTypesAttribute(params Type[] types) {
14+
Types = types;
15+
}
16+
17+
internal Type[] Types { get; }
18+
}
19+
}

0 commit comments

Comments
 (0)