Skip to content

Commit a1ed7a1

Browse files
authored
feat: generics (#11)
* feat(Generics): remove UnsupportedGenericType diagnostic and test * feat(Generics): add failing test and augment TypeData class * chore: move warning in FullyQualifiedTypeClass.Student * feat(Generics): add generic type parameters to builder class * improve(Modifiers) * feat(Generics): generic fluent API classes * test: GenericClassPrivateConstructor * refactor: rename GenericsInfo to GenericInfo * test: add failing test GenericClassWithConstraints * feat(Generics): add GenericTypeConstraint class * refactor(GenericInfo): make constructor internal * refactor: move generic classes to separate folder * feat(Generics): GenericConstraints and GenericTypeParameter abstractions * feat(Generics): class generics work * feat: split implemented interfaces across several lines * test: add GenericClassWithGenericMethod and GenericMethodWithConstraintsClass tests * docs(Readme): remove youtube and twitter reference * refactor: introduce Generics class * feat(Generics): add GenericInfo to MethodSymbolInfo * feat(Generics): generic methods * refactor: rename GenericsInfo to GenericInfo * fix(GenericConstraints): remove unused property ParametersWithConstraints * refactor: add GenericConstraintClause class * fix: formatting * refactor: memberToSetMemberCode and methodToCallMethodCode (wip) * refactor: introduce class InnerBodyCreationDelegates * docs: improve clarifying comments * feat(Generics): method overloads * test: improve GenericClassWithGenericMethod test * feat(Generics): get generic method via reflection * test: CanExecuteGenericClassWithGenericMethods * fix: MakeGenericMethod before Invoke for generic methods * test: CanExecuteGenericClassWithOverloadedGenericMethod * test: CanExecuteGenericClassWithPrivateOverloadedGenericMethod * test: rerun tests with new generated GetMethod code for private methods * test: detect duplicate overloaded methods * fix(InnerBodyCreationDelegates): don't compute method identities * improve: add blank lines between interface method signatures * fix: formatting * docs: add fluent api logo * chore: increase nuget versions to 1.2.0
1 parent 7900286 commit a1ed7a1

File tree

137 files changed

+3507
-309
lines changed

Some content is hidden

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

137 files changed

+3507
-309
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Fluent APIs in C#
22

3+
![M31.FluentApi logo](media/fluent-api-logo-256.jpg)
4+
35
Everybody wants to use fluent APIs but writing them is tedious. With this library providing fluent APIs for your classes becomes a breeze. Simply annotate them with attributes and the source code for the fluent API will be generated. The fluent API library leverages incremental source code generation at development time and your IDE will offer you the corresponding code completion immediately.
46

57
The generated code follows the builder design pattern and allows you to construct objects step by step. This approach avoids big constructors and results in very readable code.
@@ -9,20 +11,18 @@ The generated code follows the builder design pattern and allows you to construc
911
[![version](https://img.shields.io/nuget/v/M31.FluentApi)](https://www.nuget.org/packages/M31.FluentApi/)
1012
[![CI](https://github.com/m31coding/M31.FluentAPI/actions/workflows/ci.yml/badge.svg)](https://github.com/m31coding/M31.FluentAPI/actions/workflows/ci.yml)
1113
[![m31coding](https://img.shields.io/badge/www-m31coding.com-34345B)](https://www.m31coding.com)
12-
[![youtube](https://img.shields.io/badge/youtube-kevin%20schaal-FF0000.svg)](https://www.youtube.com/channel/UC6CZ_Bcyql1kfHZvx9W85mA)
13-
[![twitter](https://img.shields.io/badge/[email protected])](https://twitter.com/m31coding)
1414

1515
Accompanying blog post: [www.m31coding.com>blog>fluent-api](https://www.m31coding.com/blog/fluent-api.html)
1616

1717
## Installing via NuGet
1818

19-
Install the latest version of the package `M31.FluentAPI` via your IDE or use the package manager console:
19+
Install the latest version of the package `M31.FluentApi` via your IDE or use the package manager console:
2020

2121
```
2222
PM> Install-Package M31.FluentApi
2323
```
2424

25-
A package reference will be added to your `csproj` file. Moreover, since this library provides code via source code generation, consumers of your project don't need the reference to `M31.FluentAPI`. Therefore, it is recommended to use the `PrivateAssets` metadata tag:
25+
A package reference will be added to your `csproj` file. Moreover, since this library provides code via source code generation, consumers of your project don't need the reference to `M31.FluentApi`. Therefore, it is recommended to use the `PrivateAssets` metadata tag:
2626

2727
```xml
2828
<PackageReference Include="M31.FluentApi" Version="1.1.0" PrivateAssets="all"/>

media/fluent-api-logo-128.jpg

9.88 KB
Loading

media/fluent-api-logo-256.jpg

27.4 KB
Loading

src/M31.FluentApi.Generator/AnalyzerReleases.Shipped.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,13 @@ M31FA018 | M31.Usage | Error | Generic types are not supported
3131
Rule ID | Category | Severity | Notes
3232
--------|----------|----------|-------
3333
M31FA019 | M31.Usage | Error | Conflicting control attributes
34-
M31FA020 | M31.Usage | Error | Missing builder step
34+
M31FA020 | M31.Usage | Error | Missing builder step
35+
36+
37+
## Release 1.2.0
38+
39+
### Removed Rules
40+
41+
Rule ID | Category | Severity | Notes
42+
--------|----------|----------|-------
43+
M31FA018 | M31.Usage | Error | Generic types are not supported

src/M31.FluentApi.Generator/CodeBuilding/Class.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal class Class : ICode
1111
internal Class(string name)
1212
{
1313
Name = name;
14+
Generics = new Generics();
1415
Modifiers = new Modifiers();
1516
fields = new List<Field>();
1617
methods = new List<Method>();
@@ -21,18 +22,29 @@ internal Class(string name)
2122

2223
internal string Name { get; }
2324

25+
internal Generics Generics { get; }
2426
internal Modifiers Modifiers { get; }
2527
internal IReadOnlyCollection<Field> Fields => fields;
2628
internal IReadOnlyCollection<Method> Methods => methods;
2729
internal IReadOnlyCollection<Property> Properties => properties;
2830
internal IReadOnlyCollection<string> Interfaces => interfaces;
2931
internal IReadOnlyCollection<ICode> Definitions => definitions;
3032

33+
internal void AddGenericParameter(string parameter, IEnumerable<string> constraints)
34+
{
35+
Generics.AddGenericParameter(parameter, constraints);
36+
}
37+
3138
internal void AddModifiers(params string[] modifiers)
3239
{
3340
Modifiers.Add(modifiers);
3441
}
3542

43+
internal void AddModifiers(IEnumerable<string> modifiers)
44+
{
45+
Modifiers.Add(modifiers);
46+
}
47+
3648
internal void AddField(Field field)
3749
{
3850
fields.Add(field);
@@ -63,16 +75,37 @@ public CodeBuilder AppendCode(CodeBuilder codeBuilder)
6375
codeBuilder
6476
.StartLine()
6577
.Append(Modifiers)
66-
.Append($"class {Name}");
78+
.Append($"class {Name}")
79+
.Append(Generics.Parameters);
6780

68-
if (interfaces.Count > 0)
81+
if (interfaces.Count == 1)
6982
{
7083
codeBuilder.Append(" : ");
71-
codeBuilder.Append(interfaces, ", ");
84+
codeBuilder.Append(interfaces[0]);
85+
}
86+
87+
if (interfaces.Count > 1)
88+
{
89+
codeBuilder.Append(" :");
90+
codeBuilder.EndLine();
91+
codeBuilder.Indent();
92+
93+
foreach (string @interface in interfaces.Take(interfaces.Count - 1))
94+
{
95+
codeBuilder.AppendLine($"{@interface},");
96+
}
97+
98+
codeBuilder.AppendLine(interfaces[interfaces.Count - 1]);
99+
100+
codeBuilder.Unindent();
72101
}
73102

74103
return codeBuilder
75104
.EndLine()
105+
.Indent()
106+
.Append(Generics.Constraints)
107+
.EndLine()
108+
.Unindent()
76109
.OpenBlock()
77110
.Append(fields)
78111
.BlankLine()

src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ internal CodeBuilder BlankLine()
4242
return this;
4343
}
4444

45+
internal CodeBuilder Indent()
46+
{
47+
IndentationLevel++;
48+
return this;
49+
}
50+
51+
internal CodeBuilder Unindent()
52+
{
53+
IndentationLevel--;
54+
return this;
55+
}
56+
4557
internal CodeBuilder OpenBlock()
4658
{
4759
AppendLine("{");
@@ -78,9 +90,24 @@ internal CodeBuilder Append(string? code, bool condition)
7890
return this;
7991
}
8092

93+
internal CodeBuilder Append(Func<string?> createCode, bool condition)
94+
{
95+
if (condition)
96+
{
97+
Append(createCode());
98+
}
99+
100+
return this;
101+
}
102+
81103
internal CodeBuilder Append(IEnumerable<string> code, string separator)
82104
{
83-
return Append(code, separator, c => Append(c));
105+
return AppendSeparated(code, () => stringBuilder.Append(separator), c => Append(c));
106+
}
107+
108+
internal CodeBuilder AppendNewLineSeparated(IEnumerable<string> code)
109+
{
110+
return AppendSeparated(code, () => EndLine(), c => Append(GetIndentation()).Append(c));
84111
}
85112

86113
internal CodeBuilder Append(ICode? code)
@@ -126,10 +153,15 @@ internal CodeBuilder AppendWithBlankLines(IEnumerable<ICode> code)
126153

127154
internal CodeBuilder Append(IEnumerable<ICode> code, string separator)
128155
{
129-
return Append(code, separator, c => c.AppendCode(this));
156+
return AppendSeparated(code, () => stringBuilder.Append(separator), c => c.AppendCode(this));
157+
}
158+
159+
internal CodeBuilder AppendNewLineSeparated(IEnumerable<ICode> code)
160+
{
161+
return AppendSeparated(code, () => EndLine(), c => Append(GetIndentation()).Append(c));
130162
}
131163

132-
private CodeBuilder Append<T>(IEnumerable<T> code, string separator, Action<T> append)
164+
private CodeBuilder AppendSeparated<T>(IEnumerable<T> code, Action separationAction, Action<T> append)
133165
{
134166
using IEnumerator<T> en = code.GetEnumerator();
135167

@@ -147,7 +179,7 @@ private CodeBuilder Append<T>(IEnumerable<T> code, string separator, Action<T> a
147179

148180
do
149181
{
150-
stringBuilder.Append(separator);
182+
separationAction();
151183
append(en.Current);
152184
}
153185
while (en.MoveNext());

src/M31.FluentApi.Generator/CodeBuilding/Field.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,43 @@ internal Field(string type, string name)
77
Type = type;
88
Name = name;
99
Modifiers = new Modifiers();
10+
GenericParameters = new GenericParameters();
1011
}
1112

1213
internal string Type { get; }
1314
internal string Name { get; }
1415

16+
internal GenericParameters GenericParameters { get; }
1517
internal Modifiers Modifiers { get; }
1618

1719
internal void AddModifiers(params string[] modifiers)
1820
{
1921
Modifiers.Add(modifiers);
2022
}
2123

22-
private static string AccessModifierToString(string accessModifier)
24+
internal void AddModifiers(IEnumerable<string> modifiers)
2325
{
24-
return accessModifier == string.Empty ? accessModifier : $"{accessModifier} ";
26+
Modifiers.Add(modifiers);
27+
}
28+
29+
internal void AddGenericParameters(params string[] parameters)
30+
{
31+
GenericParameters.Add(parameters);
32+
}
33+
34+
internal void AddGenericParameters(IEnumerable<string> parameters)
35+
{
36+
GenericParameters.Add(parameters);
2537
}
2638

2739
public CodeBuilder AppendCode(CodeBuilder codeBuilder)
2840
{
2941
return codeBuilder
3042
.StartLine()
3143
.Append(Modifiers)
32-
.Append($"{Type} {Name};")
44+
.Append(Type)
45+
.Append(GenericParameters)
46+
.Append($" {Name};")
3347
.EndLine();
3448
}
3549
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace M31.FluentApi.Generator.CodeBuilding;
2+
3+
internal class GenericConstraintClause : ICode
4+
{
5+
internal GenericConstraintClause(string parameter, IReadOnlyCollection<string> constraints)
6+
{
7+
Parameter = parameter;
8+
Constraints = constraints;
9+
}
10+
11+
internal string Parameter { get; }
12+
internal IReadOnlyCollection<string> Constraints { get; }
13+
14+
public CodeBuilder AppendCode(CodeBuilder codeBuilder)
15+
{
16+
return codeBuilder.Append($"where {Parameter} : {string.Join(", ", Constraints)}");
17+
}
18+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace M31.FluentApi.Generator.CodeBuilding;
2+
3+
internal class GenericConstraints : ICode
4+
{
5+
private readonly List<GenericConstraintClause> genericConstraintClauses;
6+
7+
internal GenericConstraints()
8+
{
9+
genericConstraintClauses = new List<GenericConstraintClause>();
10+
}
11+
12+
internal GenericConstraints(GenericConstraints constraints)
13+
{
14+
genericConstraintClauses = constraints.genericConstraintClauses.ToList();
15+
}
16+
17+
internal IReadOnlyCollection<GenericConstraintClause> GenericConstraintClauses => genericConstraintClauses;
18+
internal int Count => genericConstraintClauses.Count;
19+
20+
internal void Add(string parameter, IReadOnlyCollection<string> constraints)
21+
{
22+
genericConstraintClauses.Add(new GenericConstraintClause(parameter, constraints));
23+
}
24+
25+
public CodeBuilder AppendCode(CodeBuilder codeBuilder)
26+
{
27+
return codeBuilder.AppendNewLineSeparated(genericConstraintClauses);
28+
}
29+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace M31.FluentApi.Generator.CodeBuilding;
2+
3+
internal class GenericParameters : ICode
4+
{
5+
private readonly List<string> values;
6+
7+
internal GenericParameters(params string[] parameters)
8+
{
9+
values = parameters.ToList();
10+
}
11+
12+
internal GenericParameters(GenericParameters parameters)
13+
{
14+
values = parameters.values.ToList();
15+
}
16+
17+
internal IReadOnlyCollection<string> Values => values;
18+
19+
internal void Add(string parameter)
20+
{
21+
values.Add(parameter);
22+
}
23+
24+
internal void Add(params string[] parameters)
25+
{
26+
values.AddRange(parameters);
27+
}
28+
29+
internal void Add(IEnumerable<string> parameters)
30+
{
31+
values.AddRange(parameters);
32+
}
33+
34+
public CodeBuilder AppendCode(CodeBuilder codeBuilder)
35+
{
36+
return codeBuilder.Append(() => $"<{string.Join(", ", values)}>", values.Count > 0);
37+
}
38+
39+
public override string ToString()
40+
{
41+
return Values.Count == 0 ? string.Empty : $"<{string.Join(", ", values)}>";
42+
}
43+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace M31.FluentApi.Generator.CodeBuilding;
2+
3+
internal class Generics
4+
{
5+
internal Generics()
6+
{
7+
Parameters = new GenericParameters();
8+
Constraints = new GenericConstraints();
9+
}
10+
11+
internal Generics(Generics generics)
12+
{
13+
Parameters = new GenericParameters(generics.Parameters);
14+
Constraints = new GenericConstraints(generics.Constraints);
15+
}
16+
17+
internal GenericParameters Parameters { get; }
18+
internal GenericConstraints Constraints { get; }
19+
20+
internal void AddGenericParameter(string parameter, IEnumerable<string> constraints)
21+
{
22+
Parameters.Add(parameter);
23+
IReadOnlyCollection<string> constraintsCollection = constraints.ToArray();
24+
if (constraintsCollection.Count > 0)
25+
{
26+
Constraints.Add(parameter, constraintsCollection);
27+
}
28+
}
29+
}

src/M31.FluentApi.Generator/CodeBuilding/Interface.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public CodeBuilder AppendCode(CodeBuilder codeBuilder)
3030
return codeBuilder
3131
.AppendLine($"{AccessModifier} interface {Name}")
3232
.OpenBlock()
33-
.Append(methodSignatures)
33+
.AppendWithBlankLines(methodSignatures)
3434
.CloseBlock();
3535
}
3636
}

0 commit comments

Comments
 (0)