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

Issue:
remove line break after expanded let ... in
```nix
# One line, everyone's happy
let self = { }; in self
```


https://github.com/nix-community/nixpkgs-fmt/issues/65#issuecomment-507306029 suggests that we could consider `let ... in` just as an `assert ...;` or a `with ... ;` statement that does not indent the following body. In that case example 2 is inconsistent.

```nix
# input
# This should be okay if `let` is considered like a `with` or `assert` 
let self = { }; in
self
```

```nix
# nix-fmt output
# But it is expanded :-(
let
  self = {};
in
self
```

Overall, it seems that the `let ... in body` is expanded (split across multiple lines as long as it is not written on one line. I think that expanding the `let ... in` part should be independent from expanding the body itself and from decoupling the `in` from the body.

The above example is made even worse by the fact that we do not indent toplevel let bodies (https://github.com/nix-community/nixpkgs-fmt/pull/108). The `self` really looks weird and could be on the same line as the `in`.

May I propose the following patterns ?

```nix
let foo = bar; in foo
```
```nix
let foo = bar; in
foo
```
```nix
let
  foo = bar;
in {
  bar = foo;
}
```

The implicit rule is that when the let+in construct is one one line, it behaves like a `with` or `assert`, and when it is on several lines (expanded form), no line break is enforced after the `in` (which means that line breaks are only enforced for a multi-line body after a single line let+in).

Pull request description:
Fixing formatting on let in notation
This PR is a WIP which try to fix #125 and #167 

I have change the possible `let ... in ` syntax that I learn based on `test_data` folder.

Patch:
diff --git a/README.md b/README.md
index 3e0a294..04273d6 100644
--- a/README.md
+++ b/README.md
@@ -186,9 +186,3 @@ Feel free to submit your project!
 * [nixpkgs style guide](https://nixos.org/nixpkgs/manual/#sec-syntax)
 * [On Nix expression formatting](https://discourse.nixos.org/t/on-nix-expression-formatting/1521/14)
 * [[Job] Implement a `nix-fmt` formatter](https://discourse.nixos.org/t/job-implement-a-nix-fmt-formatter/2819/12)
-
-## Sponsors
-
-This work has been sponsored by [NumTide](https://numtide.com).
-
-<img src="https://numtide.com/logo.png" alt="NumTide Logo" width="80">
diff --git a/src/dsl.rs b/src/dsl.rs
index 4810730..1bed023 100644
--- a/src/dsl.rs
+++ b/src/dsl.rs
@@ -172,10 +172,6 @@ impl<'a> SpacingRuleBuilder<'a> {
     pub(crate) fn single_space(self) -> &'a mut SpacingDsl {
         self.finish(SpaceValue::Single)
     }
-    /// Enforce newline
-    pub(crate) fn newline(self) -> &'a mut SpacingDsl {
-        self.finish(SpaceValue::Newline)
-    }
     pub(crate) fn single_space_or_optional_newline(self) -> &'a mut SpacingDsl {
         self.finish(SpaceValue::SingleOptionalNewline)
     }
diff --git a/src/rules.rs b/src/rules.rs
index 2aefbb3..879ea8f 100644
--- a/src/rules.rs
+++ b/src/rules.rs
@@ -3,14 +3,15 @@ use rnix::{
     types::{Lambda, TypedNode, With},
     NodeOrToken, SyntaxElement, SyntaxKind,
     SyntaxKind::*,
-    SyntaxToken, T,
+    T,
 };
 
 use crate::{
     dsl::{self, IndentDsl, IndentValue::*, SpacingDsl},
     pattern::p,
     tree_utils::{
-        get_parent, has_newline, next_non_whitespace_sibling, next_token_sibling, prev_sibling,
+        get_parent, has_newline, next_non_whitespace_sibling, next_token_sibling,
+        prev_non_whitespace_parent, prev_sibling, prev_token_sibling,
     },
 };
 
@@ -88,9 +89,8 @@ pub(crate) fn spacing() -> SpacingDsl {
 
         .test("let   foo = bar;in  92", "let foo = bar; in 92")
         .inside(NODE_LET_IN).after(T![let]).single_space_or_optional_newline()
-        .inside(NODE_LET_IN).before(T![in]).single_space_or_optional_newline()
-        .inside(NODE_LET_IN).after(T![in]).single_space_or_newline()
-
+        .inside(NODE_LET_IN).around(T![in]).single_space_or_optional_newline()
+        
         .test("{a?3}: a", "{ a ? 3 }: a")
         .inside(NODE_PAT_ENTRY).around(T![?]).single_space()
 
@@ -135,9 +135,19 @@ pub(crate) fn spacing() -> SpacingDsl {
         })
         .add_rule(dsl::SpacingRule {
             name: None,
-            pattern: p(TOKEN_IN) & p(in_contain_linebreak_pattern),
-            space: dsl::Space { loc: dsl::SpaceLoc::After, value: dsl::SpaceValue::Newline }
+            pattern: p(TOKEN_LET) & p(let_contain_linebreak_pattern) & p(let_inside_lambda_pattern),
+            space: dsl::Space { loc: dsl::SpaceLoc::Before, value: dsl::SpaceValue::Newline }
         })
+        .add_rule(dsl::SpacingRule {
+            name: None,
+            pattern: p(TOKEN_IN) & p(in_direct_below_root_pattern),
+            space: dsl::Space { loc: dsl::SpaceLoc::After, value: dsl::SpaceValue::Newline}
+        })      
+        .add_rule(dsl::SpacingRule {
+            name: None,
+            pattern: p(TOKEN_IN) & p(prev_in_contain_linebreak_pattern),
+            space: dsl::Space { loc: dsl::SpaceLoc::Before, value: dsl::SpaceValue::Newline}
+        })  
         ;
 
     dsl
@@ -177,13 +187,38 @@ fn let_contain_linebreak_pattern(element: &SyntaxElement) -> bool {
     find(element) == Some(true)
 }
 
-fn in_contain_linebreak_pattern(element: &SyntaxElement) -> bool {
+fn let_inside_lambda_pattern(element: &SyntaxElement) -> bool {
     fn find(element: &SyntaxElement) -> Option<bool> {
-        let token_in = get_parent(element)?.next_sibling_or_token()?;
-        match token_in {
-            NodeOrToken::Node(_) => None,
-            NodeOrToken::Token(it) => Some(it.text().contains(';')),
-        }
+        let parent_let = get_parent(element)?;
+        let inside_lambda_pattern =
+            prev_non_whitespace_parent(element)?.into_token()?.text().contains(':');
+        let prev_let_linebreak_pattern = match parent_let.prev_sibling_or_token()? {
+            NodeOrToken::Node(_) => false,
+            NodeOrToken::Token(it) => it.text().contains('\n'),
+        };
+        Some(prev_let_linebreak_pattern || inside_lambda_pattern)
+    }
+
+    find(element) == Some(true)
+}
+
+fn in_direct_below_root_pattern(element: &SyntaxElement) -> bool {
+    fn find(element: &SyntaxElement) -> Option<bool> {
+        let parent_in = on_top_level(element);
+        let get_let = get_parent(element)?.first_child_or_token()?;
+        let prev_in_linebreak_pattern = let_contain_linebreak_pattern(&get_let);
+        Some(parent_in & prev_in_linebreak_pattern)
+    }
+
+    find(element) == Some(true)
+}
+
+fn prev_in_contain_linebreak_pattern(element: &SyntaxElement) -> bool {
+    fn find(element: &SyntaxElement) -> Option<bool> {
+        let prev_in_token_pattern = prev_token_sibling(element)?.text().contains('\n');
+        let get_let = get_parent(element)?.first_child_or_token()?;
+        let prev_in_linebreak_pattern = let_contain_linebreak_pattern(&get_let);
+        Some(prev_in_token_pattern || prev_in_linebreak_pattern)
     }
 
     find(element) == Some(true)
@@ -435,7 +470,7 @@ fn on_top_level(element: &SyntaxElement) -> bool {
     };
     match parent.kind() {
         NODE_ROOT => true,
-        NODE_LAMBDA | NODE_WITH | NODE_ASSERT => on_top_level(&parent.into()),
+        NODE_LAMBDA | NODE_WITH | NODE_ASSERT | NODE_LET_IN => on_top_level(&parent.into()),
         _ => false,
     }
 }
diff --git a/src/tree_utils.rs b/src/tree_utils.rs
index 5b04c2c..e204257 100644
--- a/src/tree_utils.rs
+++ b/src/tree_utils.rs
@@ -47,6 +47,21 @@ pub(crate) fn next_token_sibling(element: &SyntaxElement) -> Option<SyntaxToken>
         },
     )
 }
+
+pub(crate) fn prev_token_sibling(element: &SyntaxElement) -> Option<SyntaxToken> {
+    successors(element.prev_sibling_or_token(), |it| it.prev_sibling_or_token()).find_map(
+        |element| match element {
+            NodeOrToken::Node(_) => None,
+            NodeOrToken::Token(it) => Some(it),
+        },
+    )
+}
+
+pub(crate) fn prev_non_whitespace_parent(element: &SyntaxElement) -> Option<SyntaxElement> {
+    successors(get_parent(element)?.prev_sibling_or_token(), |it| it.prev_sibling_or_token())
+        .find(|it| it.kind() != TOKEN_WHITESPACE)
+}
+
 pub(crate) fn prev_non_whitespace_sibling(element: &SyntaxElement) -> Option<SyntaxElement> {
     successors(element.prev_sibling_or_token(), |it| it.prev_sibling_or_token())
         .find(|it| it.kind() != TOKEN_WHITESPACE)


Test patch:
diff --git a/test_data/curried_fn.good.nix b/test_data/curried_fn.good.nix
index 5dcb464..7ae57c2 100644
--- a/test_data/curried_fn.good.nix
+++ b/test_data/curried_fn.good.nix
@@ -3,6 +3,5 @@
     let
       err = t: v: abort
         "oups";
-    in
-      92;
+    in 92;
 }
diff --git a/test_data/issue-125.bad.nix b/test_data/issue-125.bad.nix
index 6b70906..3010205 100644
--- a/test_data/issue-125.bad.nix
+++ b/test_data/issue-125.bad.nix
@@ -2,7 +2,11 @@
 let
 foo = let x = y; in
 z;
-#all in one line
+# all in one line
 bar = let x = 3; in x;
-in
-body
+baz = v: let x = 3; in
+x;
+qux = v: let y = 3; in y;
+nux = v: let x = 3;
+y = 3; in y;
+in body
diff --git a/test_data/issue-125.good.nix b/test_data/issue-125.good.nix
index 15a9fce..35f3350 100644
--- a/test_data/issue-125.good.nix
+++ b/test_data/issue-125.good.nix
@@ -2,8 +2,15 @@
 let
   foo = let x = y; in
     z;
-  #all in one line
-  bar = let x = 3; in
+  # all in one line
+  bar = let x = 3; in x;
+  baz = v: let x = 3; in
     x;
+  qux = v: let y = 3; in y;
+  nux = v:
+    let
+      x = 3;
+      y = 3;
+    in y;
 in
 body
diff --git a/test_data/issue-83-2.good.nix b/test_data/issue-83-2.good.nix
index 96e82ae..47257b5 100644
--- a/test_data/issue-83-2.good.nix
+++ b/test_data/issue-83-2.good.nix
@@ -3,6 +3,5 @@
     a = ''
       foo
     '';
-  in
-    a
+  in a
 )