Skip to content

Commit

Permalink
feat: generics (#11)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
m31coding authored May 10, 2024
1 parent 7900286 commit a1ed7a1
Show file tree
Hide file tree
Showing 137 changed files with 3,507 additions and 309 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Fluent APIs in C#

![M31.FluentApi logo](media/fluent-api-logo-256.jpg)

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.

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.
Expand All @@ -9,20 +11,18 @@ The generated code follows the builder design pattern and allows you to construc
[![version](https://img.shields.io/nuget/v/M31.FluentApi)](https://www.nuget.org/packages/M31.FluentApi/)
[![CI](https://github.com/m31coding/M31.FluentAPI/actions/workflows/ci.yml/badge.svg)](https://github.com/m31coding/M31.FluentAPI/actions/workflows/ci.yml)
[![m31coding](https://img.shields.io/badge/www-m31coding.com-34345B)](https://www.m31coding.com)
[![youtube](https://img.shields.io/badge/youtube-kevin%20schaal-FF0000.svg)](https://www.youtube.com/channel/UC6CZ_Bcyql1kfHZvx9W85mA)
[![twitter](https://img.shields.io/badge/twitter-@m31coding-1DA1F2.svg)](https://twitter.com/m31coding)

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

## Installing via NuGet

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

```
PM> Install-Package M31.FluentApi
```

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:
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:

```xml
<PackageReference Include="M31.FluentApi" Version="1.1.0" PrivateAssets="all"/>
Expand Down
Binary file added media/fluent-api-logo-128.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/fluent-api-logo-256.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion src/M31.FluentApi.Generator/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,13 @@ M31FA018 | M31.Usage | Error | Generic types are not supported
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
M31FA019 | M31.Usage | Error | Conflicting control attributes
M31FA020 | M31.Usage | Error | Missing builder step
M31FA020 | M31.Usage | Error | Missing builder step


## Release 1.2.0

### Removed Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
M31FA018 | M31.Usage | Error | Generic types are not supported
39 changes: 36 additions & 3 deletions src/M31.FluentApi.Generator/CodeBuilding/Class.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal class Class : ICode
internal Class(string name)
{
Name = name;
Generics = new Generics();
Modifiers = new Modifiers();
fields = new List<Field>();
methods = new List<Method>();
Expand All @@ -21,18 +22,29 @@ internal Class(string name)

internal string Name { get; }

internal Generics Generics { get; }
internal Modifiers Modifiers { get; }
internal IReadOnlyCollection<Field> Fields => fields;
internal IReadOnlyCollection<Method> Methods => methods;
internal IReadOnlyCollection<Property> Properties => properties;
internal IReadOnlyCollection<string> Interfaces => interfaces;
internal IReadOnlyCollection<ICode> Definitions => definitions;

internal void AddGenericParameter(string parameter, IEnumerable<string> constraints)
{
Generics.AddGenericParameter(parameter, constraints);
}

internal void AddModifiers(params string[] modifiers)
{
Modifiers.Add(modifiers);
}

internal void AddModifiers(IEnumerable<string> modifiers)
{
Modifiers.Add(modifiers);
}

internal void AddField(Field field)
{
fields.Add(field);
Expand Down Expand Up @@ -63,16 +75,37 @@ public CodeBuilder AppendCode(CodeBuilder codeBuilder)
codeBuilder
.StartLine()
.Append(Modifiers)
.Append($"class {Name}");
.Append($"class {Name}")
.Append(Generics.Parameters);

if (interfaces.Count > 0)
if (interfaces.Count == 1)
{
codeBuilder.Append(" : ");
codeBuilder.Append(interfaces, ", ");
codeBuilder.Append(interfaces[0]);
}

if (interfaces.Count > 1)
{
codeBuilder.Append(" :");
codeBuilder.EndLine();
codeBuilder.Indent();

foreach (string @interface in interfaces.Take(interfaces.Count - 1))
{
codeBuilder.AppendLine($"{@interface},");
}

codeBuilder.AppendLine(interfaces[interfaces.Count - 1]);

codeBuilder.Unindent();
}

return codeBuilder
.EndLine()
.Indent()
.Append(Generics.Constraints)
.EndLine()
.Unindent()
.OpenBlock()
.Append(fields)
.BlankLine()
Expand Down
40 changes: 36 additions & 4 deletions src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ internal CodeBuilder BlankLine()
return this;
}

internal CodeBuilder Indent()
{
IndentationLevel++;
return this;
}

internal CodeBuilder Unindent()
{
IndentationLevel--;
return this;
}

internal CodeBuilder OpenBlock()
{
AppendLine("{");
Expand Down Expand Up @@ -78,9 +90,24 @@ internal CodeBuilder Append(string? code, bool condition)
return this;
}

internal CodeBuilder Append(Func<string?> createCode, bool condition)
{
if (condition)
{
Append(createCode());
}

return this;
}

internal CodeBuilder Append(IEnumerable<string> code, string separator)
{
return Append(code, separator, c => Append(c));
return AppendSeparated(code, () => stringBuilder.Append(separator), c => Append(c));
}

internal CodeBuilder AppendNewLineSeparated(IEnumerable<string> code)
{
return AppendSeparated(code, () => EndLine(), c => Append(GetIndentation()).Append(c));
}

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

internal CodeBuilder Append(IEnumerable<ICode> code, string separator)
{
return Append(code, separator, c => c.AppendCode(this));
return AppendSeparated(code, () => stringBuilder.Append(separator), c => c.AppendCode(this));
}

internal CodeBuilder AppendNewLineSeparated(IEnumerable<ICode> code)
{
return AppendSeparated(code, () => EndLine(), c => Append(GetIndentation()).Append(c));
}

private CodeBuilder Append<T>(IEnumerable<T> code, string separator, Action<T> append)
private CodeBuilder AppendSeparated<T>(IEnumerable<T> code, Action separationAction, Action<T> append)
{
using IEnumerator<T> en = code.GetEnumerator();

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

do
{
stringBuilder.Append(separator);
separationAction();
append(en.Current);
}
while (en.MoveNext());
Expand Down
20 changes: 17 additions & 3 deletions src/M31.FluentApi.Generator/CodeBuilding/Field.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,43 @@ internal Field(string type, string name)
Type = type;
Name = name;
Modifiers = new Modifiers();
GenericParameters = new GenericParameters();
}

internal string Type { get; }
internal string Name { get; }

internal GenericParameters GenericParameters { get; }
internal Modifiers Modifiers { get; }

internal void AddModifiers(params string[] modifiers)
{
Modifiers.Add(modifiers);
}

private static string AccessModifierToString(string accessModifier)
internal void AddModifiers(IEnumerable<string> modifiers)
{
return accessModifier == string.Empty ? accessModifier : $"{accessModifier} ";
Modifiers.Add(modifiers);
}

internal void AddGenericParameters(params string[] parameters)
{
GenericParameters.Add(parameters);
}

internal void AddGenericParameters(IEnumerable<string> parameters)
{
GenericParameters.Add(parameters);
}

public CodeBuilder AppendCode(CodeBuilder codeBuilder)
{
return codeBuilder
.StartLine()
.Append(Modifiers)
.Append($"{Type} {Name};")
.Append(Type)
.Append(GenericParameters)
.Append($" {Name};")
.EndLine();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace M31.FluentApi.Generator.CodeBuilding;

internal class GenericConstraintClause : ICode
{
internal GenericConstraintClause(string parameter, IReadOnlyCollection<string> constraints)
{
Parameter = parameter;
Constraints = constraints;
}

internal string Parameter { get; }
internal IReadOnlyCollection<string> Constraints { get; }

public CodeBuilder AppendCode(CodeBuilder codeBuilder)
{
return codeBuilder.Append($"where {Parameter} : {string.Join(", ", Constraints)}");
}
}
29 changes: 29 additions & 0 deletions src/M31.FluentApi.Generator/CodeBuilding/GenericConstraints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace M31.FluentApi.Generator.CodeBuilding;

internal class GenericConstraints : ICode
{
private readonly List<GenericConstraintClause> genericConstraintClauses;

internal GenericConstraints()
{
genericConstraintClauses = new List<GenericConstraintClause>();
}

internal GenericConstraints(GenericConstraints constraints)
{
genericConstraintClauses = constraints.genericConstraintClauses.ToList();
}

internal IReadOnlyCollection<GenericConstraintClause> GenericConstraintClauses => genericConstraintClauses;
internal int Count => genericConstraintClauses.Count;

internal void Add(string parameter, IReadOnlyCollection<string> constraints)
{
genericConstraintClauses.Add(new GenericConstraintClause(parameter, constraints));
}

public CodeBuilder AppendCode(CodeBuilder codeBuilder)
{
return codeBuilder.AppendNewLineSeparated(genericConstraintClauses);
}
}
43 changes: 43 additions & 0 deletions src/M31.FluentApi.Generator/CodeBuilding/GenericParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace M31.FluentApi.Generator.CodeBuilding;

internal class GenericParameters : ICode
{
private readonly List<string> values;

internal GenericParameters(params string[] parameters)
{
values = parameters.ToList();
}

internal GenericParameters(GenericParameters parameters)
{
values = parameters.values.ToList();
}

internal IReadOnlyCollection<string> Values => values;

internal void Add(string parameter)
{
values.Add(parameter);
}

internal void Add(params string[] parameters)
{
values.AddRange(parameters);
}

internal void Add(IEnumerable<string> parameters)
{
values.AddRange(parameters);
}

public CodeBuilder AppendCode(CodeBuilder codeBuilder)
{
return codeBuilder.Append(() => $"<{string.Join(", ", values)}>", values.Count > 0);
}

public override string ToString()
{
return Values.Count == 0 ? string.Empty : $"<{string.Join(", ", values)}>";
}
}
29 changes: 29 additions & 0 deletions src/M31.FluentApi.Generator/CodeBuilding/Generics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace M31.FluentApi.Generator.CodeBuilding;

internal class Generics
{
internal Generics()
{
Parameters = new GenericParameters();
Constraints = new GenericConstraints();
}

internal Generics(Generics generics)
{
Parameters = new GenericParameters(generics.Parameters);
Constraints = new GenericConstraints(generics.Constraints);
}

internal GenericParameters Parameters { get; }
internal GenericConstraints Constraints { get; }

internal void AddGenericParameter(string parameter, IEnumerable<string> constraints)
{
Parameters.Add(parameter);
IReadOnlyCollection<string> constraintsCollection = constraints.ToArray();
if (constraintsCollection.Count > 0)
{
Constraints.Add(parameter, constraintsCollection);
}
}
}
2 changes: 1 addition & 1 deletion src/M31.FluentApi.Generator/CodeBuilding/Interface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public CodeBuilder AppendCode(CodeBuilder codeBuilder)
return codeBuilder
.AppendLine($"{AccessModifier} interface {Name}")
.OpenBlock()
.Append(methodSignatures)
.AppendWithBlankLines(methodSignatures)
.CloseBlock();
}
}
Loading

0 comments on commit a1ed7a1

Please sign in to comment.