Description
This may be a duplicate of #1613 but after reading that ticket, I am having a hard time understanding it's intentions.
The behavior I am after is:
When a method call is too long, one parameter is a lambda, and the last parameter is a lambda: prefer adding a new line somewhere after the => instead of chopping the parameters
When I tried to introduce CSharpier to my company this was the only issue raised. Everything else was a nitpick but the lambda-configuration formatting was seen as "icky". I believe the above rule is sufficient to cover the case and could apply to expression-bodied and method-bodied lambdas just fine.
Example
Often, this shows up with configuration or fluent libraries. You'll provide 0-4 parameters and then end with a lambda to do "whatever else". This pattern is very common:
My personal gripe is with a state machine library which would greatly suffer if methods were used to configure states and transitions. Keeping the configuration in one solid chunk makes it easy to read it instead of scrolling to other methods.
Current Behavior
A simple state machine with that library is currently formatted with CSharpier as:
var stateMachine = StateMachine
.Configure<string, string>()
.WithInitialState("A", 1)
.WithState(
"A",
state =>
state
.WithParameter<int>()
.WithTransition(
"To B",
t =>
t.WithMappedParameter<string>(value => value.ToString())
.If(mapped => mapped.Length > 0)
.To("B")
)
)
.WithState("B", state => state.WithParameter<string>())
.Build();
There is a ton of unnecessary whitespace being added due to aggressive chopping which directly makes the state machine harder to read.
Better Behavior
Ideally, the state machine configuration could just split the lambdas before chopping, which prevents long lines while still being easy to read. I'm not sure if this is exactly how it would look, but the idea of not chopping the parameters already saves bloating whitespace:
var stateMachine = StateMachine
.Configure<string, string>()
.WithInitialState("A", 1)
.WithState("A", state =>
state
.WithParameter<int>()
.WithTransition("To B", t =>
t
.WithMappedParameter<string>(value => value.ToString())
.If(mapped => mapped.Length > 0)
.To("B")
)
)
.WithState("B", state => state.WithParameter<string>())
.Build();
Ideal Behavior
Depending on how #1676 works out this could even reduce indentation more:
var stateMachine = StateMachine
.Configure<string, string>()
.WithInitialState("A", 1)
.WithState("A", state => state
.WithParameter<int>()
.WithTransition("To B", t => t
.WithMappedParameter<string>(value => value.ToString())
.If(mapped => mapped.Length > 0)
.To("B")
)
)
.WithState("B", state => state.WithParameter<string>())
.Build();
Out of Scope
It feels unreasonable to do this when there are multiple lambdas or parameters after the lambda.
With multiple lambdas you're probably always better off chopping because the API was designed with shorter lambdas in mind (not long configuration chains). For example, this looks great and I wouldn't want this to be changed:
messageBus.Subscribe(
"orders/created",
Retries.AtLeastOnce,
e => e.Connected += OnConnected,
e => e.Disconnected += OnDisconnected
);
If there are parameters after the lambda (typical for a CancellationToken) then chopping is almost always fine too. I feel like this is outside the scope of a linter and the writer should just create a method - there is often little readability benefit to using a lambda. This is typical for InvokeAsync or Task.Run sort of scenarios:
Task.Run(
() =>
{
var order = await this.orderRepository.GetOrder(orderId, token);
var orderUpdated = new OrderUpdatedEvent { Order = order };
await this.messageBus.Publish(orderUpdated, token);
},
token
);
// Just use a method group!
Task.Run(PublishOrderUpdatedEvent, token);
Next Steps
If this seems like this has potential I could fork CSharpier and (try to) implement this. I am unfamiliar with the code base but it feels like the ArgumentListLikeSyntax already has handling of lambda-edge-cases and may be a suitable place to start with.
P.S: I absolutely love this tool and feel that it has saved me countless hours. I will continue to use this personally because it just makes programming so much easier.
Description
This may be a duplicate of #1613 but after reading that ticket, I am having a hard time understanding it's intentions.
The behavior I am after is:
When I tried to introduce CSharpier to my company this was the only issue raised. Everything else was a nitpick but the lambda-configuration formatting was seen as "icky". I believe the above rule is sufficient to cover the case and could apply to expression-bodied and method-bodied lambdas just fine.
Example
Often, this shows up with configuration or fluent libraries. You'll provide 0-4 parameters and then end with a lambda to do "whatever else". This pattern is very common:
IConfiguration.Get<T>EntityTypeBuilder.OwnsManyHostApplicationBuilder.ConfigureContainerMy personal gripe is with a state machine library which would greatly suffer if methods were used to configure states and transitions. Keeping the configuration in one solid chunk makes it easy to read it instead of scrolling to other methods.
Current Behavior
A simple state machine with that library is currently formatted with CSharpier as:
There is a ton of unnecessary whitespace being added due to aggressive chopping which directly makes the state machine harder to read.
Better Behavior
Ideally, the state machine configuration could just split the lambdas before chopping, which prevents long lines while still being easy to read. I'm not sure if this is exactly how it would look, but the idea of not chopping the parameters already saves bloating whitespace:
Ideal Behavior
Depending on how #1676 works out this could even reduce indentation more:
Out of Scope
It feels unreasonable to do this when there are multiple lambdas or parameters after the lambda.
With multiple lambdas you're probably always better off chopping because the API was designed with shorter lambdas in mind (not long configuration chains). For example, this looks great and I wouldn't want this to be changed:
If there are parameters after the lambda (typical for a
CancellationToken) then chopping is almost always fine too. I feel like this is outside the scope of a linter and the writer should just create a method - there is often little readability benefit to using a lambda. This is typical forInvokeAsyncorTask.Runsort of scenarios:Next Steps
If this seems like this has potential I could fork CSharpier and (try to) implement this. I am unfamiliar with the code base but it feels like the
ArgumentListLikeSyntaxalready has handling of lambda-edge-cases and may be a suitable place to start with.P.S: I absolutely love this tool and feel that it has saved me countless hours. I will continue to use this personally because it just makes programming so much easier.