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

Issue:
sample: `++` folding
nixpkgs-fmt should respect `\n` and never fold lines back into one

## Input
```nix
foo ++
bar
```

## Output
```nix
foo ++ bar

```

## Desired output

```nix
foo
++ bar
```


Pull request description:
Prefer wrapping before binary operators
Fixes #165. Comments welcome.

This is a bit tricky because it relies on the order of the rules to move
the newline around, and let the previous rules do the right job.

Also, `next_sibling_has_newline` is calling for composition like this,
but I could not make it happen in rust.

```
with_next_sibling(has_newline)
has_newline . next_sibling
next_sibling >>= has_newline
```

Patch:
diff --git a/src/dsl.rs b/src/dsl.rs
index 1bed023..753872b 100644
--- a/src/dsl.rs
+++ b/src/dsl.rs
@@ -190,6 +190,10 @@ impl<'a> SpacingRuleBuilder<'a> {
     pub(crate) fn no_space_or_newline(self) -> &'a mut SpacingDsl {
         self.finish(SpaceValue::NoneOrNewline)
     }
+    /// Enforce a newline
+    pub(crate) fn newline(self) -> &'a mut SpacingDsl {
+        self.finish(SpaceValue::Newline)
+    }
     fn finish(self, value: SpaceValue) -> &'a mut SpacingDsl {
         assert!(self.between.is_some() ^ self.child.is_some());
         let parent = self.parent.expect("parent must be set for each rule");
diff --git a/src/rules.rs b/src/rules.rs
index 879ea8f..3b0d14e 100644
--- a/src/rules.rs
+++ b/src/rules.rs
@@ -31,6 +31,8 @@ pub(crate) fn spacing() -> SpacingDsl {
         .inside(NODE_KEY_VALUE).before(T![;]).no_space_or_optional_newline()
         .inside(NODE_KEY_VALUE).before(T![;]).when(after_literal).no_space()
 
+        .test("a ++\nb", "a\n++ b")
+        .inside(NODE_BIN_OP).before(BIN_OPS).when(next_sibling_has_newline).newline()
         .test("a==  b", "a == b")
         .test("a!=  b", "a != b")
         .test("a++  b", "a ++ b")
@@ -166,6 +168,13 @@ fn after_literal(node: &SyntaxElement) -> bool {
     }
 }
 
+fn next_sibling_has_newline(element: &SyntaxElement) -> bool {
+    element
+        .next_sibling_or_token()
+        .and_then(|e| e.into_token().map(|t| t.text().contains("\n")))
+        .unwrap_or(false)
+}
+
 fn next_sibling_is_multiline_lambda_pattern(element: &SyntaxElement) -> bool {
     fn find(element: &SyntaxElement) -> Option<bool> {
         let lambda = next_non_whitespace_sibling(element)?.into_node().and_then(Lambda::cast)?;


Test patch:
diff --git a/test_data/binop_wrap_before.bad.nix b/test_data/binop_wrap_before.bad.nix
new file mode 100644
index 0000000..f3d5e91
--- /dev/null
+++ b/test_data/binop_wrap_before.bad.nix
@@ -0,0 +1,8 @@
+{
+a = "foo" ++
+"bar" ++
+"lol"
+++
+"bla"
+;
+}
diff --git a/test_data/binop_wrap_before.good.nix b/test_data/binop_wrap_before.good.nix
new file mode 100644
index 0000000..86bed0a
--- /dev/null
+++ b/test_data/binop_wrap_before.good.nix
@@ -0,0 +1,7 @@
+{
+  a = "foo"
+  ++ "bar"
+  ++ "lol"
+  ++ "bla"
+  ;
+}
diff --git a/test_data/nested_indent_after_binop.good.nix b/test_data/nested_indent_after_binop.good.nix
index c480416..4e86964 100644
--- a/test_data/nested_indent_after_binop.good.nix
+++ b/test_data/nested_indent_after_binop.good.nix
@@ -1,5 +1,6 @@
 pkgs.example rec {
-  buildInputs = [ pkgs.foo ] ++ pkgs.stdenv.lib.optionals pkgs.stdenv.isDarwin [
+  buildInputs = [ pkgs.foo ]
+  ++ pkgs.stdenv.lib.optionals pkgs.stdenv.isDarwin [
     pkgs.bar
   ];
 }