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

Issue:
sample: closing bracket line collapse
Closing brackets in multi line strings don't get collapsed into the previous line.

## Input
```nix
let
  cfg = ''
    ${call
    (value: ''
        ${value}
      ''
    )
  }
  '';
in
cfg

```

## Output
```nix
let
  cfg = ''
    ${call
      (value: ''
        ${value}
      ''
        )
    }
  '';
in
cfg

```

## Desired output

```nix
let
  cfg = ''
    ${call
      (value: ''
        ${value}
      '')
    }
  '';
in
cfg

```


Pull request description:
Fix #198: Apply parentheses rule when inline with multi-line string

Patch:
diff --git a/flake.nix b/flake.nix
index d11d0a5..505403d 100644
--- a/flake.nix
+++ b/flake.nix
@@ -18,8 +18,8 @@
         nixpkgs-fmt = final.callPackage ./. { };
       };
 
-      packages = forAllSystems
-        (
+      packages =
+        forAllSystems (
           system:
           {
             nixpkgs-fmt = (
@@ -35,14 +35,14 @@
         );
 
 
-      defaultPackage = forAllSystems
-        (
+      defaultPackage =
+        forAllSystems (
           system:
           self.packages.${system}.nixpkgs-fmt
         );
 
-      apps = forAllSystems
-        (
+      apps =
+        forAllSystems (
           system:
           {
             nixpkgs-fmt = {
@@ -52,8 +52,8 @@
           }
         );
 
-      defaultApp = forAllSystems
-        (
+      defaultApp =
+        forAllSystems (
           system:
           self.apps.${system}.nixpkgs-fmt
         );
diff --git a/shell.nix b/shell.nix
index 64e969c..cd8959a 100644
--- a/shell.nix
+++ b/shell.nix
@@ -5,23 +5,24 @@ let
     stdenv
     darwin
     ;
-  rust = (
-    pkgs.rustChannelOf {
-      date = "2019-09-13";
-      channel = "nightly";
-      sha256 = "06881g7ba2hzmfq5vaz888d2q762zf4bxjc621rw3g8z702ps7w9";
-    }
-  ).rust.override {
-    extensions = [
-      "clippy-preview"
-      "rls-preview"
-      "rustfmt-preview"
-      "rust-analysis"
-      "rust-std"
-      "rust-src"
-    ];
-    targets = [ "wasm32-unknown-unknown" ];
-  };
+  rust =
+    (
+      pkgs.rustChannelOf {
+        date = "2019-09-13";
+        channel = "nightly";
+        sha256 = "06881g7ba2hzmfq5vaz888d2q762zf4bxjc621rw3g8z702ps7w9";
+      }
+    ).rust.override {
+      extensions = [
+        "clippy-preview"
+        "rls-preview"
+        "rustfmt-preview"
+        "rust-analysis"
+        "rust-std"
+        "rust-src"
+      ];
+      targets = [ "wasm32-unknown-unknown" ];
+    };
 in
 pkgs.mkShell {
   buildInputs = [
diff --git a/src/engine/fixes.rs b/src/engine/fixes.rs
index d216295..db1015c 100644
--- a/src/engine/fixes.rs
+++ b/src/engine/fixes.rs
@@ -182,6 +182,10 @@ fn fix_string_interpolation(
     anchor_set: &PatternSet<&Pattern>,
 ) {
     for element in walk_non_whitespace(node) {
+        if element.parent().map(|it| it.text_range().start()) == Some(element.text_range().start())
+        {
+            continue;
+        }
         string_interpol_indent(&element, model, anchor_set);
     }
 }
diff --git a/src/engine/indentation.rs b/src/engine/indentation.rs
index 20372ce..b86f586 100644
--- a/src/engine/indentation.rs
+++ b/src/engine/indentation.rs
@@ -3,17 +3,13 @@ use std::{
     fmt,
 };
 
-use rnix::{
-    NodeOrToken, SmolStr, SyntaxElement, SyntaxKind,
-    SyntaxKind::{NODE_ROOT, TOKEN_ELSE, TOKEN_IN, TOKEN_THEN},
-    SyntaxNode, SyntaxToken, TextUnit,
-};
+use rnix::{SmolStr, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxNode, SyntaxToken, TextUnit};
 
 use crate::{
     dsl::{IndentRule, Modality, RuleName},
     engine::{BlockPosition, FmtModel, SpaceBlock, SpaceBlockOrToken},
     pattern::{Pattern, PatternSet},
-    tree_utils::prev_non_whitespace_token_sibling,
+    tree_utils::{prev_non_whitespace_token_sibling, prev_token_sibling},
 };
 
 const INDENT_SIZE: u32 = 2;
@@ -193,29 +189,43 @@ pub(super) fn string_interpol_indent(
     model: &mut FmtModel,
     anchor_set: &PatternSet<&Pattern>,
 ) {
+    let indent = IndentLevel::default();
     let anchor_indent = match indent_anchor(&element, model, anchor_set) {
         Some((_anchor, indent)) => indent,
         _ => IndentLevel::default(),
     };
+
+    let anchor_custom =
+        indent_custom_anchor(&element, model, NODE_STRING_INTERPOL, anchor_set).unwrap_or(indent);
+
     let block = model.block_for(&element, BlockPosition::Before);
     if !block.has_newline() {
         // No need to indent an element if it doesn't start a line
         return;
     }
-
-    if element.parent().map(|it| it.text_range().start()) == Some(element.text_range().start()) {
-        match element {
-            NodeOrToken::Token(_token) => {
-                block.set_indent(anchor_indent, RuleName::new("String Interpolation Value"))
+    match element {
+        e if e.kind() == NODE_APPLY => {
+            let node_string_count =
+                e.ancestors().filter(|e| e.kind() == NODE_STRING_INTERPOL).count();
+            let node_string = e
+                .ancestors()
+                .find(|e| e.kind() == NODE_STRING)
+                .and_then(|t| prev_token_sibling(&t.into()).map(|e| e.text().contains("\n")))
+                .unwrap_or(false);
+            if node_string_count < 2 && !node_string {
+                block
+                    .set_indent(anchor_custom.indent(), RuleName::new("String Interpolation Value"))
+            } else {
+                block.set_indent(anchor_custom, RuleName::new("String Interpolation Value"))
             }
-            _ => return,
         }
-    }
-
-    if element.kind() == TOKEN_THEN || element.kind() == TOKEN_ELSE {
-        block.set_indent(anchor_indent, RuleName::new("String Interpolation Value"))
-    } else {
-        block.set_indent(anchor_indent.indent(), RuleName::new("String Interpolation"))
+        e if e.kind() == TOKEN_INTERPOL_END => {
+            block.set_indent(anchor_indent.indent(), RuleName::new("String Interpolation Value"))
+        }
+        e if e.kind() == TOKEN_SQUARE_B_CLOSE => {
+            block.set_indent(anchor_custom, RuleName::new("String Interpolation Value"))
+        }
+        _ => block.set_indent(anchor_custom.indent(), RuleName::new("String Interpolation Value")),
     }
 }
 
diff --git a/src/rules.rs b/src/rules.rs
index e5f0012..969c55f 100644
--- a/src/rules.rs
+++ b/src/rules.rs
@@ -69,12 +69,16 @@ pub(crate) fn spacing() -> SpacingDsl {
         .inside(NODE_PAREN).before(T![")"]).when(paren_open_newline).newline()
         .inside(NODE_PAREN).before(T![")"]).when(node_inside_paren).when(between_open_paren_newline).newline()
         .inside(NODE_PAREN).before(T![")"]).when(node_inside_paren).when(between_open_paren_not_newline).no_space()
+        
         .inside(NODE_PAREN).before(T![")"]).when(prev_is_let).when(paren_on_top_level).newline()
+        .inside(NODE_PAREN).before(T![")"]).when(prev_is_if).when(not_inside_node_interpol).newline()
+        .inside(NODE_PAREN).before(T![")"]).when(inside_multiple_argument_function).newline()
         .test("{foo = 92;}", "{ foo = 92; }")
         .inside(NODE_ATTR_SET).after(T!["{"]).single_space_or_newline()
         .inside(NODE_ATTR_SET).before(T!["}"]).single_space_or_newline()
         .test("{}", "{ }")
         .inside(NODE_ATTR_SET).between(T!["{"], T!["}"]).single_space()
+        .inside(NODE_ATTR_SET).before(NODE_KEY_VALUE).single_space_or_optional_newline()
         .inside(NODE_ATTR_SET).between(NODE_KEY_VALUE, NODE_KEY_VALUE).single_space_or_newline()
         .inside(NODE_ATTR_SET).between(NODE_KEY_VALUE, TOKEN_COMMENT).single_space_or_optional_newline()
 
@@ -109,8 +113,20 @@ pub(crate) fn spacing() -> SpacingDsl {
         
         .test("f  x", "f x")
         .inside(NODE_APPLY).between(VALUES, VALUES).single_space_or_optional_newline()
-        .inside(NODE_APPLY).before(NODE_PAREN).when(inside_node_has_newline).when(not_inside_node_interpol).newline()
+        .inside(NODE_APPLY).before(NODE_PAREN).when(last_argument_in_function).single_space()
+        .inside(NODE_APPLY).before(NODE_PAREN).when(last_argument_in_function).when(multi_argument_in_function).when(between_argument_has_newline).newline()
+        .inside(NODE_APPLY).before(NODE_PAREN).when(non_last_argument_in_function).when(between_argument_has_newline).when(not_inside_node_interpol).newline()
         .inside(NODE_APPLY).before(NODE_PAREN).when(inside_node_interpol).single_space_or_optional_newline()
+        .inside(NODE_APPLY).before(NODE_LIST).when(last_argument_in_function).single_space()
+        .inside(NODE_APPLY).before(NODE_LIST).when(last_argument_in_function).when(multi_argument_in_function).when(between_argument_has_newline).newline()
+        .inside(NODE_APPLY).before(NODE_ATTR_SET).when(last_argument_in_function).single_space()
+        .inside(NODE_APPLY).before(NODE_ATTR_SET).when(non_last_argument_in_function).when(node_apply_has_newline).when(not_inside_node_interpol).newline()
+        .inside(NODE_APPLY).before(NODE_IDENT).when(node_is_argument).when(last_argument_in_function).when(node_apply_has_newline).when(not_inside_node_interpol).newline()
+        .inside(NODE_APPLY).before(NODE_IDENT).when(node_is_argument).when(non_last_argument_in_function).when(between_argument_has_newline).when(not_inside_node_interpol).newline()
+        .inside(NODE_APPLY).before(NODE_SELECT).when(node_is_argument).when(last_argument_in_function).when(node_apply_has_newline).when(not_inside_node_interpol).newline()
+        .inside(NODE_APPLY).before(NODE_SELECT).when(node_is_argument).when(non_last_argument_in_function).when(between_argument_has_newline).when(not_inside_node_interpol).newline()
+        .inside(NODE_APPLY).before(NODE_IDENT).when(node_is_function).when(outside_inline_pattern).when(between_argument_has_newline).when(not_inside_node_interpol).newline()
+        .inside(NODE_APPLY).before(NODE_SELECT).when(node_is_function).when(outside_inline_pattern).when(between_argument_has_newline).when(not_inside_node_interpol).newline()
 
         .test("if  cond  then  tru  else  fls", "if cond then tru else fls")
         .inside(NODE_IF_ELSE).before(T![if]).when(not_on_top_level).when(not_inline_if).single_space_or_newline()
@@ -120,6 +136,7 @@ pub(crate) fn spacing() -> SpacingDsl {
         .inside(NODE_IF_ELSE).before(T![then]).single_space_or_optional_newline()
         .inside(NODE_IF_ELSE).after([T![if],T![then]]).single_space_or_optional_newline()
         .inside(NODE_IF_ELSE).around(T![else]).single_space_or_optional_newline()
+        .inside(NODE_IF_ELSE).after(T![else]).when(after_else_has_newline).newline()
         
         // special-case to force a linebreak before `=` in
         //
@@ -199,9 +216,117 @@ fn inline_with_attr_set(element: &SyntaxElement) -> bool {
         && element.parent().and_then(|e| e.first_child().map(|n| n.kind() == NODE_ATTR_SET))
             == Some(true)
 }
-fn inside_node_has_newline(element: &SyntaxElement) -> bool {
-    element.as_node().map(|e| has_newline(e)).unwrap_or(false)
+
+fn node_apply_has_newline(element: &SyntaxElement) -> bool {
+    fn top_level_node_function(element: &SyntaxElement) -> Option<bool> {
+        element
+            .parent()?
+            .ancestors()
+            .take_while(|e| {
+                e.kind() != NODE_KEY_VALUE && e.kind() != NODE_IF_ELSE && e.kind() != NODE_PAREN
+            })
+            .filter(|e| e.kind() == NODE_APPLY)
+            .max_by_key(|e| e.text_range().start())
+            .map(|e| has_newline(&e))
+    }
+    top_level_node_function(element).unwrap_or(false)
+}
+
+fn between_argument_has_newline(element: &SyntaxElement) -> bool {
+    fn newline_in_between(element: &SyntaxElement) -> bool {
+        let prev_argument =
+            prev_token_sibling(element).map(|e| e.text().contains("\n")).unwrap_or(false);
+        let prev_sibling = prev_sibling(element).map(|e| has_newline(&e)).unwrap_or(false);
+        prev_argument || prev_sibling
+    }
+
+    fn between_arg_newline(element: &SyntaxElement) -> Option<bool> {
+        let list_node_apply = element
+            .parent()?
+            .ancestors()
+            .take_while(|e| {
+                e.kind() != NODE_KEY_VALUE && e.kind() != NODE_IF_ELSE && e.kind() != NODE_PAREN
+            })
+            .filter_map(|e| match e.kind() {
+                NODE_APPLY => e.last_child(),
+                _ => None,
+            });
+        let exist_newline = list_node_apply.fold(false, |b, e| b || newline_in_between(&e.into()));
+        Some(exist_newline)
+    }
+
+    between_arg_newline(element).unwrap_or(false)
+}
+
+fn node_is_argument(element: &SyntaxElement) -> bool {
+    fn is_argument(element: &SyntaxElement) -> Option<bool> {
+        let element_text_range = element.text_range().start();
+        Some(element.parent()?.last_child()?.text_range().start() == element_text_range)
+    }
+    is_argument(element).unwrap_or(false)
 }
+
+fn last_argument_in_function(element: &SyntaxElement) -> bool {
+    fn is_last_argument(element: &SyntaxElement) -> Option<bool> {
+        let inside_apply = element.parent()?.kind() == NODE_APPLY;
+        let last_argument = element.parent()?.parent()?.kind() != NODE_APPLY;
+        Some(last_argument && inside_apply)
+    }
+    is_last_argument(element).unwrap_or(false)
+}
+
+// Check whether the function is unary function or not
+fn multi_argument_in_function(element: &SyntaxElement) -> bool {
+    prev_sibling(element).map(|e| e.kind() == NODE_APPLY).unwrap_or(false)
+}
+
+fn non_last_argument_in_function(element: &SyntaxElement) -> bool {
+    !last_argument_in_function(element)
+}
+
+fn node_is_function(element: &SyntaxElement) -> bool {
+    fn is_function(element: &SyntaxElement) -> Option<bool> {
+        let element_text_range = element.text_range().start();
+        Some(element.parent()?.first_child()?.text_range().start() == element_text_range)
+    }
+    is_function(element).unwrap_or(false) && not_on_top_level(element)
+}
+
+// Special case if function is nested inside certain node
+fn outside_inline_pattern(element: &SyntaxElement) -> bool {
+    let node = element.ancestors().find(|e| {
+        e.kind() == NODE_PATTERN
+            || e.kind() == NODE_BIN_OP
+            || e.kind() == NODE_IF_ELSE
+            || e.kind() == NODE_WITH
+            || e.kind() == NODE_INHERIT
+            || e.kind() == NODE_OR_DEFAULT
+            || e.kind() == NODE_ASSERT
+            || e.kind() == NODE_LAMBDA
+    });
+
+    node.and_then(|e| match e.kind() {
+        NODE_ASSERT => {
+            let closing_semicolon = element.text_range().start();
+            let has_newline = e
+                .descendants_with_tokens()
+                .filter(|element| match element {
+                    NodeOrToken::Token(_) => true,
+                    _ => false,
+                })
+                .take_while(|e| e.text_range().start() != closing_semicolon)
+                .any(|t| t.as_token().map(|n| n.text().contains("\n")).unwrap_or(false));
+            Some(has_newline)
+        }
+        NODE_LAMBDA => Some(false),
+
+        NODE_IF_ELSE => Some(false),
+
+        _ => Some(true),
+    })
+    .unwrap_or(true)
+}
+
 fn paren_open_newline(element: &SyntaxElement) -> bool {
     fn after_paren_open_newline(element: &SyntaxElement) -> Option<bool> {
         element
@@ -212,13 +337,47 @@ fn paren_open_newline(element: &SyntaxElement) -> bool {
     after_paren_open_newline(element) == Some(true)
 }
 
+// Check whether there is exists function call inside parentheses
+fn inside_multiple_argument_function(element: &SyntaxElement) -> bool {
+    fn multiple_argument_function(element: &SyntaxElement) -> Option<bool> {
+        let first_node = element.parent()?.first_child()?;
+        let contain_node_apply = first_node.clone().kind() == NODE_APPLY;
+        let contain_multiple_argument_function =
+            multi_argument_in_function(&first_node.clone().last_child()?.into());
+        if contain_node_apply && contain_multiple_argument_function {
+            let exist_newline = first_node
+                .clone()
+                .descendants_with_tokens()
+                .filter(|element| match element {
+                    NodeOrToken::Token(_) => true,
+                    _ => false,
+                })
+                .any(|t| t.as_token().map(|n| n.text().contains("\n")).unwrap_or(false));
+            Some(exist_newline)
+        } else {
+            None
+        }
+    }
+
+    multiple_argument_function(element).unwrap_or(false)
+}
+
+fn multiline_string(element: &SyntaxElement) -> bool {
+    if element.kind() == NODE_STRING {
+        element.as_node().map(|e| has_newline(&e)).unwrap_or(false);
+    }
+    false
+}
+
 fn node_inside_paren(element: &SyntaxElement) -> bool {
     fn inside_paren_exist_node(element: &SyntaxElement) -> Option<bool> {
         let open_paren_token_unit = element.parent()?.first_child_or_token()?.text_range().start();
         let paren_contain_node_unit = element
             .parent()?
             .descendants()
-            .find(|e| e.kind() == NODE_LIST || e.kind() == NODE_ATTR_SET)?
+            .find(|e| {
+                e.kind() == NODE_LIST || e.kind() == NODE_ATTR_SET || multiline_string(element)
+            })?
             .ancestors()
             .find(|e| e.kind() == NODE_PAREN)?
             .text_range()
@@ -230,8 +389,10 @@ fn node_inside_paren(element: &SyntaxElement) -> bool {
         let paren_contain_node_unit = element
             .parent()?
             .descendants_with_tokens()
-            .take_while(|e| e.kind() != NODE_LET_IN && e.kind() != NODE_KEY_VALUE)
-            .any(|t| t.kind() == NODE_LIST || t.kind() == NODE_ATTR_SET);
+            .take_while(|e| {
+                e.kind() != NODE_LET_IN && e.kind() != NODE_KEY_VALUE && e.kind() != NODE_IF_ELSE
+            })
+            .any(|t| t.kind() == NODE_LIST || t.kind() == NODE_ATTR_SET || t.kind() == NODE_STRING);
 
         Some(paren_contain_node_unit)
     }
@@ -246,17 +407,19 @@ fn between_open_paren_not_newline(element: &SyntaxElement) -> bool {
 
 fn between_open_paren_newline(element: &SyntaxElement) -> bool {
     fn after_paren_open_newline(element: &SyntaxElement) -> Option<bool> {
-        let between_paren_list_has_newline = element
+        let between_paren_node_has_newline = element
             .parent()?
             .descendants_with_tokens()
-            .take_while(|n| n.kind() != NODE_ATTR_SET && n.kind() != NODE_LIST)
+            .take_while(|n| {
+                n.kind() != NODE_ATTR_SET && n.kind() != NODE_LIST && n.kind() != NODE_STRING
+            })
             .filter(|element| match element {
                 NodeOrToken::Token(_) => true,
                 _ => false,
             })
             .any(|t| t.as_token().map(|n| n.text().contains("\n")).unwrap_or(false));
 
-        Some(between_paren_list_has_newline)
+        Some(between_paren_node_has_newline)
     }
     after_paren_open_newline(element) == Some(true)
 }
@@ -281,7 +444,6 @@ fn not_inside_node_interpol(element: &SyntaxElement) -> bool {
 
 fn inside_node_interpol(element: &SyntaxElement) -> bool {
     element.ancestors().any(|n| n.kind() == NODE_STRING_INTERPOL)
-    //.unwrap_or(false)
 }
 
 fn not_inline_if(element: &SyntaxElement) -> bool {
@@ -320,12 +482,36 @@ fn between_if_then_not_newline(element: &SyntaxElement) -> bool {
     not_inline_if_then_else(element) == Some(false)
 }
 
+// This function to make sure let..in inside else get expanded
+fn after_else_has_newline(element: &SyntaxElement) -> bool {
+    next_non_whitespace_sibling(element)
+        .and_then(|e| match e.as_node() {
+            Some(node) => {
+                if node.kind() == NODE_LET_IN {
+                    Some(has_newline(&node))
+                } else {
+                    Some(false)
+                }
+            }
+            _ => Some(false),
+        })
+        .unwrap_or(false)
+}
+
 fn prev_is_let(element: &SyntaxElement) -> bool {
     prev_non_whitespace_sibling(element)
         .and_then(|e| e.into_node().map(|n| n.kind() == NODE_LET_IN))
         .unwrap_or(false)
 }
 
+fn prev_is_if(element: &SyntaxElement) -> bool {
+    let node_if = prev_non_whitespace_sibling(element)
+        .and_then(|e| e.into_node().map(|n| n.kind() == NODE_IF_ELSE))
+        .unwrap_or(false);
+    let is_expanded_if = element.parent().map(|e| has_newline(&e)).unwrap_or(false);
+    node_if && is_expanded_if
+}
+
 fn prev_parent_is_newline(element: &SyntaxElement) -> bool {
     prev_token_parent(element).map(|n| n.text().contains('\n')).unwrap_or(false)
 }
@@ -566,10 +752,13 @@ pub(crate) fn indentation() -> IndentDsl {
         .rule("Indent lambda body")
             .inside(p(NODE_LAMBDA) & p(not_on_top_level) & p(pattern_not_newline))
             .set(Indent)
-        
+        .rule("Indent newline lambda body")
+            .inside(p(NODE_LAMBDA) & p(not_on_top_level) & p(pattern_newline) & p(lambda_inside_node_pattern))
+            .not_matching(p(TOKEN_COMMENT)) 
+            .set(Indent)
          .rule("Indent newline lambda body")
-            .inside(p(NODE_LAMBDA) & p(not_on_top_level) & p(pattern_newline))
-            .not_matching(p([TOKEN_COMMENT]) | p(VALUES)) 
+            .inside(p(NODE_LAMBDA) & p(not_on_top_level) & p(pattern_newline) & p(lambda_outside_node_pattern))
+            .not_matching(p(TOKEN_COMMENT) | p(VALUES)) 
             .set(Indent)
             .test(r#"
                 {}:
@@ -602,7 +791,8 @@ pub(crate) fn indentation() -> IndentDsl {
                 bar baz
             "#, r#"
                 foo
-                  bar baz
+                  bar
+                  baz
             "#)
 
         .rule("Indent apply arg")
@@ -614,7 +804,8 @@ pub(crate) fn indentation() -> IndentDsl {
                 bar baz
             "#, r#"
                 foo
-                  bar baz
+                  bar
+                  baz
             "#)
 
         .rule("Indent apply arg")
@@ -662,7 +853,7 @@ pub(crate) fn indentation() -> IndentDsl {
         
         .rule("Indent if-then-else")
             .inside(p(NODE_IF_ELSE) & p(not_inline_if_else))
-            .not_matching([T![if], T![then], T![else]])
+            .not_matching([T![if], T![then], T![else], TOKEN_COMMENT])
             .set(Indent)
             .test(r#"
                 if
@@ -763,6 +954,14 @@ fn pattern_not_newline(element: &SyntaxElement) -> bool {
     !pattern_newline(element)
 }
 
+fn lambda_inside_node_pattern(element: &SyntaxElement) -> bool {
+    element.ancestors().any(|e| e.kind() == NODE_PATTERN)
+}
+
+fn lambda_outside_node_pattern(element: &SyntaxElement) -> bool {
+    !lambda_inside_node_pattern(element)
+}
+
 fn after_concat_is_newline(element: &SyntaxElement) -> bool {
     fn node_newline(element: &SyntaxElement) -> Option<bool> {
         let first_el = element.as_node()?.descendants().filter(|e| e.kind() != NODE_BIN_OP).nth(0);


Test patch:
diff --git a/test_data/attr_fn.bad.nix b/test_data/attr_fn.bad.nix
index 219dc85..d8af1ae 100644
--- a/test_data/attr_fn.bad.nix
+++ b/test_data/attr_fn.bad.nix
@@ -1,5 +1,7 @@
 {
   f = { x
   , y
-  }: body;
+      }: body;
+
+  testAllTrue = expr: {inherit expr;expected=map (x: true) expr; };
 }
diff --git a/test_data/attr_fn.good.nix b/test_data/attr_fn.good.nix
index fc8863f..dfa303b 100644
--- a/test_data/attr_fn.good.nix
+++ b/test_data/attr_fn.good.nix
@@ -3,4 +3,6 @@
     { x
     , y
     }: body;
+
+  testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };
 }
diff --git a/test_data/function_call.bad.nix b/test_data/function_call.bad.nix
new file mode 100644
index 0000000..199a3ee
--- /dev/null
+++ b/test_data/function_call.bad.nix
@@ -0,0 +1,37 @@
+{
+  fooA = fun.f a b (with c; d);
+  fooB = fun (with a; b) c d;
+  fooC = fun a (with b; c) d;
+  fooD = fun a b (with c; {
+    inherit d;
+  });
+  fooF = fun (with a; b)
+    c d;
+  fooG = fun (with a; b) c
+    d;
+  fooH = fun a (with b; c)
+    d;
+  fooI = fun a
+    (with b; c) d;
+  fooJ = fun a
+    (with b; c) d.a;
+  fooK = fun a;
+  fooL = fun
+    a;
+
+
+  barA = fun a b {inherit d;};
+  barB = fun {inherit b;} c d;
+  barC = fun a {inherit c;} d;
+  barD = fun a b {
+    inherit d;
+  };
+  barF = fun {inherit b;} c
+    d;
+  barG = fun {inherit b;} c
+    d;
+  barH = fun a {inherit c;}
+    d;
+  barI = fun a
+    {inherit c;} d;
+}
diff --git a/test_data/function_call.good.nix b/test_data/function_call.good.nix
new file mode 100644
index 0000000..719371b
--- /dev/null
+++ b/test_data/function_call.good.nix
@@ -0,0 +1,65 @@
+{
+  fooA = fun.f a b (with c; d);
+  fooB = fun (with a; b) c d;
+  fooC = fun a (with b; c) d;
+  fooD = fun a b (with c; {
+    inherit d;
+  });
+  fooF =
+    fun
+      (with a; b)
+      c
+      d;
+  fooG =
+    fun
+      (with a; b)
+      c
+      d;
+  fooH =
+    fun
+      a
+      (with b; c)
+      d;
+  fooI =
+    fun
+      a
+      (with b; c)
+      d;
+  fooJ =
+    fun
+      a
+      (with b; c)
+      d.a;
+  fooK = fun a;
+  fooL =
+    fun
+      a;
+
+
+  barA = fun a b { inherit d; };
+  barB = fun { inherit b; } c d;
+  barC = fun a { inherit c; } d;
+  barD = fun a b {
+    inherit d;
+  };
+  barF =
+    fun
+      { inherit b; }
+      c
+      d;
+  barG =
+    fun
+      { inherit b; }
+      c
+      d;
+  barH =
+    fun
+      a
+      { inherit c; }
+      d;
+  barI =
+    fun
+      a
+      { inherit c; }
+      d;
+}
diff --git a/test_data/indent_paren.good.nix b/test_data/indent_paren.good.nix
index 9f4466a..fae98c0 100644
--- a/test_data/indent_paren.good.nix
+++ b/test_data/indent_paren.good.nix
@@ -6,19 +6,21 @@
       )
     )
   );
-  xxx = listToAttrs
-    (
-      concatMap
-        (name:
-          let a = 4; in 5
-        )
-    );
+  xxx = listToAttrs (
+    concatMap (name:
+      let a = 4; in 5
+    )
+  );
   foldAttrs = op: nul: list_of_attrs:
     fold
       (n: a:
         fold
           (name: o:
             o // { ${name} = op n.${name} (a.${name} or nul); }
-          ) a (attrNames n)
-      ) { } list_of_attrs;
+          )
+          a
+          (attrNames n)
+      )
+      { }
+      list_of_attrs;
 }
diff --git a/test_data/indent_string_literal_interpolation.bad.nix b/test_data/indent_string_literal_interpolation.bad.nix
index b115c1b..ec61f44 100644
--- a/test_data/indent_string_literal_interpolation.bad.nix
+++ b/test_data/indent_string_literal_interpolation.bad.nix
@@ -66,4 +66,11 @@ nested_antiquotation = mkBefore
   setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
   ''}
   '';
+
+singleAsciiDoc = value: ''
+Example::
+${
+builtins.toJSON value.example
+}
+'';
 }
diff --git a/test_data/indent_string_literal_interpolation.good.nix b/test_data/indent_string_literal_interpolation.good.nix
index e6c5d05..1534054 100644
--- a/test_data/indent_string_literal_interpolation.good.nix
+++ b/test_data/indent_string_literal_interpolation.good.nix
@@ -57,13 +57,21 @@
     }
   '';
 
-  nested_antiquotation = mkBefore
-    ''
-      ${optionalString cfg.earlySetup ''
+  nested_antiquotation =
+    mkBefore
+      ''
         ${optionalString cfg.earlySetup ''
+          ${optionalString cfg.earlySetup ''
+            setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
+          ''}
           setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
         ''}
-        setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
-      ''}
-    '';
+      '';
+
+  singleAsciiDoc = value: ''
+    Example::
+    ${
+      builtins.toJSON value.example
+    }
+  '';
 }
diff --git a/test_data/indented_lambda.bad.nix b/test_data/indented_lambda.bad.nix
index e7232b6..ef7790c 100644
--- a/test_data/indented_lambda.bad.nix
+++ b/test_data/indented_lambda.bad.nix
@@ -1,3 +1,16 @@
+{
+  # how to string-format the option name;
+  # by default one character is a short option (`-`),
+  # more than one characters a long option (`--`).
+  mkOptionName ?
+  k: if builtins.stringLength k == 1
+    then "-${k}"
+    else "--${k}"
+, mkOption ? k: v:
+  if v == null
+  then [ ]
+  else [ (mkOptionName k) (lib.generators.mkValueStringDefault { } v) ]
+}:
 {  toINI = {
   # parameter comment
   mkSectionName ? (name: libStr.escape [ "[" "]" ] name)
diff --git a/test_data/indented_lambda.good.nix b/test_data/indented_lambda.good.nix
index 48ff347..be1ed36 100644
--- a/test_data/indented_lambda.good.nix
+++ b/test_data/indented_lambda.good.nix
@@ -1,3 +1,16 @@
+{
+  # how to string-format the option name;
+  # by default one character is a short option (`-`),
+  # more than one characters a long option (`--`).
+  mkOptionName ? k:
+    if builtins.stringLength k == 1
+    then "-${k}"
+    else "--${k}"
+, mkOption ? k: v:
+    if v == null
+    then [ ]
+    else [ (mkOptionName k) (lib.generators.mkValueStringDefault { } v) ]
+}:
 {
   toINI =
     {
diff --git a/test_data/issue-126.good.nix b/test_data/issue-126.good.nix
index c22746a..939bc31 100644
--- a/test_data/issue-126.good.nix
+++ b/test_data/issue-126.good.nix
@@ -43,19 +43,17 @@
     let
       foo = "0.95";
     in
-    buildLinux
-      ({
-        version = "${foo}-mptcp_v1.0.0";
-        inherit bar;
-      } // args);
+    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;
-      });
+    buildLinux ({
+      version = "${foo}-mptcp_v1.0.0";
+      inherit bar;
+    });
 }
diff --git a/test_data/issue-178.good.nix b/test_data/issue-178.good.nix
index b06aef9..d190f9d 100644
--- a/test_data/issue-178.good.nix
+++ b/test_data/issue-178.good.nix
@@ -5,9 +5,11 @@
     src = sources.nixpkgs-fmt;
   }).nixpkgs-fmt.build;
 
-  testB = buildLinux
-    (map
+  testB = buildLinux (
+    map
       ({ name }:
         { inherit name; }
-      ) cfg.feeds);
+      )
+      cfg.feeds
+  );
 }
diff --git a/test_data/issue-181.good.nix b/test_data/issue-181.good.nix
index 8df54f2..6cdfafc 100644
--- a/test_data/issue-181.good.nix
+++ b/test_data/issue-181.good.nix
@@ -3,9 +3,8 @@ python3.pkgs.buildPythonApplication {
   propagatedBuildInputs = (with python3.pkgs; [
     pkg1
   ]);
-  testB = forAllSystems
-    (
-      system:
-      self.apps.${system}.nixpkgs-fmt
-    );
+  testB = forAllSystems (
+    system:
+    self.apps.${system}.nixpkgs-fmt
+  );
 }
diff --git a/test_data/nested_if_else.good.nix b/test_data/nested_if_else.good.nix
index de6c6ff..e7873c0 100644
--- a/test_data/nested_if_else.good.nix
+++ b/test_data/nested_if_else.good.nix
@@ -3,12 +3,14 @@
     if (n == 0) then fn v
     else if isList v then map (modify (n - 1) fn) v
     else if isAttrs v then mapAttrs
-      (const (modify (n - 1) fn)) v
+      (const (modify (n - 1) fn))
+      v
     else v;
   modifyB = n: fn: v:
     if (n == 0) then fn v
     else if isList v then map (modify (n - 1) fn) v
     else if isAttrs v then mapAttrs
-      (const (modify (n - 1) fn)) v
+      (const (modify (n - 1) fn))
+      v
     else v;
 }
diff --git a/test_data/nixpkgs_repository/doc_shellnix.good.nix b/test_data/nixpkgs_repository/doc_shellnix.good.nix
index a839a9f..542b6d5 100644
--- a/test_data/nixpkgs_repository/doc_shellnix.good.nix
+++ b/test_data/nixpkgs_repository/doc_shellnix.good.nix
@@ -1,6 +1,5 @@
 { pkgs ? import ../. { } }:
-(import ./default.nix { }).overrideAttrs
-  (x: {
-    buildInputs = x.buildInputs ++ [ pkgs.xmloscopy pkgs.ruby ];
+(import ./default.nix { }).overrideAttrs (x: {
+  buildInputs = x.buildInputs ++ [ pkgs.xmloscopy pkgs.ruby ];
 
-  })
+})
diff --git a/test_data/nixpkgs_repository/flakenix.good.nix b/test_data/nixpkgs_repository/flakenix.good.nix
index 2230ad7..68ed7b2 100644
--- a/test_data/nixpkgs_repository/flakenix.good.nix
+++ b/test_data/nixpkgs_repository/flakenix.good.nix
@@ -1,16 +1,15 @@
 {
   lib = lib // {
     nixosSystem = { modules, ... } @ args:
-      import ./nixos/lib/eval-config.nix
-        (args // {
-          modules = modules ++
-            [
-              {
-                system.nixos.versionSuffix =
-                  ".${lib.substring 0 8 self.lastModified}.${self.shortRev or "dirty"}";
-                system.nixos.revision = lib.mkIf (self ? rev) self.rev;
-              }
-            ];
-        });
+      import ./nixos/lib/eval-config.nix (args // {
+        modules = modules ++
+          [
+            {
+              system.nixos.versionSuffix =
+                ".${lib.substring 0 8 self.lastModified}.${self.shortRev or "dirty"}";
+              system.nixos.revision = lib.mkIf (self ? rev) self.rev;
+            }
+          ];
+      });
   };
 }
diff --git a/test_data/nixpkgs_repository/lib_attrset.good.nix b/test_data/nixpkgs_repository/lib_attrset.good.nix
index 06b65a0..759fc11 100644
--- a/test_data/nixpkgs_repository/lib_attrset.good.nix
+++ b/test_data/nixpkgs_repository/lib_attrset.good.nix
@@ -1,15 +1,17 @@
 {
   matchAttrs = pattern: attrs: assert isAttrs pattern;
-    fold and true
-      (attrValues
-        (zipAttrsWithNames (attrNames pattern)
-          (n: values:
-            let
-              pat = head values;
-              val = head (tail values);
-            in
-            if length values == 1 then false
-            else if isAttrs pat then isAttrs val && matchAttrs pat val
-            else pat == val
-          ) [ pattern attrs ]));
+    fold and true (attrValues (
+      zipAttrsWithNames
+        (attrNames pattern)
+        (n: values:
+          let
+            pat = head values;
+            val = head (tail values);
+          in
+          if length values == 1 then false
+          else if isAttrs pat then isAttrs val && matchAttrs pat val
+          else pat == val
+        )
+        [ pattern attrs ]
+    ));
 }
diff --git a/test_data/nixpkgs_repository/lib_customisation.good.nix b/test_data/nixpkgs_repository/lib_customisation.good.nix
index 881c5ef..8be314f 100644
--- a/test_data/nixpkgs_repository/lib_customisation.good.nix
+++ b/test_data/nixpkgs_repository/lib_customisation.good.nix
@@ -3,22 +3,21 @@
     let
       newDrv = derivation (drv.drvAttrs // (f drv));
     in
-    lib.flip (extendDerivation true) newDrv
+    lib.flip (extendDerivation true) newDrv (
+      {
+        meta = drv.meta or { };
+        passthru = if drv ? passthru then drv.passthru else { };
+      }
+      //
+      (drv.passthru or { })
+      //
       (
-        {
-          meta = drv.meta or { };
-          passthru = if drv ? passthru then drv.passthru else { };
+        if (drv ? crossDrv && drv ? nativeDrv)
+        then {
+          crossDrv = overrideDerivation drv.crossDrv f;
+          nativeDrv = overrideDerivation drv.nativeDrv f;
         }
-        //
-        (drv.passthru or { })
-        //
-        (
-          if (drv ? crossDrv && drv ? nativeDrv)
-          then {
-            crossDrv = overrideDerivation drv.crossDrv f;
-            nativeDrv = overrideDerivation drv.nativeDrv f;
-          }
-          else { }
-        )
-      );
+        else { }
+      )
+    );
 }
diff --git a/test_data/nixpkgs_repository/lib_deprecated.bad.nix b/test_data/nixpkgs_repository/lib_deprecated.bad.nix
new file mode 100644
index 0000000..d2c5c11
--- /dev/null
+++ b/test_data/nixpkgs_repository/lib_deprecated.bad.nix
@@ -0,0 +1,18 @@
+{
+  innerClosePropagation = acc: xs:
+    if xs == []
+    then acc
+    else let y  = head xs;
+             ys = tail xs;
+         in if ! isAttrs y
+            then innerClosePropagation acc ys
+            else let acc' = [y] ++ acc;
+                 in innerClosePropagation
+                      acc'
+                      (uniqList { inputList = (maybeAttrNullable "propagatedBuildInputs" [] y)
+                                           ++ (maybeAttrNullable "propagatedNativeBuildInputs" [] y)
+                                           ++ ys;
+                                  acc = acc';
+                                }
+                      );
+}
diff --git a/test_data/nixpkgs_repository/lib_deprecated.good.nix b/test_data/nixpkgs_repository/lib_deprecated.good.nix
new file mode 100644
index 0000000..c44161d
--- /dev/null
+++ b/test_data/nixpkgs_repository/lib_deprecated.good.nix
@@ -0,0 +1,23 @@
+{
+  innerClosePropagation = acc: xs:
+    if xs == [ ]
+    then acc
+    else
+      let
+        y = head xs;
+        ys = tail xs;
+      in
+      if ! isAttrs y
+      then innerClosePropagation acc ys
+      else
+        let acc' = [ y ] ++ acc;
+        in
+        innerClosePropagation
+          acc'
+          (uniqList {
+            inputList = (maybeAttrNullable "propagatedBuildInputs" [ ] y)
+              ++ (maybeAttrNullable "propagatedNativeBuildInputs" [ ] y)
+              ++ ys;
+            acc = acc';
+          });
+}
diff --git a/test_data/nixpkgs_repository/lib_generators.good.nix b/test_data/nixpkgs_repository/lib_generators.good.nix
index 6e03ab7..66ab420 100644
--- a/test_data/nixpkgs_repository/lib_generators.good.nix
+++ b/test_data/nixpkgs_repository/lib_generators.good.nix
@@ -7,7 +7,8 @@
     let
       # map function to string for each key val
       mapAttrsToStringsSep = sep: mapFn: attrs:
-        libStr.concatStringsSep sep
+        libStr.concatStringsSep
+          sep
           (libAttr.mapAttrsToList mapFn attrs);
       mkSection = sectName: sectValues: ''
         [${mkSectionName sectName}]
diff --git a/test_data/nixpkgs_repository/lib_strings.bad.nix b/test_data/nixpkgs_repository/lib_strings.bad.nix
new file mode 100644
index 0000000..616efda
--- /dev/null
+++ b/test_data/nixpkgs_repository/lib_strings.bad.nix
@@ -0,0 +1,13 @@
+{
+    fixedWidthString = width: filler: str:
+    let
+      strw = lib.stringLength str;
+      reqWidth = width - (lib.stringLength filler);
+    in
+    assert lib.assertMsg
+      (strw <= width)
+      "fixedWidthString: requested string length (${
+          toString width}) must not be shorter than actual length (${
+          toString strw})";
+      if strw == width then str else filler + fixedWidthString reqWidth filler str;
+}
diff --git a/test_data/nixpkgs_repository/lib_strings.good.nix b/test_data/nixpkgs_repository/lib_strings.good.nix
new file mode 100644
index 0000000..9f91ea4
--- /dev/null
+++ b/test_data/nixpkgs_repository/lib_strings.good.nix
@@ -0,0 +1,13 @@
+{
+  fixedWidthString = width: filler: str:
+    let
+      strw = lib.stringLength str;
+      reqWidth = width - (lib.stringLength filler);
+    in
+    assert lib.assertMsg
+      (strw <= width)
+      "fixedWidthString: requested string length (${
+        toString width}) must not be shorter than actual length (${
+        toString strw})";
+    if strw == width then str else filler + fixedWidthString reqWidth filler str;
+}
diff --git a/test_data/nixpkgs_repository/maintainers_scripts_update.good.nix b/test_data/nixpkgs_repository/maintainers_scripts_update.good.nix
index dbbf052..f17ff0f 100644
--- a/test_data/nixpkgs_repository/maintainers_scripts_update.good.nix
+++ b/test_data/nixpkgs_repository/maintainers_scripts_update.good.nix
@@ -29,7 +29,8 @@
     if attrSet == null then
       builtins.throw "Attribute path `${path}` does not exists."
     else
-      packagesWith (name: pkg: builtins.hasAttr "updateScript" pkg)
+      packagesWith
+        (name: pkg: builtins.hasAttr "updateScript" pkg)
         (name: pkg: pkg)
         attrSet;
 }
diff --git a/test_data/nixpkgs_repository/nixos_lib_testing_python.good.nix b/test_data/nixpkgs_repository/nixos_lib_testing_python.good.nix
index 5b0b36f..92b84e0 100644
--- a/test_data/nixpkgs_repository/nixos_lib_testing_python.good.nix
+++ b/test_data/nixpkgs_repository/nixos_lib_testing_python.good.nix
@@ -1,7 +1,8 @@
 {
   driver = let warn = if skipLint then lib.warn "Linting is disabled!" else lib.id; in
-    warn
-      (runCommand testDriverName
+    warn (
+      runCommand
+        testDriverName
         {
           buildInputs = [ makeWrapper ];
           testScript = testScript';
@@ -30,5 +31,5 @@
             --set VLANS '${toString vlans}' \
             ${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"}
         ''
-      ); # "
+    ); # "
 }