Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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:

```xml
<PackageReference Include="M31.FluentApi" Version="2.0.0" PrivateAssets="all"/>
<PackageReference Include="M31.FluentApi" Version="2.1.0" PrivateAssets="all"/>
```

If you would like to examine the generated code, you may emit it by adding the following lines to your `csproj` file:
Expand Down Expand Up @@ -502,6 +502,18 @@ To simplify adding documentation comments, a code action is available to generat
![doc-comments-action](https://raw.githubusercontent.com/m31coding/M31.FluentAPI/main/media/create-doc-comments-action.png)

For reference, you can view the documented version of the `Student` class in [DocumentedStudent.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs). The corresponding generated code is located in [DocumentedStudent.g.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs)


### Modifying an existing instance

The static `FromExisting` method can be used to modify an existing instance:

```cs
Student student1 = CreateStudent.WithFirstName("Alice").WithLastName("King");
Student student2 = CreateStudent.FromExisting(student1).WithLastName("Queen");
```

After calling `FromExisting`, the builder can continue from any step. To avoid mutating the original instance, create a copy constructor on the `Student` class and pass a copy to the `FromExisting` method.


### Lambda pattern
Expand Down Expand Up @@ -592,4 +604,4 @@ In particular, if your IDE visually indicates that there are errors in your code

This library is free. If you find it valuable and wish to express your support, please leave a star. You are kindly invited to contribute. If you see the possibility for enhancement, please create a GitHub issue and you will receive timely feedback.

Happy coding!
Happy coding!
Binary file modified media/create-doc-comments-action.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public void Modify(CodeBoard codeBoard)

List<Interface> interfaces = new List<Interface>(builderStepMethods.Interfaces.Count);
interfaces.Add(CreateInitialStepInterface(builderStepMethods, codeBoard));
interfaces.Add(CreateFromAnyStepInterface(builderStepMethods, codeBoard));

foreach (BuilderInterface builderInterface in builderStepMethods.Interfaces)
{
Expand Down Expand Up @@ -92,6 +93,19 @@ private Interface CreateInitialStepInterface(BuilderStepMethods builderStepMetho
return initialStepInterface;
}

private Interface CreateFromAnyStepInterface(BuilderStepMethods builderStepMethods, CodeBoard codeBoard)
{
Interface fromExistingInterface =
new Interface(codeBoard.Info.DefaultAccessModifier, codeBoard.Info.FromExistingInterfaceName);

foreach (BuilderInterface @interface in builderStepMethods.Interfaces)
{
fromExistingInterface.AddBaseInterface(@interface.InterfaceName);
}

return fromExistingInterface;
}

private void AddInterfacesToBuilderClass(List<Interface> interfaces, Class builderClass, string prefix)
{
foreach (Interface @interface in interfaces)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors;
internal class ConstructorGenerator : ICodeBoardActor
{
public void Modify(CodeBoard codeBoard)
{
CreateParameterlessConstructor(codeBoard);
CreateConstructorWithInstanceParameter(codeBoard);
}

private static void CreateParameterlessConstructor(CodeBoard codeBoard)
{
string instanceName = codeBoard.Info.ClassInstanceName;
string classNameWithTypeParameters = codeBoard.Info.FluentApiClassNameWithTypeParameters;

Method constructor = CreateConstructor(codeBoard.Info.BuilderClassName);
Method constructor = CreateParameterlessConstructorMethod(codeBoard.Info.BuilderClassName);
ConstructorInfo constructorInfo = codeBoard.Info.FluentApiTypeConstructorInfo;

if (codeBoard.Info.FluentApiTypeConstructorInfo.ConstructorIsNonPublic)
Expand Down Expand Up @@ -59,7 +65,17 @@ public void Modify(CodeBoard codeBoard)
constructor.AppendBodyLine(codeBuilder.ToString());
}

codeBoard.Constructor = constructor;
codeBoard.BuilderClass.AddMethod(constructor);
}

private static void CreateConstructorWithInstanceParameter(CodeBoard codeBoard)
{
Method constructor = CreateConstructorWithInstanceParameterMethod(codeBoard.Info);

CodeBuilder codeBuilder = new CodeBuilder(codeBoard.NewLineString);
codeBuilder.Append($"this.{codeBoard.Info.ClassInstanceName} = {codeBoard.Info.ClassInstanceName};");
constructor.AppendBodyLine(codeBuilder.ToString());

codeBoard.BuilderClass.AddMethod(constructor);
}

Expand Down Expand Up @@ -112,11 +128,22 @@ private static string CreateArgument(
return "default!";
}

private static Method CreateConstructor(string builderClassName)
private static Method CreateParameterlessConstructorMethod(string builderClassName)
{
// private CreateStudent()
MethodSignature signature = MethodSignature.CreateConstructorSignature(builderClassName);
signature.AddModifiers("private");
return new Method(signature);
}

private static Method CreateConstructorWithInstanceParameterMethod(
BuilderAndTargetInfo info)
{
// private CreateStudent(Student student)
MethodSignature signature = MethodSignature.CreateConstructorSignature(info.BuilderClassName);
Parameter parameter = new Parameter(info.FluentApiClassNameWithTypeParameters, info.ClassInstanceName);
signature.AddParameter(parameter);
signature.AddModifiers("private");
return new Method(signature);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using M31.FluentApi.Generator.CodeBuilding;
using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements;

namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors;

internal class FromExistingMethodGenerator : ICodeBoardActor
{
public void Modify(CodeBoard codeBoard)
{
// public static ICreateStudentFromAnyStep FromExisting(Student student)
// {
// return new CreateStudent(student);
// }
BuilderAndTargetInfo info = codeBoard.Info;
string methodName = "FromExisting";
MethodSignature methodSignature = MethodSignature.Create(
info.FromExistingInterfaceName,
methodName,
null,
false);
methodSignature.AddModifiers(info.DefaultAccessModifier, "static");
Parameter parameter = new Parameter(
info.FluentApiClassNameWithTypeParameters,
info.ClassInstanceName);
methodSignature.AddParameter(parameter);
Method method = new Method(methodSignature);
string parameterListInAngleBrackets = info.GenericInfo?.ParameterListInAngleBrackets ?? string.Empty;
method.AppendBodyLine(
$"return new {info.BuilderClassName}{parameterListInAngleBrackets}({info.ClassInstanceName});");
codeBoard.BuilderClass.AddMethod(method);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal BuilderAndTargetInfo(
BuilderInstanceName = builderClassName.FirstCharToLower();
ClassInstanceName = fluentApiClassName.FirstCharToLower();
InitialStepInterfaceName = $"I{builderClassName}";
FromExistingInterfaceName = $"I{builderClassName}FromExisting";
}

internal string? Namespace { get; }
Expand All @@ -45,4 +46,5 @@ internal BuilderAndTargetInfo(
internal string BuilderInstanceName { get; }
internal string ClassInstanceName { get; }
internal string InitialStepInterfaceName { get; }
internal string FromExistingInterfaceName { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ private CodeBoard(
Info = builderAndTargetInfo;
CodeFile = codeFile;
BuilderClass = builderClass;
Constructor = null;
StaticConstructor = null;
InnerBodyCreationDelegates = new InnerBodyCreationDelegates();
TransformedComments = new TransformedComments();
GroupsToMethods = new Dictionary<FluentApiInfoGroup, BuilderMethods>();
Expand All @@ -48,8 +46,6 @@ private CodeBoard(
internal BuilderAndTargetInfo Info { get; }
internal CodeFile CodeFile { get; }
internal Class BuilderClass { get; }
internal Method? Constructor { get; set; }
internal Method? StaticConstructor { get; set; }
internal InnerBodyCreationDelegates InnerBodyCreationDelegates { get; }
internal TransformedComments TransformedComments { get; }
internal Dictionary<FluentApiInfoGroup, BuilderMethods> GroupsToMethods { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal static CodeGeneratorResult GenerateCode(FluentApiClassInfo classInfo, C
new ForkCreator(),
new DuplicateMethodsChecker(),
new InitialStepMethodGenerator(),
new FromExistingMethodGenerator(),
new BuilderGenerator(),
};

Expand Down
2 changes: 1 addition & 1 deletion src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<PackageVersion>2.0.0</PackageVersion>
<PackageVersion>2.1.0</PackageVersion>
<Authors>Kevin Schaal</Authors>
<Description>The generator package for M31.FluentAPI. Don't install this package explicitly, install M31.FluentAPI instead.</Description>
<PackageTags>fluentapi fluentbuilder fluentinterface fluentdesign fluent codegeneration</PackageTags>
Expand Down
14 changes: 11 additions & 3 deletions src/M31.FluentApi.Storybook/01_Basics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ namespace BasicExample
{
/* Generates a builder class with name CreateStudent and methods WithFirstName and WithLastName. The methods have to
be called in the specified order, WithFirstName (builder step 0) has to be called before WithLastName (builder
step 1). As shown in the usage examples below, a student can be either created by calling the static
WithFirstName method on the CreateStudent class, or by first creating a new builder instance.
step 1).
As shown in the usage examples below, a student can be created by calling the static WithFirstName
method on the CreateStudent class.
Alternatively, an existing Student can be passed to the static FromExisting method. After calling FromExisting,
the builder can continue from any step. The FromExisting method mutates the passed instance, and the last name
is set to "Queen".
The InitialStep method can be used to create a builder instance without creating a student. This is useful for
advanced use cases such as the lambda pattern, as described in the README.md.
Although I use classes with properties in all examples of this file, the FluentApi attribute can also be applied
to structs and records, and the FluentMember attribute also works with fields. */

Expand All @@ -29,8 +35,10 @@ public static void UseTheGeneratedFluentApi()
{
Student student1 = CreateStudent.WithFirstName("Alice").WithLastName("King");

Student student2 = CreateStudent.FromExisting(student1).WithLastName("Queen");

CreateStudent.ICreateStudent createStudent = CreateStudent.InitialStep();
Student student2 = createStudent.WithFirstName("Bob").WithLastName("Bishop");
Student student3 = createStudent.WithFirstName("Bob").WithLastName("Bishop");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.AliasNamespace

public class CreateStudent :
CreateStudent.ICreateStudent,
CreateStudent.ICreateStudentFromExisting,
CreateStudent.IWhoseFriendsAre
{
private readonly Student student;
Expand All @@ -21,11 +22,21 @@ public class CreateStudent :
student = new Student();
}

private CreateStudent(Student student)
{
this.student = student;
}

public static ICreateStudent InitialStep()
{
return new CreateStudent();
}

public static ICreateStudentFromExisting FromExisting(Student student)
{
return new CreateStudent(student);
}

public static Student WhoseFriendsAre(System.Collections.Generic.IList<string> friends)
{
CreateStudent createStudent = new CreateStudent();
Expand Down Expand Up @@ -82,6 +93,10 @@ public class CreateStudent :
{
}

public interface ICreateStudentFromExisting : IWhoseFriendsAre
{
}

public interface IWhoseFriendsAre
{
Student WhoseFriendsAre(System.Collections.Generic.IList<string> friends);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.AliasNamespace

public class CreateStudent :
CreateStudent.ICreateStudent,
CreateStudent.ICreateStudentFromExisting,
CreateStudent.IWhoseFriendsAre
{
private readonly Student student;
Expand All @@ -21,11 +22,21 @@ private CreateStudent()
student = new Student();
}

private CreateStudent(Student student)
{
this.student = student;
}

public static ICreateStudent InitialStep()
{
return new CreateStudent();
}

public static ICreateStudentFromExisting FromExisting(Student student)
{
return new CreateStudent(student);
}

public static Student WhoseFriendsAre(System.Collections.Generic.IList<string> friends)
{
CreateStudent createStudent = new CreateStudent();
Expand Down Expand Up @@ -82,6 +93,10 @@ public interface ICreateStudent : IWhoseFriendsAre
{
}

public interface ICreateStudentFromExisting : IWhoseFriendsAre
{
}

public interface IWhoseFriendsAre
{
Student WhoseFriendsAre(System.Collections.Generic.IList<string> friends);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.CollectionInte

public class CreateStudent :
CreateStudent.ICreateStudent,
CreateStudent.ICreateStudentFromExisting,
CreateStudent.IWhoseFriendsAre,
CreateStudent.IWithPets,
CreateStudent.IWithBackpackContent
Expand All @@ -22,11 +23,21 @@ public class CreateStudent :
student = new Student();
}

private CreateStudent(Student student)
{
this.student = student;
}

public static ICreateStudent InitialStep()
{
return new CreateStudent();
}

public static ICreateStudentFromExisting FromExisting(Student student)
{
return new CreateStudent(student);
}

public static IWithPets WhoseFriendsAre(System.Collections.Generic.IList<string> friends)
{
CreateStudent createStudent = new CreateStudent();
Expand Down Expand Up @@ -131,6 +142,10 @@ public class CreateStudent :
{
}

public interface ICreateStudentFromExisting : IWhoseFriendsAre, IWithPets, IWithBackpackContent
{
}

public interface IWhoseFriendsAre
{
IWithPets WhoseFriendsAre(System.Collections.Generic.IList<string> friends);
Expand Down
Loading
Loading