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

Issue:
Avoid opening a scope for parentheses and for single-element lists.
Looking at the diff produced by @zimbatm on [discourse](https://discourse.nixos.org/t/nixpkgs-fmt-beta-0-3-1/3812/5?u=layus), the main differences that I see are on using compact forms to avoid extra lines.
For example:

```diff
diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix
index 7ff22fb61fa..87d2ca3aa43 100644
--- a/maintainers/maintainer-list.nix
+++ b/maintainers/maintainer-list.nix
@@ -51,10 +51,12 @@
     name = "Joachim Ernst";
     github = "0x4A6F";
     githubId = 9675338;
-    keys = [{
-      longkeyid = "rsa8192/0x87027528B006D66D";
-      fingerprint = "F466 A548 AD3F C1F1 8C88  4576 8702 7528 B006 D66D";
-    }];
+    keys = [
+      {
+        longkeyid = "rsa8192/0x87027528B006D66D";
+        fingerprint = "F466 A548 AD3F C1F1 8C88  4576 8702 7528 B006 D66D";
+      }
+    ];
   };
   "1000101" = {
     email = "jan.hrnko@satoshilabs.com";
```

and this occurs in many places, like for `fetch*` calls  

```diff
diff --git a/pkgs/development/python-modules/pyscard/default.nix b/pkgs/development/python-modules/pyscard/default.nix
index bab8e502b9c..e601f23ada9 100644
--- a/pkgs/development/python-modules/pyscard/default.nix
+++ b/pkgs/development/python-modules/pyscard/default.nix
@@ -19,10 +19,12 @@ buildPythonPackage rec {
   patches = [
     # Fixes darwin tests
     # See: https://github.com/LudovicRousseau/pyscard/issues/77
-    (fetchpatch {
-      url = "https://github.com/LudovicRousseau/pyscard/commit/62e675028086c75656444cc21d563d9f08ebf8e7.patch";
-      sha256 = "1lr55npcpc8j750vf7vaisqyk18d5f00l7nii2lvawg4sssjaaf7";
-    })
+    (
+      fetchpatch {
+        url = "https://github.com/LudovicRousseau/pyscard/commit/62e675028086c75656444cc21d563d9f08ebf8e7.patch";
+        sha256 = "1lr55npcpc8j750vf7vaisqyk18d5f00l7nii2lvawg4sssjaaf7";
+      }
+    )
   ];
```

There seems to be an implicit rule that collapsing delimiters (parentheses, square brackets and such) is desirable if there is only one expression inside. Furthermore, the collapsing results in a single indent, instead of two (or more).

In short: 

## Input
```nix
{ 
  testA= [(fetchurl {
  url = "bla";
})];

 testB = [( fetchurl{url="bla";} )];
}
```

## Output
```nix
{
  testA = [
    (
      fetchurl {
        url = "bla";
      }
    )
  ];

  testB = [ (fetchurl { url = "bla"; }) ];
}
```

## Desired output

```nix
{
  testA = [ 
    (fetchurl {
      url = "bla";
    })
  ];

  testB = [ (fetchurl { url = "bla"; }) ];
}
```
or

```nix
{
  testA = [ (fetchurl {
    url = "bla";
  }) ];

  testB = [ (fetchurl { url = "bla"; }) ];
}
```

or (mind the space changes)

```nix
{
  testA = [(fetchurl {
    url = "bla";
  })];

  testB = [ (fetchurl { url = "bla"; }) ];
}
```

Of course, when the list constains mutiple elements, we should switch to a safer form.

## Input
```nix
{ 
  testA= [(fetchurl {
  url = "bla";
})(fetchurl {
  url = "foo";
})];

 testB = [( fetchurl{url="bla";} )( fetchurl{url="foo";} )];
}
```

## Output
```nix
{
  testA = [
    (
      fetchurl {
        url = "bla";
      }
    )
    (
      fetchurl {
        url = "foo";
      }
    )
  ];

  testB = [ (fetchurl { url = "bla"; }) (fetchurl { url = "foo"; }) ];
}

```

## Desired output

```nix
{
  testA = [
    (fetchurl {
      url = "bla";
    })
    (fetchurl {
      url = "foo";
    })
  ];

  testB = [ (fetchurl { url = "bla"; }) (fetchurl { url = "foo"; }) ];
}
```

The simplest way to achieve this would be to make parentheses special. When they are expanded (split on several lines) do not force a line break after them. that would still keep the following desired output impossible, but get all the other options above right.

```nix
{
  testA = [(fetchurl {
    url = "bla";
  })];

  testB = [ (fetchurl { url = "bla"; }) ];
}
```

Pull request:
Fix #126: parentheses and list element rules
This PR will fix list item and parentheses

Patch:
diff --git a/nix/nixpkgs.nix b/nix/nixpkgs.nix
index 2bad71c..50fdd57 100644
--- a/nix/nixpkgs.nix
+++ b/nix/nixpkgs.nix
@@ -5,11 +5,9 @@ let
     config = {};
     overlays = [
       (import sources.nixpkgs-mozilla)
-      (
-        self: pkgs: {
-          naersk = pkgs.callPackage sources.naersk {};
-        }
-      )
+      (self: pkgs: {
+        naersk = pkgs.callPackage sources.naersk {};
+      })
     ];
   };
 in
diff --git a/src/engine.rs b/src/engine.rs
index 525c4d6..fd13bee 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -49,7 +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.
+        // 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 44c8733..16b1edd 100644
--- a/src/rules.rs
+++ b/src/rules.rs
@@ -10,8 +10,9 @@ use crate::{
     dsl::{self, IndentDsl, IndentValue::*, SpacingDsl},
     pattern::p,
     tree_utils::{
-        get_parent, has_newline, next_non_whitespace_sibling, next_token_sibling,
-        prev_non_whitespace_parent, prev_sibling, prev_token_sibling,
+        get_parent, has_newline, next_non_whitespace_parent, next_non_whitespace_sibling,
+        next_token_sibling, prev_non_whitespace_parent, prev_non_whitespace_sibling, prev_sibling,
+        prev_token_sibling, walk_tokens,
     },
 };
 
@@ -42,6 +43,7 @@ pub(crate) fn spacing() -> SpacingDsl {
         .test("a/  b", "a / b")
         .inside(NODE_BIN_OP).after(BIN_OPS).single_space()
         .inside(NODE_BIN_OP).before(BIN_OPS).single_space_or_optional_newline()
+        .inside(NODE_BIN_OP).before(BIN_OPS).when(prev_is_attrset).newline()
 
         .test("foo . bar . baz", "foo.bar.baz")
         .inside(NODE_SELECT).around(T![.]).no_space()
@@ -60,12 +62,19 @@ pub(crate) fn spacing() -> SpacingDsl {
         .inside(NODE_LIST).between(TOKEN_COMMENT, VALUES).single_space_or_newline()
 
         .test("( 92 )", "(92)")
+        .inside(NODE_PAREN).before(T!["("]).when(paren_inside_list).when(parent_has_newline).newline()
         .inside(NODE_PAREN).after(T!["("]).no_space_or_optional_newline()
+        .inside(NODE_PAREN).after(T!["("]).when(paren_has_token_update).newline()        
         .inside(NODE_PAREN).before(T![")"]).no_space_or_newline()
-
+        .inside(NODE_PAREN).before(T![")"]).when(paren_inside_list).no_space()
+        .inside(NODE_PAREN).before(T![")"]).when(prev_paren_has_update).when(parent_has_newline).newline()
+        .inside(NODE_PAREN).before(T![")"]).when(prev_is_attrset).no_space()
+        
         .test("{foo = 92;}", "{ foo = 92; }")
         .inside(NODE_ATTR_SET).after(T!["{"]).single_space_or_newline()
+        .inside(NODE_ATTR_SET).before(T!["{"]).when(next_parent_has_update).newline()
         .inside(NODE_ATTR_SET).before(T!["}"]).single_space_or_newline()
+        .inside(NODE_ATTR_SET).before(T!["}"]).when(next_parent_has_update).newline()      
         .test("{ }", "{}")
         .inside(NODE_ATTR_SET).between(T!["{"], T!["}"]).no_space()
         .inside(NODE_ATTR_SET).between(NODE_KEY_VALUE, NODE_KEY_VALUE).single_space_or_newline()
@@ -205,6 +214,46 @@ fn next_sibling_is_paren(element: &SyntaxElement) -> bool {
         .unwrap_or(false)
 }
 
+fn paren_inside_list(element: &SyntaxElement) -> bool {
+    element.as_token().map(|e| e.ancestors().any(|n| n.kind() == NODE_LIST)).unwrap_or(false)
+}
+
+fn parent_has_newline(element: &SyntaxElement) -> bool {
+    element.parent().map(|e| has_newline(&e)).unwrap_or(false)
+}
+
+fn prev_is_attrset(element: &SyntaxElement) -> bool {
+    prev_non_whitespace_sibling(element)
+        .and_then(|e| e.into_node().map(|n| n.kind() == NODE_ATTR_SET))
+        .unwrap_or(false)
+}
+
+fn next_is_attrset(element: &SyntaxElement) -> bool {
+    next_non_whitespace_sibling(element)
+        .and_then(|e| e.into_node().map(|n| n.kind() == NODE_ATTR_SET))
+        .unwrap_or(false)
+}
+
+fn prev_paren_has_update(element: &SyntaxElement) -> bool {
+    prev_sibling(element)
+        .map(|n| walk_tokens(&n).any(|it| it.kind() == TOKEN_UPDATE))
+        .unwrap_or(false)
+}
+
+fn next_parent_has_update(element: &SyntaxElement) -> bool {
+    next_non_whitespace_parent(element)
+        .and_then(|e| e.into_token().map(|it| it.kind() == TOKEN_UPDATE))
+        .unwrap_or(false)
+}
+
+fn paren_has_token_update(element: &SyntaxElement) -> bool {
+    let token_update = element
+        .parent()
+        .map(|n| walk_tokens(&n).any(|it| it.kind() == TOKEN_UPDATE))
+        .unwrap_or(false);
+    token_update & next_is_attrset(element)
+}
+
 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)?;
diff --git a/src/tree_utils.rs b/src/tree_utils.rs
index e204257..47b1a84 100644
--- a/src/tree_utils.rs
+++ b/src/tree_utils.rs
@@ -16,6 +16,7 @@ pub(crate) fn walk_non_whitespace(node: &SyntaxNode) -> impl Iterator<Item = Syn
         WalkEvent::Leave(_) => None,
     })
 }
+
 pub(crate) fn walk_tokens(node: &SyntaxNode) -> impl Iterator<Item = SyntaxToken> {
     walk(node).filter_map(|element| match element {
         NodeOrToken::Token(token) => Some(token),
@@ -67,6 +68,11 @@ pub(crate) fn prev_non_whitespace_sibling(element: &SyntaxElement) -> Option<Syn
         .find(|it| it.kind() != TOKEN_WHITESPACE)
 }
 
+pub(crate) fn next_non_whitespace_parent(element: &SyntaxElement) -> Option<SyntaxElement> {
+    successors(get_parent(element)?.next_sibling_or_token(), |it| it.next_sibling_or_token())
+        .find(|it| it.kind() != TOKEN_WHITESPACE)
+}
+
 pub(crate) fn next_non_whitespace_sibling(element: &SyntaxElement) -> Option<SyntaxElement> {
     successors(element.next_sibling_or_token(), |it| it.next_sibling_or_token())
         .find(|it| it.kind() != TOKEN_WHITESPACE)


Test patch:
diff --git a/test_data/issue-126.bad.nix b/test_data/issue-126.bad.nix
new file mode 100644
index 0000000..3e5c843
--- /dev/null
+++ b/test_data/issue-126.bad.nix
@@ -0,0 +1,47 @@
+{ 
+  testA= [(fetchurl {
+  url = "bla";
+})];
+
+  testB = [( fetchurl{url="bla";} )];
+
+  testC = { 
+  testA1= [(fetchurl {
+  url = "bla";
+})];
+
+ testB1 = [( fetchurl{url="bla";} )];
+  };
+
+  testD= [(fetchurl {
+  url = "bla";
+})(fetchurl {
+  url = "foo";
+})];
+
+ testE = [( fetchurl{url="bla";} )( fetchurl{url="foo";} )];
+ testF = [( fetchurl{
+   url="bla";} )( fetchurl{url="foo";} )];
+ testG = [( fetchurl{url="bla";} ) bar ];
+ testH = [( fetchurl{url="bla";} )
+bar ];
+
+ testI = { stdenv, ... } @ args:
+let
+  foo = "0.95";
+in
+buildLinux ({
+  version = "${foo}-mptcp_v1.0.0";
+  inherit bar;
+} // args);
+
+testJ = { stdenv, ... } @ args:
+let
+  foo = "0.95";
+in
+buildLinux ({
+  version = "${foo}-mptcp_v1.0.0";
+  inherit bar;
+}
+);
+}
diff --git a/test_data/issue-126.good.nix b/test_data/issue-126.good.nix
new file mode 100644
index 0000000..a6ef58a
--- /dev/null
+++ b/test_data/issue-126.good.nix
@@ -0,0 +1,62 @@
+{
+  testA = [
+    (fetchurl {
+      url = "bla";
+    })
+  ];
+
+  testB = [ (fetchurl { url = "bla"; }) ];
+
+  testC = {
+    testA1 = [
+      (fetchurl {
+        url = "bla";
+      })
+    ];
+
+    testB1 = [ (fetchurl { url = "bla"; }) ];
+  };
+
+  testD = [
+    (fetchurl {
+      url = "bla";
+    })
+    (fetchurl {
+      url = "foo";
+    })
+  ];
+
+  testE = [ (fetchurl { url = "bla"; }) (fetchurl { url = "foo"; }) ];
+  testF = [
+    (fetchurl {
+      url = "bla";
+    })
+    (fetchurl { url = "foo"; })
+  ];
+  testG = [ (fetchurl { url = "bla"; }) bar ];
+  testH = [
+    (fetchurl { url = "bla"; })
+    bar
+  ];
+
+  testI = { stdenv, ... } @ args:
+    let
+      foo = "0.95";
+    in
+      buildLinux (
+        {
+          version = "${foo}-mptcp_v1.0.0";
+          inherit bar;
+        }
+        // args
+      );
+
+  testJ = { stdenv, ... } @ args:
+    let
+      foo = "0.95";
+    in
+      buildLinux ({
+        version = "${foo}-mptcp_v1.0.0";
+        inherit bar;
+      });
+}