Repository: nix-community/nixpkgs-fmt (rust)

Issue:
sample: if statement after key
## Input
```nix
{
  userHome = if home != null
    then home
    else config.users.extraUsers.${user}.home;
}
```

## Output
```nix
{
  userHome = if home != null
  then home
  else config.users.extraUsers.${user}.home;
}

```

## Desired output

```nix
{
  userHome = if home != null
    then home
    else config.users.extraUsers.${user}.home;
}
```
or maybe
```nix
{
  userHome =
    if home != null
    then home
    else config.users.extraUsers.${user}.home;
}
```


Pull request:
Fix #158 spacing and indentation on node if then else
Note: In addition to #158, node `if .. else` will not be expanded if `if .. else` is after `in` token.

Patch:
diff --git a/src/engine.rs b/src/engine.rs
index d4efaae..525c4d6 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -49,6 +49,7 @@ pub(crate) fn format(
         // ```
         //
         // we only indent top-level node (lambda), and not it's first child (parameter)
+        // TODO : Remove it when refactoring indentation engine.
         if element.parent().map(|it| it.text_range().start()) == Some(element.text_range().start())
         {
             continue;
diff --git a/src/rules.rs b/src/rules.rs
index e409ca5..44c8733 100644
--- a/src/rules.rs
+++ b/src/rules.rs
@@ -102,9 +102,11 @@ pub(crate) fn spacing() -> SpacingDsl {
         .inside(NODE_APPLY).after(NODE_SELECT).when(next_sibling_is_paren).single_space_or_newline()
 
         .test("if  cond  then  tru  else  fls", "if cond then tru else fls")
-        .inside(NODE_IF_ELSE).after(T![if]).single_space_or_optional_newline()
-        .inside(NODE_IF_ELSE).around([T![then], T![else]]).single_space_or_optional_newline()
-
+        .inside(NODE_IF_ELSE).before(T![if]).when(node_has_newline).single_space_or_newline()
+        .inside(NODE_IF_ELSE).after([T![if],T![then]]).single_space_or_optional_newline()
+        .inside(NODE_IF_ELSE).before(T![then]).single_space_or_optional_newline()
+        .inside(NODE_IF_ELSE).around(T![else]).single_space_or_optional_newline()
+        
         // special-case to force a linebreak before `=` in
         //
         // ```nix
@@ -151,7 +153,12 @@ pub(crate) fn spacing() -> SpacingDsl {
             name: None,
             pattern: p(TOKEN_IN) & p(prev_in_contain_linebreak_pattern),
             space: dsl::Space { loc: dsl::SpaceLoc::Before, value: dsl::SpaceValue::Newline}
-        })  
+        })
+        .add_rule(dsl::SpacingRule {
+            name: None,
+            pattern: p(TOKEN_THEN) & p(node_has_newline) & p(not_inside_in_pattern),
+            space: dsl::Space { loc: dsl::SpaceLoc::Before, value: dsl::SpaceValue::Newline}
+        })
         ;
 
     dsl
@@ -177,6 +184,21 @@ fn next_sibling_has_newline(element: &SyntaxElement) -> bool {
         .unwrap_or(false)
 }
 
+fn node_has_newline(element: &SyntaxElement) -> bool {
+    let before_node_if = prev_non_whitespace_parent(element)
+        .and_then(|e| e.into_token().map(|t| t.kind() != TOKEN_ELSE))
+        .unwrap_or(false);
+    let newline_pattern = get_parent(element).map(|e| has_newline(&e)).unwrap_or(false);
+
+    (not_on_top_level(element) || before_node_if) & newline_pattern
+}
+
+fn not_inside_in_pattern(element: &SyntaxElement) -> bool {
+    prev_non_whitespace_parent(element)
+        .and_then(|e| e.into_token().map(|t| t.kind() != TOKEN_IN))
+        .unwrap_or(false)
+}
+
 fn next_sibling_is_paren(element: &SyntaxElement) -> bool {
     next_non_whitespace_sibling(element)
         .and_then(|e| e.into_node().map(|t| t.kind() == NODE_PAREN))
@@ -371,8 +393,7 @@ pub(crate) fn indentation() -> IndentDsl {
             "#)
 
         .rule("Indent lambda body")
-            .inside(p(NODE_LAMBDA) & p(not_on_top_level) & p(inline_lambda)) //& p(align_comment)
-            //.not_matching()
+            .inside(p(NODE_LAMBDA) & p(not_on_top_level) & p(inline_lambda))
             .set(Indent)
             .test(r#"
                 {}:
@@ -489,9 +510,6 @@ fn inline_lambda(element: &SyntaxElement) -> bool {
     prev_token_sibling(element).map(|t| t.text().contains("\n")) != Some(true)
 }
 
-fn align_comment(element: &SyntaxElement) -> bool {
-    prev_token_sibling(element).map(|t| t.text().contains("\n")) != Some(true)
-}
 fn not_on_top_level(element: &SyntaxElement) -> bool {
     !on_top_level(element)
 }
@@ -503,7 +521,9 @@ fn on_top_level(element: &SyntaxElement) -> bool {
     };
     match parent.kind() {
         NODE_ROOT => true,
-        NODE_LAMBDA | NODE_WITH | NODE_ASSERT | NODE_LET_IN => on_top_level(&parent.into()),
+        NODE_LAMBDA | NODE_WITH | NODE_ASSERT | NODE_LET_IN | NODE_IF_ELSE => {
+            on_top_level(&parent.into())
+        }
         _ => false,
     }
 }


Test patch:
diff --git a/test_data/issue-158.bad.nix b/test_data/issue-158.bad.nix
new file mode 100644
index 0000000..75cd3d9
--- /dev/null
+++ b/test_data/issue-158.bad.nix
@@ -0,0 +1,12 @@
+{
+foo = if bar != null
+then bar
+      else baz;
+bar =
+if bar == null
+then foo
+else baz;
+qux = if bux == null then nux else baz;
+nux = if foo == baz then bar
+else bar;
+}
diff --git a/test_data/issue-158.good.nix b/test_data/issue-158.good.nix
new file mode 100644
index 0000000..f5b406d
--- /dev/null
+++ b/test_data/issue-158.good.nix
@@ -0,0 +1,15 @@
+{
+  foo =
+    if bar != null
+    then bar
+    else baz;
+  bar =
+    if bar == null
+    then foo
+    else baz;
+  qux = if bux == null then nux else baz;
+  nux =
+    if foo == baz
+    then bar
+    else bar;
+}