Skip to content

Commit bca7b8c

Browse files
committed
Merge branch 'main' into feature/UpdateDocumentation
# Conflicts: # README.md # src/MiniMock/Builders/Documentation.cs
2 parents c593546 + 824f9c4 commit bca7b8c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1823
-884
lines changed

.editorconfig

Lines changed: 56 additions & 56 deletions
Large diffs are not rendered by default.

src/MiniMock/AnalyzerReleases.Shipped.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
### New Rules
44

5-
Rule ID | Category | Severity | Notes
6-
--------|----------|----------|--------------------
7-
MM0002 | Usage | Error | Ref properties not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0002.md)
8-
MM0003 | Usage | Error | Ref return type not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0003.md)
9-
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)
10-
MM0005 | Usage | Error | Static abstract members not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0005.md)
11-
MM0006 | Usage | Error | Can not create mock for a sealed class, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0006.md)
5+
Rule ID | Category | Severity | Notes
6+
---------|----------|----------|------------------------------------------------------------------------------------------------------------------------------------------------
7+
MM0002 | Usage | Error | Ref properties not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0002.md)
8+
MM0003 | Usage | Error | Ref return type not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0003.md)
9+
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)
10+
MM0005 | Usage | Error | Static abstract members not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0005.md)
11+
MM0006 | Usage | Error | Can not create mock for a sealed class, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0006.md)
1212

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
### Removed Rules
22

3-
Rule ID | Category | Severity | Notes
4-
--------|----------|----------|--------------------
5-
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)
3+
Rule ID | Category | Severity | Notes
4+
---------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------
5+
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)

src/MiniMock/Builders/ClassBuilder.cs

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
1-
namespace MiniMock.Builders;
2-
31
using System;
42
using System.Collections.Generic;
53
using System.Linq;
64
using Microsoft.CodeAnalysis;
7-
5+
using MiniMock;
6+
using MiniMock.Builders;
7+
using MiniMock.Util;
8+
9+
/// <summary>
10+
/// ClassBuilder is responsible for generating mock classes for a given target symbol.
11+
/// </summary>
12+
/// <param name="target">The target symbol to build the mock class for.</param>
813
internal class ClassBuilder(ISymbol target)
914
{
15+
/// <summary>
16+
/// Array of builders for different member types.
17+
/// </summary>
18+
private static readonly ISymbolBuilder[] Builders = [new ConstructorBuilder(), new EventBuilder(), new MethodBuilder(), new PropertyBuilder(), new IndexBuilder()];
19+
20+
/// <summary>
21+
/// Filter to determine if a member's accessibility is public or protected.
22+
/// </summary>
23+
private readonly Func<Accessibility, bool> accessibilityFilter = accessibility => accessibility == Accessibility.Public || accessibility == Accessibility.Protected;
24+
25+
/// <summary>
26+
/// Builds a mock class for the given symbol.
27+
/// </summary>
28+
/// <param name="symbol">The symbol to build the mock class for.</param>
29+
/// <returns>The generated mock class as a string.</returns>
1030
public static string Build(ISymbol symbol) =>
1131
new ClassBuilder(symbol).BuildClass();
1232

33+
/// <summary>
34+
/// Generates the mock class code.
35+
/// </summary>
36+
/// <returns>The generated mock class as a string.</returns>
1337
private string BuildClass()
1438
{
1539
if (target.IsSealed && target is INamedTypeSymbol symbol)
@@ -35,6 +59,8 @@ private string BuildClass()
3559

3660
var documentationName = fullName.Replace("<", "{").Replace(">", "}");
3761

62+
var hasConstructors = ((INamedTypeSymbol)target).Constructors.Any(c => this.accessibilityFilter(c.DeclaredAccessibility));
63+
3864
builder.Add($$"""
3965
// Generated by MiniMock on {{DateTime.Now}}
4066
#nullable enable
@@ -47,29 +73,32 @@ namespace {{interfaceNamespace}}
4773
internal class {{name}} : {{fullName}} {{constraints}}
4874
{
4975
->
50-
internal protected MockOf_{{target.Name}}(System.Action<Config>? config = null) {
51-
var result = new Config(this);
52-
config = config ?? new System.Action<Config>(t => { });
53-
config.Invoke(result);
54-
_config = result;
55-
}
56-
57-
public static {{fullName}} Create(System.Action<Config>? config = null) => new {{name}}(config);
58-
5976
private Config _config { get; }
6077
internal void GetConfig(out Config config) => config = _config;
6178
79+
/// <summary>
80+
/// Configuration class for the mock.
81+
/// </summary>
6282
internal partial class Config
6383
{
6484
private readonly {{name}} target;
6585
86+
/// <summary>
87+
/// Initializes a new instance of the <see cref="Config"/> class.
88+
/// </summary>
89+
/// <param name="target">The target mock class.</param>
6690
public Config({{name}} target)
6791
{
6892
this.target = target;
6993
}
7094
}
7195
""");
7296

97+
if (!hasConstructors)
98+
{
99+
builder.Add(ConstructorBuilder.BuildEmptyConstructor(target));
100+
}
101+
73102
this.BuildMembers(builder);
74103

75104
builder.Add("""
@@ -82,9 +111,13 @@ public Config({{name}} target)
82111
return builder.ToString();
83112
}
84113

114+
/// <summary>
115+
/// Builds the members of the mock class.
116+
/// </summary>
117+
/// <param name="builder">The code builder to add the members to.</param>
85118
private void BuildMembers(CodeBuilder builder)
86119
{
87-
var memberCandidates = new List<ISymbol>(((INamedTypeSymbol)target).GetMembers());
120+
var memberCandidates = new List<ISymbol>(((INamedTypeSymbol)target).GetMembers().Where(t => this.accessibilityFilter(t.DeclaredAccessibility)));
88121

89122
if (((INamedTypeSymbol)target).TypeKind == TypeKind.Interface)
90123
{
@@ -96,24 +129,19 @@ private void BuildMembers(CodeBuilder builder)
96129
foreach (var members in memberGroups)
97130
{
98131
var symbol = members.First();
99-
switch (symbol)
132+
var wasBuild = Builders.FirstOrDefault(b => b.TryBuild(builder, members));
133+
if (wasBuild == null)
100134
{
101-
case IEventSymbol:
102-
EventBuilder.BuildEvents(builder, members.OfType<IEventSymbol>());
103-
break;
104-
case IMethodSymbol { MethodKind: MethodKind.Ordinary }:
105-
MethodBuilder.BuildMethods(builder, members.OfType<IMethodSymbol>());
106-
break;
107-
case IPropertySymbol { IsIndexer: false }:
108-
PropertyBuilder.BuildProperties(builder, members.OfType<IPropertySymbol>());
109-
break;
110-
case IPropertySymbol { IsIndexer: true }:
111-
IndexBuilder.BuildIndexes(builder, members.OfType<IPropertySymbol>());
112-
break;
135+
builder.Add("// Ignored " + symbol.Kind + " " + symbol);
113136
}
114137
}
115138
}
116139

140+
/// <summary>
141+
/// Adds inherited interfaces to the member candidates list.
142+
/// </summary>
143+
/// <param name="memberCandidates">The list of member candidates.</param>
144+
/// <param name="namedTypeSymbol">The named type symbol to add inherited interfaces from.</param>
117145
private void AddInheritedInterfaces(List<ISymbol> memberCandidates, INamedTypeSymbol namedTypeSymbol)
118146
{
119147
var allInterfaces = namedTypeSymbol.AllInterfaces;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
namespace MiniMock.Builders;
2+
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using Microsoft.CodeAnalysis;
6+
using Util;
7+
8+
/// <summary>
9+
/// Represents a builder for constructing mock constructors.
10+
/// </summary>
11+
internal class ConstructorBuilder : ISymbolBuilder
12+
{
13+
/// <summary>
14+
/// Tries to build constructors for the given symbols.
15+
/// </summary>
16+
/// <param name="builder">The code builder to add the constructors to.</param>
17+
/// <param name="symbols">The symbols to build constructors for.</param>
18+
/// <returns>True if constructors were built; otherwise, false.</returns>
19+
public bool TryBuild(CodeBuilder builder, IGrouping<string, ISymbol> symbols)
20+
{
21+
var first = symbols.First();
22+
if (first is IMethodSymbol { MethodKind: MethodKind.Constructor })
23+
{
24+
return BuildConstructors(builder, first.ContainingSymbol, symbols.OfType<IMethodSymbol>());
25+
}
26+
27+
return false;
28+
}
29+
30+
/// <summary>
31+
/// Builds constructors for the specified target and adds them to the code builder.
32+
/// </summary>
33+
/// <param name="builder">The code builder to add the constructors to.</param>
34+
/// <param name="target">The target symbol for which to build constructors.</param>
35+
/// <param name="constructors">The constructors to build.</param>
36+
/// <returns>True if constructors were built; otherwise, false.</returns>
37+
private static bool BuildConstructors(CodeBuilder builder, ISymbol target, IEnumerable<IMethodSymbol> constructors)
38+
{
39+
var fullName = target.ToString();
40+
var name = "MockOf_" + target.Name;
41+
42+
var typeArguments = ((INamedTypeSymbol)target).TypeArguments;
43+
if (typeArguments.Length > 0)
44+
{
45+
var types = string.Join(", ", typeArguments.Select(t => t.Name));
46+
name = $"MockOf_{target.Name}<{types}>";
47+
}
48+
49+
builder.Add("#region Constructors");
50+
51+
foreach (var constructor in constructors)
52+
{
53+
var parameterList = constructor.Parameters.ToString(p => $"{p.Type} {p.Name}, ", "");
54+
var argumentList = constructor.Parameters.ToString(p => p.Name);
55+
56+
var parameterNames = constructor.Parameters.ToString(p => p.Name + ", ", "");
57+
58+
builder.Add($$"""
59+
internal protected MockOf_{{target.Name}}({{parameterList}}System.Action<Config>? config = null) : base({{argumentList}}) {
60+
var result = new Config(this);
61+
config = config ?? new System.Action<Config>(t => { });
62+
config.Invoke(result);
63+
_config = result;
64+
}
65+
66+
public static {{fullName}} Create({{parameterList}}System.Action<Config>? config = null) => new {{name}}({{parameterNames}}config);
67+
""");
68+
}
69+
70+
builder.Add("#endregion");
71+
72+
return true;
73+
}
74+
75+
/// <summary>
76+
/// Builds an empty constructor for the specified target.
77+
/// </summary>
78+
/// <param name="target">The target symbol for which to build an empty constructor.</param>
79+
/// <returns>A code builder containing the empty constructor.</returns>
80+
public static CodeBuilder BuildEmptyConstructor(ISymbol target)
81+
{
82+
var fullName = target.ToString();
83+
var name = "MockOf_" + target.Name;
84+
85+
var typeArguments = ((INamedTypeSymbol)target).TypeArguments;
86+
if (typeArguments.Length > 0)
87+
{
88+
var types = string.Join(", ", typeArguments.Select(t => t.Name));
89+
name = $"MockOf_{target.Name}<{types}>";
90+
}
91+
92+
CodeBuilder builder = new();
93+
94+
builder.Add($$"""
95+
#region Constructor
96+
97+
internal protected MockOf_{{target.Name}}(System.Action<Config>? config = null) {
98+
var result = new Config(this);
99+
config = config ?? new System.Action<Config>(t => { });
100+
config.Invoke(result);
101+
_config = result;
102+
}
103+
104+
public static {{fullName}} Create(System.Action<Config>? config = null) => new {{name}}(config);
105+
106+
#endregion
107+
""");
108+
109+
return builder;
110+
}
111+
}

src/MiniMock/Builders/Documentation.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ namespace MiniMock.Builders;
33
internal static class Documentation
44
{
55
internal const string CallBack = "Configures the mock to execute the specified action when the method matching the signature is called.";
6-
internal const string AcceptAny = "Configures the mock to accept any call to the method ignoring all parameters.";
6+
7+
internal const string AcceptAny = "Configures the mock to accept any call to the method.";
8+
79
internal const string ThrowsException = "Configures the mock to throw the specified exception when the method is called.";
8-
internal const string SpecificValue = "Configures the mock to return the specific value ignoring all parameters.";
10+
11+
internal const string SpecificValue = "Configures the mock to return the specific value.";
12+
913
internal const string SpecificValueList = "Configures the mock to return the consecutive values from an enumeration of values.";
10-
internal const string GenericTaskObject = "Configures the mock to return the specific value wrapped in a task object ignoring all parameters.";
14+
15+
internal const string GenericTaskObject = "Configures the mock to return the specific value in a task object.";
16+
1117
internal const string GenericTaskFunction = "Configures the mock to call the specified function and return the value wrapped in a task object when the method matching the signature is called.";
1218
}

0 commit comments

Comments
 (0)