BRO1523: Each call of a split call chain starts its own line¶
When a member access chain is split over several lines (a . or ?. of the chain starts a line), every call after the
chain's first line starts its own line. Not a StyleCop rule; Roslynator's RCS0054 checks the same.
Example¶
Before¶
using System.Collections.Generic;
using System.Linq;
public class Directory
{
public List<string> ActiveNames(IEnumerable<string> people) =>
people
.Where(p => p.Length > 0).Select(p => p.Trim())
.Distinct().OrderBy(p => p).ToList();
}
After¶
using System.Collections.Generic;
using System.Linq;
public class Directory
{
public List<string> ActiveNames(IEnumerable<string> people) =>
people
.Where(p => p.Length > 0)
.Select(p => p.Trim())
.Distinct()
.OrderBy(p => p)
.ToList();
}
Why¶
A chain someone has already split reads as a list of steps, one per line; a step tacked onto the end of another line is easy to miss when scanning the left edge. In the eight reference repositories, 99.6% of the split chains (20,880 of 20,959) already follow the rule (see decisions.md).
What counts¶
- A chain is split when one of its
.or?.starts a line. A chain on one line is fine, and so is one where only the arguments span lines (Task.Run(() => { ... }).ConfigureAwait(false)). - The chain's first line may hold the receiver and one call:
services.AddLogging()+ newline +.AddOptions()is checked from the second line on. Only calls below the first line are checked. - A chain whose first line holds two or more calls wraps by filling lines and isn't checked:
sb.Append(id).Append(name)+ newline +.Append(text).Append(end)stays as it is. The receiver's own call counts (RuleFor(x => x.Name).NotNull()+ newline +.WithMessage(...)). - A call counts from the member right after the previous call:
.WriteTo.Sink(x)and.Items[0].Trim()are one step each and stay together,.Resultor.Lengthafter the last call stays where it is, and the first member after the receiver (new Config { ... }.Apply()) isn't checked. ?.counts like.,x!andx[0]after a call belong to that call.
How the fix works¶
Only the gap before the . (or ?.) changes: it becomes a line break plus the indentation of the chain's lines that
already start with ., the nearest one above (or else below). The rule doesn't check indentation, so a chain aligned
under its first . stays aligned. Arguments and lambda bodies aren't touched; a multi-line step moves only when its
other lines are already at least as deep as its new line (a lambda body under the step), otherwise it's skipped.
Not reported¶
- A comment in the gap (
.Where(x) /* why */ .Select(y)) or a directive anywhere in the chain (#ifbetween steps or inside an argument).#region/#endregionlines don't count (also not for a multi-line step's indentation): the chain is judged the same as after BRO1112/BRO1113 remove them, so one run is enough. - A multi-line step whose other lines are less indented than its new line would be: the fix would leave its lambda body to the left of the call.
- Chains whose first line holds two or more calls (see What counts).
- Chains inside an interpolated string's hole (a line break there needs C# 11) and code with syntax errors.
Compared with StyleCop¶
StyleCop has no such rule. Roslynator's RCS0054 (off by default) checks the same breaks with four differences: it
checks chains whose first line holds several calls (below that line), it also
splits a property step (.WriteTo + newline + .Sink(x)) and a trailing property, it reports chains where only the
arguments span lines, and it enforces one indentation unit deeper than the chain's first line (StyleBro keeps the
chain's own indentation). stylebro-migrate turns this rule off, so migrating doesn't change a StyleCop-clean
repository.