BRO1149: Merge an if into the enclosing if¶
An if that is the only statement of another if, where neither has an else, joins it: if (a) { if (b) { ... } }
becomes if (a && b) { ... }. Not a StyleCop rule: Sonar's S1066
reports the same pattern, and StyleBro also fixes it.
Example¶
Before¶
public static class Orders
{
public static void Ship(Order order, bool expressAllowed)
{
if (order.IsPaid)
{
if (expressAllowed || order.Total > 100)
{
order.Express = true;
}
}
}
}
public sealed class Order
{
public bool IsPaid { get; set; }
public decimal Total { get; set; }
public bool Express { get; set; }
}
After¶
public static class Orders
{
public static void Ship(Order order, bool expressAllowed)
{
if (order.IsPaid && (expressAllowed || order.Total > 100))
{
order.Express = true;
}
}
}
public sealed class Order
{
public bool IsPaid { get; set; }
public decimal Total { get; set; }
public bool Express { get; set; }
}
Why¶
Two nested conditions with nothing between them are one condition written twice as deep. Merged, the body loses a level of indentation and the reader sees at once that both must hold.
How the fix works¶
- The conditions are joined with
&&, in order; one that binds looser than&&(||,??,?:, an assignment) gets parentheses.&&evaluates left to right and stops early, like the nestedifs, and a pattern variable from the first condition is still definitely assigned in the second (if (o is string s && s.Length > 0)). - The inner body is kept with its braces (or without them) and moves left by as much as the inner
ifwas indented deeper than the outer one; a condition spread over several lines moves the same way. The outerif's{,}and the innerif (...)go. Once the outer condition spans lines, the inner one starts a line of its own, aligned with the outer condition,&&wheredotnet_style_operator_placement_when_wrappingputs it (at the beginning by default) when BRO1520 enforces that key. When BRO1520 is off (always with CSharpier), it goes at the end of the outer condition's line if the conditions already wrap after an operator there (a &&+b), and where the key says otherwise; its own later lines keep their indentation relative to it. - Deeper nests become one condition (
if (a && b && c)), and nests inside the merged body are merged in the same fix, so onedotnet formatrun is enough. - When BRO1514-BRO1516 (braces) are on, the merged
ifgets the braces they want in the same edit: merging can move a statement into an if/else chain (else if (a)+if (b) X();), where another brace rule reports it.
Not reported¶
- An
elseon eitherif. - An outer block with more than the inner
if(a statement, a local, a comment). - A variable declared in an inner condition (
out var x,is string s) whose name is used elsewhere in the block around the outerif: a variable declared in anifcondition is in scope in the whole enclosing block, so moving it up would collide with a local of the same name in a sibling block (CS0136). - A comment or directive in the text that goes (
) {,if (, the inner)and the closing}), or an#if,#elif,#elseor#endifanywhere in the outerifor right after it (anelseinside#if): each target framework's copy could see other code. A#regionor#pragmaline in the body or after theifdoesn't keep it: BRO1112/BRO1113 remove regions, and the merge would otherwise come only on the next run. - A condition with a part of type
dynamic(d == 1,d.IsReady) in any of the mergedifs, withstylebro_allow_nested_dynamic_ifs = true(see Configuration). - A body that would move left but holds a string spanning lines (moving its lines would change the string), or a line that isn't indented far enough to move.
Configuration¶
[*.cs]
# true: nested ifs with a condition that has a part of type 'dynamic' aren't merged, like Sonar's S1066 (default false)
stylebro_allow_nested_dynamic_ifs = false
Sonar's S1066 skips an inner condition with a dynamic part; stylebro-migrate writes true for a repository that
enforces S1066. With it, any merged condition with a dynamic part (the inner or the outer one) keeps the nest as it is.
It is an option because finding out needs the compiler's types for the whole method, which costs time in every build.
Compared with other analyzers¶
Sonar's S1066 reports the same pattern (on the inner if) and has no fix. The SDK has no such rule. With stylebro_allow_nested_dynamic_ifs = true,
BRO1149 reports no if S1066 accepts, so stylebro-migrate turns it on with that setting for a repository that enforces
S1066. S1066 reports a few more: an if
inside nested blocks (if (a) { { if (b) ... } }), a plain dynamic variable as the condition (if (d); S1066 only
looks at the parts of a condition), and the inner pair of a nest whose middle condition is dynamic.
Also fixes Sonar's S1066 when the SonarAnalyzer.CSharp package is in the project: the fix is registered for S1066 too (the inner if keyword, where Sonar reports it) and applies at the places this rule would fix, so dotnet format clears Sonar's warnings there even with this rule off. Where this rule skips the code (see Not reported), no fix is offered and Sonar's warning stays.
Compared with StyleCop¶
StyleCop has no such rule. stylebro-migrate leaves it off.