8000 feat: FluentSkippable (#19) · m31coding/M31.FluentAPI@1ac38b9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1ac38b9

Browse files
authored
feat: FluentSkippable (#19)
* feat(FluentSkippable): FluentSkippable attribute * feat(FluentSkippable): make SkippableMemberClass test work * refactor(BuilderStepMethodCreator): create static methods from BuilderStepMethods * refactor(BuilderStepMethodCreator): cleanup * feat(FluentSkippable): make SkippableFirstMemberClass test work * test: SkippableSeveralMembersClass * fix(SkippableSeveralMembersClass): remove blank line * feat(FluentSkippable): last step cannot be skipped diagnostic * test: add failing test SkippableLoopClass * Test(SkippableLoopClass): add expected code * test(SkippableLoopClass): add desired CreatedStudent.g.cs * feat(FluentSkippable): BuilderStepsGenerator new version * feat(FluentSkippable): loop handling classes * feat(FluentSkippable): make SkippableLoopClass test work * fix: remove obsolete EmptyInterfaceBuilderMethod class * refactor: rename BuilderStepsGeneration folder and namespace * fix: rename file * test: CanExecuteSkippableLoopClass * fix: rename FirstStepBuilderMethod and SingleStepBuilderMethod classes * test: TarjansSccAlgorithmTests * test: ContinueWithInForkClass * test: SkippableFirstTwoMembersClass * test: SkippableTwoLoopsClass * test: SkippableForkMembersClass * fix: address resharper warnings * feat(FluentSkippable): adjust examples and storybook * docs(Readme): add FluentSkippable * chore: increase nuget versions to 1.6.0
1 parent 86374ac commit 1ac38b9

File tree

170 files changed

+3824
-932
lines changed
  • FluentMethodClass
  • FluentMethodDefaultValuesClass
  • FluentMethodParameterModifiersClass
  • FluentNullableClass
  • FluentNullableNoNullableAnnotationClass
  • FluentNullableNoNullableAnnotationPrivateSetClass
  • FluentReturnSingleStepClass
  • FluentReturnSingleStepPrivateMethodsClass
  • ForkClass
  • FullyQualifiedTypeClass
  • GenericClassPrivateConstructor
  • GenericClassWithConstraints
  • GenericClass
  • GenericMethodWithConstraintsClass
  • GenericOverloadedMethodClass
  • GenericOverloadedPrivateMethodClass
  • GetInitPropertyClass
  • InternalClass
  • InternalPropertyClass
  • NonGenericCollectionMemberClass
  • NullablePredicateAndCollectionClass
  • OneMemberClass
  • OverloadedMethodClass
  • PredicateClass
  • PredicatePrivateFieldClass
  • PrivateConstructorClass
  • PrivateFieldClass
  • PrivateReadonlyFieldClass
  • PrivateUnderscoreFieldClass
  • PublicFieldClass
  • PublicReadonlyFieldClass
  • SkippableFirstMemberClass
  • SkippableFirstTwoMembersClass
  • SkippableForkMembersClass
  • SkippableLoopClass
  • SkippableMemberClass
  • SkippableSeveralMembersClass
  • SkippableTwoLoopsClass
  • ThreeMemberClass
  • ThreeMemberRecordStruct
  • ThreeMemberRecord
  • ThreeMemberStruct
  • TwoMemberClass
  • TwoParameterCompoundClass
  • PersonClass
  • Components
  • M31.FluentApi
  • Some content is hidden

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

    170 files changed

    +3824
    -932
    lines changed

    README.md

    Lines changed: 38 additions & 8 deletions
    Original file line numberDiff line numberDiff line change
    @@ -25,7 +25,7 @@ PM> Install-Package M31.FluentApi
    2525
    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
    28-
    <PackageReference Include="M31.FluentApi" Version="1.5.0" PrivateAssets="all"/>
    28+
    <PackageReference Include="M31.FluentApi" Version="1.6.0" PrivateAssets="all"/>
    2929
    ```
    3030

    3131
    If you would like to examine the generated code, you may emit it by adding the following lines to your `csproj` file:
    @@ -111,7 +111,7 @@ The attributes `FluentPredicate`, `FluentCollection`, and `FluentLambda` can be
    111111

    112112
    The `FluentMethod` attribute is used for custom builder method implementations.
    113113

    114-
    The control attribute `FluentContinueWith` indicates a jump to the specified builder step, and `FluentBreak` stops the builder. `FluentReturn` allows returning arbitrary types and values within the generated API.
    114+
    The control attribute `FluentSkippable` allows builder methods to be optional, while `FluentContinueWith` indicates a jump to the specified builder step. `FluentBreak` stops the builder, and `FluentReturn` allows returning arbitrary types and values within the generated API.
    115115

    116116

    117117
    ### FluentApi
    @@ -300,23 +300,23 @@ private void BornOn(DateOnly dateOfBirth)
    300300
    ```
    301301

    302302

    303-
    ### FluentContinueWith
    303+
    ### FluentSkippable
    304304

    305305
    ```cs
    306-
    FluentContinueWith(int builderStep)
    306+
    FluentSkippable()
    307307
    ```
    308308

    309-
    Can be used at all steps on fields, properties, and methods to jump to a specific builder step. Useful for skipping steps and branching. May be used to create optional builder methods:
    309+
    Can be used at all steps on fields, properties, and methods to create an optional builder method. The generated API will offer the method but it does not have to be called.
    310310

    311311
    ```cs
    312312
    [FluentMember(0)]
    313313
    public string FirstName { get; private set; }
    314314

    315315
    [FluentMember(1)]
    316-
    [FluentContinueWith(1)]
    316+
    [FluentSkippable]
    317317
    public string? MiddleName { get; private set; }
    318318

    319-
    [FluentMember(1)]
    319+
    [FluentMember(2)]
    320320
    public string LastName { get; private set; }
    321321
    ```
    322322

    @@ -326,6 +326,36 @@ public string LastName { get; private set; }
    326326
    ```
    327327

    328328

    329+
    ### FluentContinueWith
    330+
    331+
    ```cs
    332+
    FluentContinueWith(int builderStep)
    333+
    ```
    334+
    335+
    Can be used at all steps on fields, properties, and methods to jump to a specific builder step. Useful for branching.
    336+
    337+
    ```cs
    338+
    [FluentMethod(3)]
    339+
    [FluentContinueWith(7)]
    340+
    private void WhoIsADigitalNomad()
    341+
    {
    342+
    IsDigitalNomad = true;
    343+
    }
    344+
    345+
    // ...
    346+
    347+
    [FluentMethod(7)]
    348+
    private void LivingInCity(string city)
    349+
    {
    350+
    City = city;
    351+
    }
    352+
    ```
    353+
    354+
    ```cs
    355+
    ...WhoIsADigitalNomad().LivingInCity("Berlin")...
    356+
    ```
    357+
    358+
    329359
    ### FluentBreak
    330360

    331361
    ```cs
    @@ -335,7 +365,7 @@ FluentBreak()
    335365
    Can be used at all steps on fields, properties, and methods to stop the builder. Only relevant for non-linear APIs that make use of `FluentContinueWith`.
    336366

    337367
    ```cs
    338-
    [FluentMethod(1)]
    368+
    [FluentMethod(3)]
    339369
    [FluentBreak]
    340370
    private void WhoseAddressIsUnknown()
    341371
    {

    src/ExampleProject/Person.cs

    Lines changed: 10 additions & 10 deletions
    Original file line numberDiff line numberDiff line change
    @@ -13,10 +13,10 @@ public class Person
    1313
    public string FirstName { get; private set; }
    1414

    1515
    [FluentMember(1)]
    16-
    [FluentContinueWith(1)]
    16+
    [FluentSkippable]
    1717
    public string? MiddleName { get; private set; }
    1818

    19-
    [FluentMember(1)]
    19+
    [FluentMember(2)]
    2020
    public string LastName { get; private set; }
    2121

    2222
    public string? HouseNumber { get; private set; }
    @@ -27,44 +27,44 @@ public class Person
    2727

    2828
    public bool IsDigitalNomad { get; private set; }
    2929

    30-
    [FluentMethod(2)]
    30+
    [FluentMethod(3)]
    3131
    [FluentBreak]
    3232
    private void WhoseAddressIsUnknown()
    3333
    {
    3434
    }
    3535

    36-
    [FluentMethod(2)]
    36+
    [FluentMethod(3)]
    3737
    private void WhoLivesAtAddress()
    3838
    {
    3939
    }
    4040

    41-
    [FluentMethod(3)]
    41+
    [FluentMethod(4)]
    4242
    private void WithHouseNumber(string houseNumber)
    4343
    {
    4444
    HouseNumber = houseNumber;
    4545
    }
    4646

    47-
    [FluentMethod(4)]
    47+
    [FluentMethod(5)]
    4848
    private void WithStreet(string street)
    4949
    {
    5050
    Street = street;
    5151
    }
    5252

    53-
    [FluentMethod(5)]
    53+
    [FluentMethod(6)]
    5454
    [FluentBreak]
    5555
    private void InCity(string city)
    5656
    {
    5757
    City = city;
    5858
    }
    5959

    60-
    [FluentMethod(2)]
    61-
    [FluentContinueWith(6)]
    60+
    [FluentMethod(3)]
    61+
    [FluentContinueWith(7)]
    6262
    private void WhoIsADigitalNomad()
    6363
    {
    6464
    IsDigitalNomad = true;
    6565
    }
    6666

    67-
    [FluentMethod(6)]
    67+
    [FluentMethod(7)]
    6868
    private void LivingInCity(string city)
    6969
    {
    7070
    City = city;

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

    Lines changed: 10 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -59,4 +59,13 @@ M31FA022 | M31.Usage | Error | Fluent lambda member without Fluent API
    5959

    6060
    Rule ID | Category | Severity | Notes
    6161
    --------|----------|----------|-------
    62-
    M31FA007 | M31.Usage | Error | Partial types are not supported
    62+
    M31FA007 | M31.Usage | Error | Partial types are not supported
    63+
    64+
    65+
    ## Release 1.6.0
    66+
    67+
    ### New Rules
    68+
    69+
    Rule ID | Category | Severity | Notes
    70+
    --------|----------|----------|-------
    71+
    M31FA023 | M31.Usage | Error | Last builder step cannot be skipped

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

    Lines changed: 7 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,3 +1,5 @@
    1+
    // ReSharper disable ParameterHidesMember
    2+
    13
    namespace M31.FluentApi.Generator.CodeBuilding;
    24

    35
    internal class Interface : ICode
    @@ -33,6 +35,11 @@ internal void AddBaseInterface(string baseInterface)
    3335
    baseInterfaces.Add(baseInterface);
    3436
    }
    3537

    38+
    internal void AddBaseInterfaces(IEnumerable<string> baseInterfaces)
    39+
    {
    40+
    this.baseInterfaces.AddRange(baseInterfaces);
    41+
    }
    42+
    3643
    public CodeBuilder AppendCode(CodeBuilder codeBuilder)
    3744
    {
    3845
    return codeBuilder
    Lines changed: 13 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,13 @@
    1+
    namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration;
    2+
    3+
    internal class BaseInterface
    4+
    {
    5+
    public BaseInterface(string name, int step)
    6+
    {
    7+
    Name = name;
    8+
    Step = step;
    9+
    }
    10+
    11+
    public string Name { get; }
    12+
    public int Step { get; }
    13+
    }
    Lines changed: 97 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,97 @@
    1+
    using M31.FluentApi.Generator.CodeBuilding;
    2+
    using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements;
    3+
    4+
    namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration;
    5+
    6+
    internal class BuilderGenerator : ICodeBoardActor
    7+
    {
    8+
    public void Modify(CodeBoard codeBoard)
    9+
    {
    10+
    BuilderMethods builderMethods =
    11+
    BuilderMethodsCreator.CreateBuilderMethods(codeBoard.Forks, codeBoard.CancellationToken);
    12+
    13+
    foreach (BuilderStepMethod staticMethod in builderMethods.StaticMethods)
    14+
    {
    15+
    if (codeBoard.CancellationToken.IsCancellationRequested)
    16+
    {
    17+
    break;
    18+
    }
    19+
    20+
    Method method = CreateMethod(staticMethod, codeBoard);
    21+
    codeBoard.BuilderClass.AddMethod(method);
    22+
    }
    23+
    24+
    List<Interface> interfaces = new List<Interface>(builderMethods.Interfaces.Count);
    25+
    interfaces.Add(CreateInitialStepInterface(builderMethods, codeBoard));
    26+
    27+
    foreach (BuilderInterface builderInterface in builderMethods.Interfaces)
    28+
    {
    29+
    if (codeBoard.CancellationToken.IsCancellationRequested)
    30+
    {
    31+
    break;
    32+
    }
    33+
    34+
    Interface @interface =
    35+
    new Interface(codeBoard.Info.DefaultAccessModifier, builderInterface.InterfaceName);
    36+
    37+
    foreach (InterfaceBuilderMethod interfaceMethod in builderInterface.Methods)
    38+
    {
    39+
    Method method = CreateMethod(interfaceMethod, codeBoard);
    40+
    codeBoard.BuilderClass.AddMethod(method);
    41+
    @interface.AddMethodSignature(method.MethodSignature.ToSignatureForInterface());
    42+
    }
    43+
    44+
    @interface.AddBaseInterfaces(builderInterface.BaseInterfaces);
    45+
    interfaces.Add(@interface);
    46+
    }
    47+
    48+
    AddInterfacesToBuilderClass(
    49+
    interfaces,
    50+
    codeBoard.BuilderClass,
    51+
    codeBoard.Info.BuilderClassNameWithTypeParameters);
    52+
    AddInterfaceDefinitionsToBuilderClass(interfaces, codeBoard.BuilderClass);
    53+
    }
    54+
    55+
    private Method CreateMethod(BuilderStepMethod builderStepMethod, CodeBoard codeBoard)
    56+
    {
    57+
    ReservedVariableNames reservedVariableNames = codeBoard.ReservedVariableNames.NewLocalScope();
    58+
    reservedVariableNames.ReserveLocalVariableNames(builderStepMethod.Parameters.Select(p => p.Name));
    59+
    60+
    Method method = builderStepMethod.BuildMethodCode(
    61+
    codeBoard.Info,
    62+
    reservedVariableNames);
    63+
    64+
    return method;
    65+
    }
    66+
    67+
    private Interface CreateInitialStepInterface(BuilderMethods builderMethods, CodeBoard codeBoard)
    68+
    {
    69+
    string? firstInterfaceName = builderMethods.Interfaces.FirstOrDefault()?.InterfaceName;
    70+
    71+
    Interface initialStepInterface =
    72+
    new Interface(codeBoard.Info.DefaultAccessModifier, codeBoard.Info.InitialStepInterfaceName);
    73+
    74+
    if (firstInterfaceName != null)
    75+
    {
    76+
    initialStepInterface.AddBaseInterface(firstInterfaceName);
    77+
    }
    78+
    79+
    return initialStepInterface;
    80+
    }
    81+
    82+
    private void AddInterfacesToBuilderClass(List<Interface> interfaces, Class builderClass, string prefix)
    83+
    {
    84+
    foreach (Interface @interface in interfaces)
    85+
    {
    86+
    builderClass.AddInterface($"{prefix}.{@interface.Name}");
    87+
    }
    88+
    }
    89+
    90+
    private void AddInterfaceDefinitionsToBuilderClass(List<Interface> interfaces, Class builderClass)
    91+
    {
    92+
    foreach (Interface @interface in interfaces)
    93+
    {
    94+
    builderClass.AddDefinition(@interface);
    95+
    }
    96+
    }
    97+
    }
    Lines changed: 18 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,18 @@
    1+
    namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration;
    2+
    3+
    internal class BuilderInterface
    4+
    {
    5+
    internal BuilderInterface(
    6+
    string interfaceName,
    7+
    IReadOnlyCollection<string> baseInterfaces,
    8+
    IReadOnlyCollection<InterfaceBuilderMethod> methods)
    9+
    {
    10+
    InterfaceName = interfaceName;
    11+
    BaseInterfaces = baseInterfaces;
    12+
    Methods = methods;
    13+
    }
    14+
    15+
    public string InterfaceName { get; }
    16+
    public IReadOnlyCollection<string> BaseInterfaces { get; }
    17+
    public IReadOnlyCollection<InterfaceBuilderMethod> Methods { get; }
    18+
    }
    Lines changed: 56 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,56 @@
    1+
    using M31.FluentApi.Generator.Commons;
    2+
    3+
    namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration;
    4+
    5+
    internal class BuilderMethods
    6+
    {
    7+
    public BuilderMethods(
    8+
    IReadOnlyCollection<BuilderStepMethod> staticMethods,
    9+
    IReadOnlyCollection<BuilderInterface> interfaces)
    10+
    {
    11+
    Interfaces = interfaces;
    12+
    StaticMethods = staticMethods;
    13+
    }
    14+
    15+
    internal IReadOnlyCollection<BuilderInterface> Interfaces { get; }
    16+
    internal IReadOnlyCollection<BuilderStepMethod> StaticMethods { get; }
    17+
    18+
    internal static IReadOnlyCollection<BuilderInterface> CreateInterfaces(
    19+
    IReadOnlyCollection<InterfaceBuilderMethod> interfaceMethods,
    20+
    CancellationToken cancellationToken)
    21+
    {
    22+
    List<BuilderInterface> interfaces = new List<BuilderInterface>();
    23+
    24+
    IGrouping<string, InterfaceBuilderMethod>[] methodsGroupedByInterface =
    25+
    interfaceMethods.GroupBy(m => m.InterfaceName).ToArray();
    26+
    27+
    foreach (IGrouping<string, InterfaceBuilderMethod> group in methodsGroupedByInterface)
    28+
    {
    29+
    if (cancellationToken.IsCancellationRequested)
    30+
    {
    31+
    break;
    32+
    }
    33+
    34+
    string interfaceName = group.Key;
    35+
    36+
    List<BaseInterface> baseInterfaces = new List<BaseInterface>();
    37+
    38+
    foreach (InterfaceBuilderMethod method in group)
    39+
    {
    40+
    if (method.BaseInterface != null)
    41+
    {
    42+
    baseInterfaces.Add(method.BaseInterface);
    43+
    }
    44+
    }
    45+
    46+
    string[] baseInterfaceNames = baseInterfaces
    47+
    .DistinctBy(i => i.Name)
    48+
    .OrderBy(i => i.Step)
    49+
    .Select(i => i.Name).ToArray();
    50+
    51+
    interfaces.Add(new BuilderInterface(interfaceName, baseInterfaceNames, group.ToArray()));
    52+
    }
    53+
    54+
    return interfaces;
    55+
    }
    56+
    }

    0 commit comments

    Comments
     (0)
    0