Repository: domluna/JuliaFormatter.jl
Pull request #217 (2020-04-08 02:10:08)

Implements YASGuide style
cleaned up #214 


Patch:
diff --git a/README.md b/README.md
index 25e9e3c6..6f1ac27a 100644
--- a/README.md
+++ b/README.md
@@ -33,104 +33,9 @@ julia> format_file("foo.jl")
 julia> format_text(str)
 ```
 
-[Use With Github Actions](https://github.com/julia-actions/julia-format)
-
-## Usage
-
-`JuliaFormatter` exports `format_text`, `format_file` and `format`:
-
-```julia
-format_text(
-    text::AbstractString;
-    indent = 4,
-    margin = 92,
-    always_for_in = false,
-    whitespace_typedefs::Bool = false,
-    whitespace_ops_in_indices::Bool = false,
-    remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
-)
-
-format_file(
-    file::AbstractString;
-    overwrite = true,
-    verbose = false,
-    indent = 4,
-    margin = 92,
-    always_for_in = false,
-    whitespace_typedefs::Bool = false,
-    whitespace_ops_in_indices::Bool = false,
-    remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
-)
-
-format(
-    paths...;
-    overwrite = true,
-    verbose = false,
-    indent = 4,
-    margin = 92,
-    always_for_in = false,
-    whitespace_typedefs::Bool = false,
-    whitespace_ops_in_indices::Bool = false,
-    remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
-)
-```
-
-The `text` argument to `format_text` is a string containing the code to be formatted;
-the formatted code is retuned as a new string.
-The `file` argument to `format_file` is the path of a file to be formatted.
-The `format` function is either called with a single string or a collection of strings,
-in both cases representing filesystem paths. If the path is to a `.jl` file, it formats it;
-if it's a directory, it recurses into it, looking for `.jl` files to format.
-
-### File Options
-
-If `overwrite` is `true`, the file will be reformatted in place, overwriting
-the existing file; if it is `false`, the formatted version of `foo.jl` will
-be written to `foo_fmt.jl` instead.
-
-If `verbose` is `true` details related to formatting the file will be printed
-to `stdout`.
-
-### Formatting Options
-
-`indent` - the number of spaces used for an indentation.
-
-`margin` - the maximum length of a line. Code exceeding this margin will be formatted
-across multiple lines.
-
-If `always_for_in` is true `=` is always replaced with `in` if part of a
-`for` loop condition.  For example, `for i = 1:10` will be transformed
-to `for i in 1:10`.
-
-If `whitespace_typedefs` is true, whitespace is added for type definitions.
-Make this `true` if you prefer `Union{A <: B, C}` to `Union{A<:B,C}`.
+Check out the docs for further description of the formatter and its options.
 
-If `whitespace_ops_in_indices` is true, whitespace is added for binary operations in indices.
-Make this `true` if you prefer `arr[a + b]` to `arr[a+b]`.
-Additionally, if there's a colon `:` involved, parenthesis will be added to the LHS and RHS.
-
-Example: `arr[(i1 + i2):(i3 + i4)]` instead of `arr[i1+i2:i3+i4]`.
-
-If `remove_extra_newlines` is true superflous newlines will be removed. For example:
-
-```julia
-a = 1
-
-
-
-b = 2
-```
-
-is rewritten as
-
-```julia
-a = 1
-
-b = 2
-```
+[Use With Github Actions](https://github.com/julia-actions/julia-format)
 
 ### Editor Plugins
 
diff --git a/docs/src/index.md b/docs/src/index.md
index 5468a5ad..38c7ff93 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -36,71 +36,81 @@ format_text(
     text::AbstractString;
     indent = 4,
     margin = 92,
+    style::AbstractStyle = DefaultStyle(),
     always_for_in = false,
     whitespace_typedefs::Bool = false,
     whitespace_ops_in_indices::Bool = false,
     remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
+    import_to_using::Bool = false,
+    pipe_to_function_call::Bool = false,
+    short_to_long_function_def::Bool = false,
+    always_use_return::Bool = false,
 )
 
 format_file(
     file::AbstractString;
     overwrite = true,
     verbose = false,
-    indent = 4,
-    margin = 92,
-    always_for_in = false,
-    whitespace_typedefs::Bool = false,
-    whitespace_ops_in_indices::Bool = false,
-    remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
+    format_options...,
 )
 
 format(
     paths...;
-    overwrite = true,
-    verbose = false,
-    indent = 4,
-    margin = 92,
-    always_for_in = false,
-    whitespace_typedefs::Bool = false,
-    whitespace_ops_in_indices::Bool = false,
-    remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
+    options...,
 )
 ```
 
 The `text` argument to `format_text` is a string containing the code to be formatted; the formatted code is retuned as a new string. The `file` argument to `format_file` is the path of a file to be formatted. The `format` function is either called with a singe string to format if it is a `.jl` file or to recuse into looking for `.jl` files if it is a directory. It can also be called with a collection of such paths to iterate over.
 
+`format` calls `format_file` which in turn calls `format_text`.
+
 ### File Options
 
-If `overwrite` is `true` the file will be reformatted in place, overwriting
-the existing file; if it is `false`, the formatted version of `foo.jl` will
-be written to `foo_fmt.jl` instead.
+#### `overwrite`
+
+The file will be reformatted in place, overwriting the existing file.
+If it is `false`, the formatted version of `foo.jl` will be written to
+`foo_fmt.jl` instead.
 
-If `verbose` is `true` details related to formatting the file will be printed
-to `stdout`.
+#### `verbose`
+
+Details related to formatting the file will be printed to `stdout`.
 
 ### Formatting Options
 
-`indent` - the number of spaces used for an indentation.
+Formats a Julia source passed in as a string, returning the formatted
+code as another string.
+
+#### `indent`
+
+The number of spaces used for an indentation.
+
+#### `margin`
 
-`margin` - the maximum length of a line. Code exceeding this margin will be formatted
-across multiple lines.
+The maximum length of a line. Code exceeding this margin will
+be formatted across multiple lines.
 
-If `always_for_in` is true `=` is always replaced with `in` if part of a
-`for` loop condition.  For example, `for i = 1:10` will be transformed
-to `for i in 1:10`.
+#### `always_for_in`
 
-If `whitespace_typedefs` is true, whitespace is added for type definitions.
-Make this `true` if you prefer `Union{A <: B, C}` to `Union{A<:B,C}`.
+If true `=` is always replaced with `in` if part of a `for` loop condition.
+For example, `for i = 1:10` will be transformed to `for i in 1:10`.
+
+#### `whitespace_typedefs`
+
+If true, whitespace is added for type definitions.  Make this `true`
+if you prefer `Union{A <: B, C}` to `Union{A<:B,C}`.
+
+#### `whitespace_ops_in_indices`
+
+If true, whitespace is added for binary operations in indices. Make this
+`true` if you prefer `arr[a + b]` to `arr[a+b]`. Additionally, if there's
+a colon `:` involved, parenthesis will be added to the LHS and RHS.
 
-If `whitespace_ops_in_indices` is true, whitespace is added for binary operations
-in indices. Make this `true` if you prefer `arr[a + b]` to `arr[a+b]`. Additionally,
-if there's a colon `:` involved, parenthesis will be added to the LHS and RHS.
 Example: `arr[(i1 + i2):(i3 + i4)]` instead of `arr[i1+i2:i3+i4]`.
 
-If `remove_extra_newlines` is true superflous newlines will be removed. For example:
+#### `remove_extra_newlines`
+
+If true superflous newlines will be removed. For example:
 
 ```julia
 a = 1
@@ -117,3 +127,67 @@ a = 1
 
 b = 2
 ```
+
+#### `import_to_using`
+
+If true `import` expressions are rewritten to `using` expressions
+in the following cases:
+
+```julia
+import A
+
+import A, B, C
+```
+
+is rewritten to:
+
+```julia
+using A: A
+
+using A: A
+using B: B
+using C: C
+```
+
+#### `pipe_to_function_call`
+
+If true `f |> x` is rewritten to `f(x)`.
+
+#### `short_to_long_function_def`
+
+Transforms a _short_ function definition
+
+```julia
+f(arg1, arg2) = body
+```
+
+to a _long_ function definition
+
+```julia
+function f(arg2, arg2)
+    body
+end
+```
+
+#### `always_use_return`
+
+If true `return` will be prepended to the last expression where
+applicable in function definitions, macro definitions, and do blocks.
+
+Example:
+
+```julia
+function foo()
+    expr1
+    expr2
+end
+```
+
+to
+
+```julia
+function foo()
+    expr1
+    return expr2
+end
+```
diff --git a/docs/src/transforms.md b/docs/src/transforms.md
index d819868c..96768d9e 100644
--- a/docs/src/transforms.md
+++ b/docs/src/transforms.md
@@ -80,3 +80,28 @@ foo(x::T) where {T} = ...
 ```
 
 See [this issue](https://github.com/domluna/JuliaFormatter.jl/issues/53) for more details.
+
+## Annotate unannotated type fields with `Any`
+
+```julia
+struct Foo
+    field
+end
+
+->
+
+struct Foo
+    field::Any
+end
+```
+
+## Move `@` in macro calls to the final identifier
+
+
+```julia
+@Module.macro
+
+->
+
+Module.@macro
+```
diff --git a/docs/src/yas_style.md b/docs/src/yas_style.md
new file mode 100644
index 00000000..49d3fdb6
--- /dev/null
+++ b/docs/src/yas_style.md
@@ -0,0 +1,71 @@
+# YAS Style
+
+Formatting style based on [YASGuide](https://github.com/jrevels/YASGuide) and https://github.com/domluna/JuliaFormatter.jl/issues/198.
+
+Recommended options for confirming with the above guide are:
+
+- `always_for_in` = true
+- `whitespace_ops_in_indices` = true
+- `whitespace_typedefs` = false
+- `remove_extra_newlines` = true
+- `import_to_using` = true
+- `pipe_to_function_call` = true
+- `short_to_long_function_def` = true
+- `always_use_return` = true
+
+## Usage
+
+```julia
+format("file.jl", style=YASStyle(), ...)
+```
+
+## Differences from `DefaultStyle`
+
+There are three main differences between `YASStyle` and `DefaultStyle`. They are based
+on alignment and line break behaviors.
+
+1. Arguments are aligned to just after the start of the *opener* `[, {, (, etc`.
+
+```julia
+function_call(arg1,
+              arg2)
+```
+
+2. As you can see from the above the *closer* sticks to the final argument.
+
+3. Nesting (line breaks) only occur when the margin of the next argument exceeds
+the maximim limit.
+
+```julia
+function_call(arg1, arg2,
+              arg3)
+```
+
+`arg3` exceeded the margin limit and so it was placed on the following line.
+
+
+### Nesting `=`
+
+Unlike `DefaultStyle`, assignment operations `=` are not nested. That
+is, the following
+
+```julia
+my_function(arg1, arg2) = arg1 * arg2
+```
+
+Is not nested to
+
+
+```julia
+my_function(arg1, arg2) =
+    arg1 * arg2
+```
+
+It is highly recommended setting `short_to_long_function_def` to `true`. This option
+transforms the above to a long function definition if it exceeds the maximum margin.
+
+```julia
+function my_function(arg1, arg2)
+    arg1 * arg2
+end
+```
diff --git a/src/JuliaFormatter.jl b/src/JuliaFormatter.jl
index 55eca16a..12ec7d6a 100644
--- a/src/JuliaFormatter.jl
+++ b/src/JuliaFormatter.jl
@@ -175,6 +175,10 @@ Base.@kwdef struct Options
     whitespace_typedefs::Bool = false
     whitespace_ops_in_indices::Bool = false
     remove_extra_newlines::Bool = false
+    import_to_using::Bool = false
+    pipe_to_function_call::Bool = false
+    short_to_long_function_def::Bool = false
+    always_use_return::Bool = false
 end
 
 mutable struct State
@@ -184,8 +188,9 @@ mutable struct State
     offset::Int
     line_offset::Int
     margin::Int
-    # If true will output the formatted text
-    # otherwise will output the current text
+
+    # If true, output is formatted text otherwise
+    # it's source text
     on::Bool
     opts::Options
 end
@@ -218,6 +223,7 @@ DefaultStyle() = DefaultStyle(nothing)
 @inline getstyle(s::DefaultStyle) = s.innerstyle === nothing ? s : s.innerstyle
 
 include("fst.jl")
+include("passes.jl")
 include("pretty.jl")
 include("nest.jl")
 include("print.jl")
@@ -228,36 +234,52 @@ include("styles/yas.jl")
         text::AbstractString;
         indent::Int = 4,
         margin::Int = 92,
+        style::AbstractStyle = DefaultStyle(),
         always_for_in::Bool = false,
         whitespace_typedefs::Bool = false,
         whitespace_ops_in_indices::Bool = false,
         remove_extra_newlines::Bool = false,
-        style::AbstractStyle = DefaultStyle(),
+        import_to_using::Bool = false,
+        pipe_to_function_call::Bool = false,
+        short_to_long_function_def::Bool = false,
+        always_use_return::Bool = false,
     )::String
 
 Formats a Julia source passed in as a string, returning the formatted
 code as another string.
 
-### Formatting Options
+## Formatting Options
+
+### `indent`
+
+The number of spaces used for an indentation.
+
+### `margin`
+
+The maximum length of a line. Code exceeding this margin will
+be formatted across multiple lines.
+
+### `always_for_in`
 
-`indent` - the number of spaces used for an indentation.
+If true `=` is always replaced with `in` if part of a `for` loop condition.
+For example, `for i = 1:10` will be transformed to `for i in 1:10`.
 
-`margin` - the maximum length of a line. Code exceeding this margin will be formatted
-across multiple lines.
+### `whitespace_typedefs`
 
-If `always_for_in` is true `=` is always replaced with `in` if part of a
-`for` loop condition.  For example, `for i = 1:10` will be transformed
-to `for i in 1:10`.
+If true, whitespace is added for type definitions. Make this `true`
+if you prefer `Union{A <: B, C}` to `Union{A<:B,C}`.
 
-If `whitespace_typedefs` is true, whitespace is added for type definitions.
-Make this `true` if you prefer `Union{A <: B, C}` to `Union{A<:B,C}`.
+### `whitespace_ops_in_indices`
 
-If `whitespace_ops_in_indices` is true, whitespace is added for binary operations
-in indices. Make this `true` if you prefer `arr[a + b]` to `arr[a+b]`. Additionally, if there's a colon `:` involved, parenthesis will be added to the LHS and RHS.
+If true, whitespace is added for binary operations in indices. Make this
+`true` if you prefer `arr[a + b]` to `arr[a+b]`. Additionally, if there's
+a colon `:` involved, parenthesis will be added to the LHS and RHS.
 
 Example: `arr[(i1 + i2):(i3 + i4)]` instead of `arr[i1+i2:i3+i4]`.
 
-If `remove_extra_newlines` is true superflous newlines will be removed. For example:
+### `remove_extra_newlines`
+
+If true superflous newlines will be removed. For example:
 
 ```julia
 a = 1
@@ -274,16 +296,84 @@ a = 1
 
 b = 2
 ```
+
+### `import_to_using`
+
+If true `import` expressions are rewritten to `using` expressions
+in the following cases:
+
+```julia
+import A
+
+import A, B, C
+```
+
+is rewritten to:
+
+```julia
+using A: A
+
+using A: A
+using B: B
+using C: C
+```
+
+### `pipe_to_function_call`
+
+If true `f |> x` is rewritten to `f(x)`.
+
+### `short_to_long_function_def`
+
+Transforms a _short_ function definition
+
+```julia
+f(arg1, arg2) = body
+```
+
+to a _long_ function definition
+
+```julia
+function f(arg2, arg2)
+    body
+end
+```
+
+### `always_use_return`
+
+If true `return` will be prepended to the last expression where
+applicable in function definitions, macro definitions, and do blocks.
+
+Example:
+
+```julia
+function foo()
+    expr1
+    expr2
+end
+```
+
+to
+
+```julia
+function foo()
+    expr1
+    return expr2
+end
+```
 """
 function format_text(
     text::AbstractString;
     indent::Int = 4,
     margin::Int = 92,
+    style::AbstractStyle = DefaultStyle(),
     always_for_in::Bool = false,
     whitespace_typedefs::Bool = false,
     whitespace_ops_in_indices::Bool = false,
     remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
+    import_to_using::Bool = false,
+    pipe_to_function_call::Bool = false,
+    short_to_long_function_def::Bool = false,
+    always_use_return::Bool = false,
 )
     isempty(text) && return text
 
@@ -298,10 +388,17 @@ function format_text(
         whitespace_typedefs = whitespace_typedefs,
         whitespace_ops_in_indices = whitespace_ops_in_indices,
         remove_extra_newlines = remove_extra_newlines,
+        import_to_using = import_to_using,
+        pipe_to_function_call = pipe_to_function_call,
+        short_to_long_function_def = short_to_long_function_def,
+        always_use_return = always_use_return,
     )
     s = State(Document(text), indent, margin, opts)
     t = pretty(style, x, s)
     hascomment(s.doc, t.endline) && (add_node!(t, InlineComment(t.endline), s))
+
+    opts.pipe_to_function_call && pipe_to_function_call_pass!(t)
+
     flatten_fst!(t)
     nest!(style, t, s)
 
@@ -334,13 +431,7 @@ end
         filename::AbstractString;
         overwrite::Bool = true,
         verbose::Bool = false,
-        indent::Integer = 4,
-        margin::Integer = 92,
-        always_for_in::Bool = false,
-        whitespace_typedefs::Bool = false,
-        whitespace_ops_in_indices::Bool = false,
-        remove_extra_newlines::Bool = false,
-        style::AbstractStyle = DefaultStyle(),
+        format_options...,
     )
 
 Formats the contents of `filename` assuming it's a Julia source file.
@@ -362,13 +453,7 @@ function format_file(
     filename::AbstractString;
     overwrite::Bool = true,
     verbose::Bool = false,
-    indent::Integer = 4,
-    margin::Integer = 92,
-    always_for_in::Bool = false,
-    whitespace_typedefs::Bool = false,
-    whitespace_ops_in_indices::Bool = false,
-    remove_extra_newlines::Bool = false,
-    style::AbstractStyle = DefaultStyle(),
+    format_options...,
 )
     path, ext = splitext(filename)
     shebang_pattern = r"^#!\s*/.*\bjulia[0-9.-]*\b"
@@ -377,16 +462,7 @@ function format_file(
     end
     verbose && println("Formatting $filename")
     str = String(read(filename))
-    str = format_text(
-        str,
-        indent = indent,
-        margin = margin,
-        always_for_in = always_for_in,
-        whitespace_typedefs = whitespace_typedefs,
-        whitespace_ops_in_indices = whitespace_ops_in_indices,
-        remove_extra_newlines = remove_extra_newlines,
-        style = style,
-    )
+    str = format_text(str; format_options...)
     str = replace(str, r"\n*$" => "\n")
     overwrite ? write(filename, str) : write(path * "_fmt" * ext, str)
     nothing
@@ -435,17 +511,13 @@ end
 """
     format(
         paths; # a path or collection of paths
-        indent::Integer = 4,
-        margin::Integer = 92,
-        overwrite::Bool = true,
-        verbose::Bool = false,
-        always_for_in::Bool = false,
-        remove_extra_newlines = false,
-        style = DefaultStyle(),
+        options...,
     )
 
 Recursively descend into files and directories, formatting any `.jl`
 files by calling `format_file` on them.
+
+See `format_file` and `format_text` for a description of the options.
 """
 function format(paths; options...)
     for path in paths
diff --git a/src/fst.jl b/src/fst.jl
index 67727115..479c9e69 100644
--- a/src/fst.jl
+++ b/src/fst.jl
@@ -1,4 +1,6 @@
-# FNode are extra nodes used for formatting which don't have a CSTParser equivalent.
+"""
+`FNode` is a node used for formatting which does not have a `CSTParser` equivalent.
+"""
 @enum(
     FNode,
 
@@ -11,19 +13,40 @@
     INLINECOMMENT,
     TRAILINGCOMMA,
     TRAILINGSEMICOLON,
+
+    # no equivalent in CSTParser
+    MacroBlock,
 )
 
-# Formatted Syntax Tree
+# nesting
+# DEFAULT
+# NEST
+# UNNEST
+# @enum()
+
+"""
+Formatted Syntax Tree
+"""
 mutable struct FST
     typ::Union{CSTParser.Head,FNode}
+
+    # Start and end lines of the node
+    # in the original source file.
     startline::Int
     endline::Int
     indent::Int
+
     len::Int
     val::Union{Nothing,AbstractString}
     nodes::Union{Nothing,Vector{FST}}
     ref::Union{Nothing,Ref{CSTParser.EXPR}}
     force_nest::Bool
+
+    # Extra margin caused by parent nodes.
+    # i.e. `(f(arg))`
+    #
+    # `f(arg)` would have `extra_margin` = 1
+    # due to `)` after `f(arg)`.
     extra_margin::Int
 end
 
@@ -34,11 +57,18 @@ function FST(cst::CSTParser.EXPR, startline::Integer, endline::Integer, val::Abs
     FST(cst.typ, startline, endline, 0, length(val), val, nothing, Ref(cst), false, 0)
 end
 
-function FST(cst::CSTParser.Head, startline::Integer, endline::Integer, val::AbstractString)
-    FST(cst, startline, endline, 0, length(val), val, nothing, nothing, false, 0)
+function FST(typ::CSTParser.Head, startline::Integer, endline::Integer, val::AbstractString)
+    FST(typ, startline, endline, 0, length(val), val, nothing, nothing, false, 0)
 end
 
-@inline Base.setindex!(fst::FST, node::FST, ind::Int) = fst.nodes[ind] = node
+FST(typ::CSTParser.Head, indent::Integer) =
+    FST(typ, -1, -1, indent, 0, nothing, FST[], nothing, false, 0)
+
+@inline function Base.setindex!(fst::FST, node::FST, ind::Int)
+    fst.len -= fst.nodes[ind].len
+    fst.nodes[ind] = node
+    fst.len += node.len
+end
 @inline Base.getindex(fst::FST, inds...) = fst.nodes[inds...]
 @inline Base.lastindex(fst::FST) = length(fst.nodes)
 
@@ -90,6 +120,13 @@ function is_multiline(fst::FST)
     false
 end
 
+function is_importer_exporter(fst::FST)
+    fst.typ === CSTParser.Import && return true
+    fst.typ === CSTParser.Export && return true
+    fst.typ === CSTParser.Using && return true
+    return false
+end
+
 # f a function which returns a bool
 function parent_is(cst::CSTParser.EXPR, f; ignore_typs = [])
     p = cst.parent
@@ -117,12 +154,12 @@ function get_args(cst::CSTParser.EXPR)
     elseif cst.typ === CSTParser.WhereOpCall
         # get the arguments in B of `A where B`
         return get_args(cst.args[3:end])
-    elseif cst.typ === CSTParser.Parameters ||
-           cst.typ === CSTParser.Braces ||
+    elseif cst.typ === CSTParser.Braces ||
            cst.typ === CSTParser.Vcat ||
            cst.typ === CSTParser.TupleH ||
            cst.typ === CSTParser.Vect ||
-           cst.typ === CSTParser.InvisBrackets
+           cst.typ === CSTParser.InvisBrackets ||
+           cst.typ === CSTParser.Parameters
         return get_args(cst.args)
     end
     CSTParser.get_args(cst)
@@ -193,7 +230,7 @@ function add_node!(t::FST, n::FST, s::State; join_lines = false, max_padding = -
     elseif n.typ === INLINECOMMENT
         push!(t.nodes, n)
         return
-    elseif n.typ isa FNode
+    elseif n.typ isa FNode && is_leaf(n)
         t.len += length(n)
         n.startline = t.startline
         n.endline = t.endline
@@ -205,10 +242,7 @@ function add_node!(t::FST, n::FST, s::State; join_lines = false, max_padding = -
         push!(t.nodes, n)
         return
     elseif n.typ === CSTParser.Parameters
-        if length(n) == 0
-            n.startline = t.endline
-            n.endline = t.endline
-        end
+        # unpack Parameters arguments into the parent node
         if n_args(t.ref[]) == n_args(n.ref[])
             # There are no arguments prior to params
             # so we can remove the initial placeholder.
@@ -226,6 +260,23 @@ function add_node!(t::FST, n::FST, s::State; join_lines = false, max_padding = -
             multi_arg = n_args(t.ref[]) > 0
             multi_arg ? add_node!(t, Placeholder(nws), s) : add_node!(t, Whitespace(nws), s)
         end
+        for nn in n.nodes
+            add_node!(t, nn, s, join_lines = true)
+        end
+        return
+    elseif s.opts.import_to_using && n.typ === CSTParser.Import
+        usings = import_to_usings(n, s)
+        if length(usings) > 0
+            for nn in usings
+                add_node!(t, nn, s, join_lines = false, max_padding = 0)
+            end
+            return
+        end
+    elseif n.typ === CSTParser.BinaryOpCall &&
+           n[1].typ === CSTParser.BinaryOpCall &&
+           n[1][end].typ === CSTParser.WhereOpCall
+        # normalize FST representation for WhereOpCall
+        binaryop_to_whereop!(n, s)
     end
 
     if length(t.nodes) == 0
@@ -290,10 +341,6 @@ function add_node!(t::FST, n::FST, s::State; join_lines = false, max_padding = -
             idx = length(t.nodes)
             t.nodes[idx-1], t.nodes[idx] = t.nodes[idx], t.nodes[idx-1]
         end
-
-        if n.typ === CSTParser.Parameters && n.force_nest
-            t.force_nest = true
-        end
     end
 
     @label add_node_end
@@ -308,8 +355,6 @@ function add_node!(t::FST, n::FST, s::State; join_lines = false, max_padding = -
     if !join_lines && is_end(n)
         # end keyword isn't useful w.r.t margin lengths
     elseif t.typ === CSTParser.StringH
-        # @info "insert literal into stringh" length(n) n n.indent + length(n) - t.indent t.indent n.indent
-
         # The length of this node is the length of
         # the longest string. The length of the string is
         # only considered "in the positive" when it's past
@@ -341,7 +386,7 @@ end
 
 Returns the length to any node type in `ntyps` based off the `start` index.
 """
-@inline function length_to(fst::FST, ntyps::Vector; start::Int = 1)
+@inline function length_to(fst::FST, ntyps; start::Int = 1)
     fst.typ in ntyps && return 0, true
     is_leaf(fst) && return length(fst), false
     len = 0
@@ -380,6 +425,9 @@ function is_iterable(x::Union{CSTParser.EXPR,FST})
     x.typ === CSTParser.InvisBrackets && return true
     x.typ === CSTParser.Ref && return true
     x.typ === CSTParser.TypedVcat && return true
+    x.typ === CSTParser.Import && return true
+    x.typ === CSTParser.Using && return true
+    x.typ === CSTParser.Export && return true
     return false
 end
 
@@ -396,6 +444,27 @@ function is_block(x::Union{CSTParser.EXPR,FST})
     return false
 end
 
+function is_opcall(x::Union{CSTParser.EXPR,FST})
+    x.typ === CSTParser.BinaryOpCall && return true
+    x.typ === CSTParser.Comparison && return true
+    x.typ === CSTParser.ChainOpCall && return true
+    # InvisBrackets are often mixed with operators
+    # so kwargs are propagated through its related
+    # functions
+    x.typ === CSTParser.InvisBrackets && return true
+    return false
+end
+
+# Generator typ
+# (x for x in 1:10)
+# (x for x in 1:10 if x % 2 == 0)
+function is_gen(x::Union{CSTParser.EXPR,FST})
+    x.typ === CSTParser.Generator && return true
+    x.typ === CSTParser.Filter && return true
+    x.typ === CSTParser.Flatten && return true
+    return false
+end
+
 function nest_block(cst::CSTParser.EXPR)
     cst.typ === CSTParser.If && return true
     cst.typ === CSTParser.Do && return true
@@ -441,108 +510,23 @@ function nest_rhs(cst::CSTParser.EXPR)::Bool
     false
 end
 
-
-@inline function flattenable(op::CSTParser.EXPR)
-    op.kind === Tokens.AND && return true
-    op.kind === Tokens.OR && return true
-    op.kind === Tokens.LAZY_AND && return true
-    op.kind === Tokens.LAZY_OR && return true
-    op.kind === Tokens.RPIPE && return true
+function flattenable(kind::Tokens.Kind)
+    kind === Tokens.AND && return true
+    kind === Tokens.OR && return true
+    kind === Tokens.LAZY_AND && return true
+    kind === Tokens.LAZY_OR && return true
+    kind === Tokens.RPIPE && return true
     return false
 end
+flattenable(::Nothing) = false
 
-"""
-Flattens a binary op call tree if the op repeats 2 or more times.
-"a && b && c" will be transformed while "a && b" will not.
-
-Transforms
-
-    BinaryOpCall
-     BinaryOpCall
-      BinaryOpCall
-       BinaryOpCall
-        BinaryOpCall
-         BinaryOpCall
-          some_expression
-          OP: RPIPE
-          some_expression
-         OP: RPIPE
-         some_expression
-        OP: RPIPE
-        some_expression
-       OP: RPIPE
-       some_expression
-      OP: RPIPE
-      some_expression
-     OP: RPIPE
-     some_expression
-
-into
-
-    ChainOpCall
-    some_expression
-    OP: RPIPE
-    some_expression
-    OP: RPIPE
-    some_expression
-    OP: RPIPE
-    some_expression
-    OP: RPIPE
-    some_expression
-    OP: RPIPE
-    some_expression
-    OP: RPIPE
-    some_expression
-"""
-function flatten_binaryopcall(fst::FST; top = true)
-    nodes = FST[]
-    op = fst.ref[][2]
-    flattenable(op) || return nodes
-
-    lhs = fst[1]
-    rhs = fst[end]
-    lhs_same_op = lhs.typ === CSTParser.BinaryOpCall && lhs.ref[][2].kind === op.kind
-    rhs_same_op = rhs.typ === CSTParser.BinaryOpCall && rhs.ref[][2].kind === op.kind
-
-    if top && !lhs_same_op && !rhs_same_op
-        return nodes
-    end
-
-    if lhs_same_op
-        # @info "calling lhs"
-        push!(nodes, flatten_binaryopcall(lhs, top = false)...)
-    else
-        flatten_fst!(lhs)
-        push!(nodes, lhs)
-    end
-    # everything except the indentation placeholder
-    push!(nodes, fst.nodes[2:end-2]...)
-
-    if rhs_same_op
-        # @info "calling rhs"
-        push!(nodes, flatten_binaryopcall(rhs, top = false)...)
-    else
-        flatten_fst!(rhs)
-        push!(nodes, rhs)
-    end
-
-    return nodes
-end
-
-function flatten_fst!(fst::FST)
-    is_leaf(fst) && return
-    for n in fst.nodes
-        if is_leaf(n)
-            continue
-        elseif n.typ === CSTParser.BinaryOpCall
-            # possibly convert BinaryOpCall to ChainOpCall
-            nnodes = flatten_binaryopcall(n)
-            if length(nnodes) > 0
-                n.nodes = nnodes
-                n.typ = CSTParser.ChainOpCall
-            end
-        else
-            flatten_fst!(n)
-        end
+function op_kind(cst::CSTParser.EXPR)::Union{Nothing,Tokens.Kind}
+    if cst.typ === CSTParser.BinaryOpCall
+        return cst[2].kind
+    elseif cst.typ === CSTParser.UnaryOpCall
+        return cst[1].typ === CSTParser.OPERATOR ? cst[1].kind : cst[2].kind
     end
+    return nothing
 end
+op_kind(::Nothing) = nothing
+op_kind(fst::FST) = fst.ref === nothing ? nothing : op_kind(fst.ref[])
diff --git a/src/nest.jl b/src/nest.jl
index 26dba183..f21136ea 100644
--- a/src/nest.jl
+++ b/src/nest.jl
@@ -24,15 +24,13 @@ function skip_indent(fst::FST)
     false
 end
 
-function walk(f, fst::FST, s::State)
-    f(fst, s)
-    is_leaf(fst) && return
-    for (i, n) in enumerate(fst.nodes)
-        if n.typ === NEWLINE && i < length(fst.nodes)
-            if is_closer(fst[i+1])
-                s.line_offset = fst[i+1].indent
-            elseif !skip_indent(fst[i+1])
-                s.line_offset = fst.indent
+function walk(f, nodes::Vector{FST}, s::State, indent::Int)
+    for (i, n) in enumerate(nodes)
+        if n.typ === NEWLINE && i < length(nodes)
+            if is_closer(nodes[i+1])
+                s.line_offset = nodes[i+1].indent
+            elseif !skip_indent(nodes[i+1])
+                s.line_offset = indent
             end
         else
             walk(f, n, s)
@@ -40,6 +38,12 @@ function walk(f, fst::FST, s::State)
     end
 end
 
+function walk(f, fst::FST, s::State)
+    f(fst, s)
+    is_leaf(fst) && return
+    walk(f, fst.nodes, s, fst.indent)
+end
+
 function reset_line_offset!(fst::FST, s::State)
     is_leaf(fst) || return
     s.line_offset += length(fst)
@@ -94,16 +98,41 @@ function dedent!(fst::FST, s::State)
     unnest!(fst, nl_inds)
 end
 
-function nest_if_over_margin!(style, fst::FST, s::State, i::Int)
+"""
+    nest_if_over_margin!(
+        style,
+        fst::FST,
+        s::State,
+        idx::Int;
+        stop_idx::Union{Int,Nothing} = nothing,
+    )
+
+Converts the node at `idx` to a `NEWLINE` if the margin until `stop_idx` is greater than
+the allowed margin.
+
+If `stop_idx` is `nothing`, the margin of all nodes in `fst` including and after `idx` will
+be included.
+"""
+function nest_if_over_margin!(
+    style,
+    fst::FST,
+    s::State,
+    idx::Int;
+    stop_idx::Union{Int,Nothing} = nothing,
+)
+    @assert fst[idx].typ == PLACEHOLDER
     margin = s.line_offset
-    idx = findnext(n -> n.typ === PLACEHOLDER, fst.nodes, i + 1)
-    margin += sum(length.(fst[i:end])) + fst.extra_margin
+    if stop_idx === nothing
+        margin += sum(length.(fst[idx:end])) + fst.extra_margin
+    else
+        margin += sum(length.(fst[idx:stop_idx-1]))
+    end
 
-    if margin > s.margin || is_comment(fst[i+1]) || is_comment(fst[i-1])
-        fst[i] = Newline(length = fst[i].len)
+    if margin > s.margin || is_comment(fst[idx+1]) || is_comment(fst[idx-1])
+        fst[idx] = Newline(length = fst[idx].len)
         s.line_offset = fst.indent
     else
-        nest!(style, fst[i], s)
+        nest!(style, fst[idx], s)
     end
 end
 
@@ -155,6 +184,14 @@ function nest!(ds::DefaultStyle, fst::FST, s::State)
     elseif fst.typ === CSTParser.ConditionalOpCall
         n_conditionalopcall!(style, fst, s)
     elseif fst.typ === CSTParser.BinaryOpCall
+        line_margin = s.line_offset + length(fst) + fst.extra_margin
+        if s.opts.short_to_long_function_def &&
+           line_margin > s.margin &&
+           fst.ref !== nothing &&
+           CSTParser.defines_function(fst.ref[])
+
+            short_to_long_function_def!(fst, s)
+        end
         n_binaryopcall!(style, fst, s)
     elseif fst.typ === CSTParser.Curly
         n_curly!(style, fst, s)
@@ -172,8 +209,6 @@ function nest!(ds::DefaultStyle, fst::FST, s::State)
         n_vect!(style, fst, s)
     elseif fst.typ === CSTParser.Vcat
         n_vcat!(style, fst, s)
-    elseif fst.typ === CSTParser.Parameters
-        n_parameters!(style, fst, s)
     elseif fst.typ === CSTParser.Braces
         n_braces!(style, fst, s)
     elseif fst.typ === CSTParser.InvisBrackets
@@ -225,6 +260,8 @@ function n_stringh!(ds::DefaultStyle, fst::FST, s::State)
         end
     end
 end
+n_stringh!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
+    n_stringh!(DefaultStyle(style), fst, s)
 
 function n_unaryopcall!(ds::DefaultStyle, fst::FST, s::State)
     style = getstyle(ds)
@@ -252,7 +289,7 @@ n_do!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
 
 
 # Import,Using,Export
-function n_import!(ds::DefaultStyle, fst::FST, s::State)
+function n_using!(ds::DefaultStyle, fst::FST, s::State)
     style = getstyle(ds)
     line_margin = s.line_offset + length(fst) + fst.extra_margin
     idx = findfirst(n -> n.typ === PLACEHOLDER, fst.nodes)
@@ -274,6 +311,9 @@ function n_import!(ds::DefaultStyle, fst::FST, s::State)
                 fst[i] = Newline(length = n.len)
                 s.line_offset = fst.indent
             else
+                diff = fst.indent - fst[i].indent
+                add_indent!(n, s, diff)
+                i < length(fst.nodes) && (n.extra_margin = 1)
                 nest!(style, n, s)
             end
         end
@@ -281,16 +321,16 @@ function n_import!(ds::DefaultStyle, fst::FST, s::State)
         nest!(style, fst.nodes, s, fst.indent, extra_margin = fst.extra_margin)
     end
 end
-n_import!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
-    n_import!(DefaultStyle(style), fst, s)
+n_using!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
+    n_using!(DefaultStyle(style), fst, s)
 
-@inline n_export!(ds::DefaultStyle, fst::FST, s::State) = n_import!(ds, fst, s)
+@inline n_export!(ds::DefaultStyle, fst::FST, s::State) = n_using!(ds, fst, s)
 n_export!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
     n_export!(DefaultStyle(style), fst, s)
 
-@inline n_using!(ds::DefaultStyle, fst::FST, s::State) = n_import!(ds, fst, s)
-n_using!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
-    n_using!(DefaultStyle(style), fst, s)
+@inline n_import!(ds::DefaultStyle, fst::FST, s::State) = n_using!(ds, fst, s)
+n_import!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
+    n_import!(DefaultStyle(style), fst, s)
 
 function n_tupleh!(ds::DefaultStyle, fst::FST, s::State)
     style = getstyle(ds)
@@ -302,7 +342,7 @@ function n_tupleh!(ds::DefaultStyle, fst::FST, s::State)
         if opener
             fst[end].indent = fst.indent
         end
-        if fst.typ !== CSTParser.Parameters && !(fst.typ === CSTParser.TupleH && !opener)
+        if fst.typ !== CSTParser.TupleH || opener
             fst.indent += s.indent_size
         end
 
@@ -376,6 +416,7 @@ function n_comprehension!(ds::DefaultStyle, fst::FST, s::State; indent = -1)
         if idx !== nothing
             fst[idx] = Newline(length = fst[idx].len)
         end
+
         add_indent!(fst, s, s.indent_size)
         fst[end].indent = fst.indent - s.indent_size
     end
@@ -454,13 +495,11 @@ function n_call!(ds::DefaultStyle, fst::FST, s::State)
                 n.len = 0
                 nest!(style, n, s)
             elseif i == 1 || i == length(fst.nodes)
-                n.typ === CSTParser.Parameters && (n.force_nest = true)
                 n.extra_margin = 1
                 nest!(style, n, s)
             else
                 diff = fst.indent - fst[i].indent
                 add_indent!(n, s, diff)
-                n.typ === CSTParser.Parameters && (n.force_nest = true)
                 n.extra_margin = 1
                 nest!(style, n, s)
             end
@@ -497,20 +536,12 @@ function n_whereopcall!(ds::DefaultStyle, fst::FST, s::State)
     line_margin = s.line_offset + length(fst) + fst.extra_margin
     if line_margin > s.margin || fst.force_nest
         line_offset = s.line_offset
-        # after "A where "
-        idx = findfirst(n -> n.typ === PLACEHOLDER, fst.nodes)
-        Blen = sum(length.(fst[idx+1:end]))
+        Blen = sum(length.(fst[2:end]))
 
-        # A, +7 is the length of " where "
-        fst[1].extra_margin = Blen + 7 + fst.extra_margin
+        fst[1].extra_margin = Blen + fst.extra_margin
         nest!(style, fst[1], s)
 
-        for (i, n) in enumerate(fst[2:idx-1])
-            nest!(style, n, s)
-        end
-
         # "B"
-
         has_braces = is_closer(fst[end])
         if has_braces
             fst[end].indent = fst.indent
@@ -518,8 +549,7 @@ function n_whereopcall!(ds::DefaultStyle, fst::FST, s::State)
 
         over = (s.line_offset + Blen + fst.extra_margin > s.margin) || fst.force_nest
         fst.indent += s.indent_size
-
-        for (i, n) in enumerate(fst[idx+1:end])
+        for (i, n) in enumerate(fst[2:end])
             if n.typ === NEWLINE
                 s.line_offset = fst.indent
             elseif is_opener(n)
@@ -529,7 +559,7 @@ function n_whereopcall!(ds::DefaultStyle, fst::FST, s::State)
                 end
                 nest!(style, n, s)
             elseif n.typ === PLACEHOLDER && over
-                fst[i+idx] = Newline(length = n.len)
+                fst[i+1] = Newline(length = length(n))
                 s.line_offset = fst.indent
             elseif n.typ === TRAILINGCOMMA && over
                 n.val = ","
@@ -619,17 +649,11 @@ n_conditionalopcall!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
 
 no_unnest(fst::FST) = fst.typ === CSTParser.BinaryOpCall && contains_comment(fst)
 
-# lhs op rhs
-#
-# nest in order of
-#
-# lhs op
-# lhs
 function n_binaryopcall!(ds::DefaultStyle, fst::FST, s::State)
     style = getstyle(ds)
+    line_margin = s.line_offset + length(fst) + fst.extra_margin
     # If there's no placeholder the binary call is not nestable
     idxs = findall(n -> n.typ === PLACEHOLDER, fst.nodes)
-    line_margin = s.line_offset + length(fst) + fst.extra_margin
     rhs = fst[end]
     rhs.typ === CSTParser.Block && (rhs = rhs[1])
     if length(idxs) == 2 && (line_margin > s.margin || fst.force_nest || rhs.force_nest)
@@ -676,7 +700,7 @@ function n_binaryopcall!(ds::DefaultStyle, fst::FST, s::State)
                rhs.typ === CSTParser.Comparison
                 line_margin += length(fst[end])
             elseif rhs.typ === CSTParser.Do && is_iterable(rhs[1])
-                rw, _ = length_to(fst, [NEWLINE], start = i2 + 1)
+                rw, _ = length_to(fst, (NEWLINE,), start = i2 + 1)
                 line_margin += rw
             elseif is_block(cst)
                 idx = findfirst(n -> n.typ === NEWLINE, rhs.nodes)
@@ -686,7 +710,7 @@ function n_binaryopcall!(ds::DefaultStyle, fst::FST, s::State)
                     line_margin += sum(length.(rhs[1:idx-1]))
                 end
             else
-                rw, _ = length_to(fst, [NEWLINE], start = i2 + 1)
+                rw, _ = length_to(fst, (NEWLINE,), start = i2 + 1)
                 line_margin += rw
             end
 
@@ -702,48 +726,19 @@ function n_binaryopcall!(ds::DefaultStyle, fst::FST, s::State)
         s.line_offset = line_offset
         walk(reset_line_offset!, fst, s)
     else
-        # Handles the case of a function def defined
-        # as "foo(a)::R where {A,B} = body".
-        #
-        # In this case instead of it being parsed as:
-        #
-        # CSTParser.BinaryOpCall
-        #  - CSTParser.WhereOpCall
-        #  - OP
-        #  - rhs
-        #
-        # It's parsed as:
-        #
-        # CSTParser.BinaryOpCall
-        #  - CSTParser.BinaryOpCall
-        #   - lhs
-        #   - OP
-        #   - CSTParser.WhereOpCall
-        #    - R
-        #    - ...
-        #  - OP
-        #  - rhs
-        #
-        # The result being extra width is trickier to deal with.
-        idx = findfirst(n -> n.typ === CSTParser.WhereOpCall, fst.nodes)
-        return_width = 0
-        if idx !== nothing && idx > 1
-            return_width = length(fst[idx]) + length(fst[2])
-        elseif idx === nothing
-            return_width, _ = length_to(fst, [NEWLINE], start = 2)
-        end
+        # length of op and surrounding whitespace
+        oplen = sum(length.(fst[2:end]))
 
         for (i, n) in enumerate(fst.nodes)
             if n.typ === NEWLINE
                 s.line_offset = fst.indent
             elseif i == 1
-                n.extra_margin = return_width + fst.extra_margin
+                n.extra_margin = oplen + fst.extra_margin
                 nest!(style, n, s)
-            elseif i == idx
+            elseif i == length(fst.nodes)
                 n.extra_margin = fst.extra_margin
                 nest!(style, n, s)
             else
-                n.extra_margin = fst.extra_margin
                 nest!(style, n, s)
             end
         end
diff --git a/src/passes.jl b/src/passes.jl
new file mode 100644
index 00000000..7ff9054f
--- /dev/null
+++ b/src/passes.jl
@@ -0,0 +1,395 @@
+# FST passes/transforms
+
+"""
+Flattens a binary operation call tree if the operation repeats 2 or more times.
+"a && b && c" will be transformed while "a && b" will not.
+"""
+function flatten_binaryopcall(fst::FST; top = true)
+    nodes = FST[]
+
+    lhs = fst[1]
+    kind = op_kind(fst)
+    rhs = fst[end]
+    lhs_same_op = op_kind(lhs) === kind
+    rhs_same_op = op_kind(rhs) === kind
+
+    if top && !lhs_same_op && !rhs_same_op
+        return nodes
+    end
+
+    if lhs_same_op
+        push!(nodes, flatten_binaryopcall(lhs, top = false)...)
+    else
+        flatten_fst!(lhs)
+        push!(nodes, lhs)
+    end
+    # everything except the indentation placeholder
+    push!(nodes, fst[2:end-2]...)
+
+    if rhs_same_op
+        push!(nodes, flatten_binaryopcall(rhs, top = false)...)
+    else
+        flatten_fst!(rhs)
+        push!(nodes, rhs)
+    end
+
+    return nodes
+end
+
+function flatten_fst!(fst::FST)
+    is_leaf(fst) && return
+    for n in fst.nodes
+        if is_leaf(n)
+            continue
+        elseif n.typ === CSTParser.BinaryOpCall && flattenable(op_kind(n))
+            # possibly convert BinaryOpCall to ChainOpCall
+            nnodes = flatten_binaryopcall(n)
+            if length(nnodes) > 0
+                n.nodes = nnodes
+                n.typ = CSTParser.ChainOpCall
+            end
+        else
+            flatten_fst!(n)
+        end
+    end
+end
+
+is_pipe(n) = op_kind(n) === Tokens.RPIPE
+
+
+"""
+    pipe_to_function_call_pass!(fst::FST)
+
+Rewrites `x |> f` to `f(x)`.
+"""
+function pipe_to_function_call_pass!(fst::FST)
+    is_leaf(fst) && return
+
+    if is_pipe(fst)
+        fst.nodes = pipe_to_function_call(fst)
+        fst.typ = CSTParser.Call
+        return
+    end
+
+    for n in fst.nodes
+        if is_leaf(n)
+            continue
+        elseif is_pipe(n)
+            n.nodes = pipe_to_function_call(n)
+            n.typ = CSTParser.Call
+        else
+            pipe_to_function_call_pass!(n)
+        end
+    end
+end
+
+function pipe_to_function_call(fst::FST)
+    nodes = FST[]
+    arg2 = fst[end]
+    push!(nodes, arg2)
+    paren = FST(CSTParser.PUNCTUATION, arg2.endline, arg2.endline, "(")
+    push!(nodes, paren)
+    pipe_to_function_call_pass!(fst[1])
+    arg1 = fst[1]
+    push!(nodes, arg1)
+    paren = FST(CSTParser.PUNCTUATION, arg1.endline, arg1.endline, ")")
+    push!(nodes, paren)
+    return nodes
+end
+
+function import_to_usings(fst::FST, s::State)
+    findfirst(is_colon, fst.nodes) === nothing || return FST[]
+
+    usings = FST[]
+    idxs = findall(n -> n.typ === CSTParser.IDENTIFIER, fst.nodes)
+
+    for i in idxs
+        name = fst[i].val
+        sl = fst[i].startline
+        el = fst[i].endline
+        use = FST(CSTParser.Using, fst.indent)
+        use.startline = sl
+        use.endline = el
+
+        add_node!(use, FST(CSTParser.KEYWORD, sl, el, "using"), s)
+        add_node!(use, Whitespace(1), s)
+
+        # collect the dots prior to a identifier
+        # import ..A
+        j = i - 1
+        while fst[j].typ === CSTParser.OPERATOR
+            add_node!(use, fst[j], s, join_lines = true)
+            j -= 1
+        end
+
+        add_node!(use, FST(CSTParser.IDENTIFIER, sl, el, name), s, join_lines = true)
+        add_node!(use, FST(CSTParser.OPERATOR, sl, el, ":"), s, join_lines = true)
+        add_node!(use, Whitespace(1), s)
+        add_node!(use, FST(CSTParser.IDENTIFIER, sl, el, name), s, join_lines = true)
+
+        push!(usings, use)
+    end
+    return usings
+end
+
+"""
+    annotate_typefields_with_any!(fst::FST, s::State)
+
+Annotates fields in a type definitions with `::Any` if
+no type annotation is provided.
+"""
+function annotate_typefields_with_any!(fst::FST, s::State)
+    is_leaf(fst) && return
+    for (i, n) in enumerate(fst.nodes)
+        if n.typ === CSTParser.IDENTIFIER
+            nn = FST(CSTParser.BinaryOpCall, n.indent)
+            nn.startline = n.startline
+            nn.endline = n.endline
+            add_node!(nn, n, s)
+            add_node!(
+                nn,
+                FST(CSTParser.OPERATOR, n.startline, n.endline, "::"),
+                s,
+                join_lines = true,
+            )
+            add_node!(
+                nn,
+                FST(CSTParser.IDENTIFIER, n.startline, n.endline, "Any"),
+                s,
+                join_lines = true,
+            )
+            fst[i] = nn
+        else
+            continue
+        end
+    end
+end
+
+
+"""
+    short_to_long_function_def!(fst::FST, s::State)
+
+Transforms a _short_ function definition
+
+```julia
+f(arg1, arg2) = body
+```
+
+to a _long_ function definition
+
+```julia
+function f(arg2, arg2)
+    body
+end
+```
+"""
+function short_to_long_function_def!(fst::FST, s::State)
+    # 3 cases
+    #
+    # case 1
+    #   func(a) = body
+    #
+    # case 2
+    #   func(a::T) where T = body
+    #
+    # case 3
+    #   func(a::T)::R where T = body
+
+    funcdef = FST(CSTParser.FunctionDef, fst.indent)
+    if fst[1].typ === CSTParser.Call || fst[1].typ === CSTParser.WhereOpCall
+        # function
+        kw = FST(CSTParser.KEYWORD, fst[1].startline, fst[1].endline, "function")
+        add_node!(funcdef, kw, s)
+        add_node!(funcdef, Whitespace(1), s)
+
+        # func(a)
+        # OR
+        # func(a) where T
+        add_node!(funcdef, fst[1], s, join_lines = true)
+
+        # body
+        s.opts.always_use_return && prepend_return!(fst[end], s)
+        add_node!(funcdef, fst[end], s, max_padding = s.indent_size)
+        add_indent!(funcdef[end], s, s.indent_size)
+
+        # end
+        kw = FST(CSTParser.KEYWORD, fst[end].startline, fst[end].endline, "end")
+        add_node!(funcdef, kw, s)
+
+        fst.typ = funcdef.typ
+        fst.nodes = funcdef.nodes
+        fst.len = funcdef.len
+    elseif fst[1].typ === CSTParser.BinaryOpCall &&
+           fst[1][end].typ === CSTParser.WhereOpCall
+        # function
+        kw = FST(CSTParser.KEYWORD, fst[1].startline, fst[1].endline, "function")
+        add_node!(funcdef, kw, s)
+        add_node!(funcdef, Whitespace(1), s)
+
+        # func(a)
+        add_node!(funcdef, fst[1][1], s, join_lines = true)
+
+        whereop = fst[1][end]
+        decl = FST(CSTParser.OPERATOR, fst[1].startline, fst[1].endline, "::")
+
+        # ::R where T
+        add_node!(funcdef, decl, s, join_lines = true)
+        add_node!(funcdef, whereop, s, join_lines = true)
+
+        # body
+        s.opts.always_use_return && prepend_return!(fst[end], s)
+        add_node!(funcdef, fst[end], s, max_padding = s.indent_size)
+        add_indent!(funcdef[end], s, s.indent_size)
+
+        # end
+        kw = FST(CSTParser.KEYWORD, fst[end].startline, fst[end].endline, "end")
+        add_node!(funcdef, kw, s)
+
+        fst.typ = funcdef.typ
+        fst.nodes = funcdef.nodes
+        fst.len = funcdef.len
+    end
+end
+
+"""
+    binaryop_to_whereop(fst::FST, s::State)
+
+
+Handles the case of a function def defined
+as:
+
+```julia
+foo(a::A)::R where A = body
+```
+
+In this case instead of it being parsed as (1):
+
+```
+CSTParser.BinaryOpCall
+ - CSTParser.WhereOpCall
+ - OP
+ - RHS
+```
+
+It's parsed as (2):
+
+```
+CSTParser.BinaryOpCall
+ - CSTParser.BinaryOpCall
+  - LHS
+  - OP
+  - CSTParser.WhereOpCall
+   - R
+   - ...
+ - OP
+ - RHS
+```
+
+(1) is preferrable since it's the same parsed result as:
+
+```julia
+foo(a::A) where A = body
+```
+
+This transformation converts (2) to (1).
+
+ref https://github.com/julia-vscode/CSTParser.jl/issues/93
+"""
+function binaryop_to_whereop!(fst::FST, s::State)
+    # transform fst[1] to a WhereOpCall
+    oldbinop = fst[1]
+    oldwhereop = fst[1][end]
+    binop = FST(CSTParser.BinaryOpCall, fst[1].indent)
+
+    # foo(a::A)
+    add_node!(binop, oldbinop[1], s)
+    # foo(a::A)::
+    add_node!(binop, oldbinop[2], s, join_lines = true)
+    # foo(a::A)::R
+    add_node!(binop, oldwhereop[1], s, join_lines = true)
+
+    whereop = FST(CSTParser.WhereOpCall, fst[1].indent)
+    add_node!(whereop, binop, s)
+
+    # "foo(a::A)::R where A"
+    for n in oldwhereop[2:end]
+        add_node!(whereop, n, s, join_lines = true)
+    end
+
+    fst[1] = whereop
+end
+
+"""
+    prepend_return!(fst::FST, s::State)
+
+Prepends `return` to the last expression of a block.
+
+```julia
+function foo()
+    a = 2 * 3
+    a / 3
+end
+```
+
+to
+
+```julia
+function foo()
+    a = 2 * 3
+    return a / 3
+end
+```
+"""
+function prepend_return!(fst::FST, s::State)
+    fst.typ === CSTParser.Block || return
+    fst[end].typ !== CSTParser.Return || return
+
+    ret = FST(CSTParser.Return, fst.indent)
+    ln = fst[end]
+    kw = FST(CSTParser.KEYWORD, fst[end].startline, fst[end].endline, "return")
+    add_node!(ret, kw, s)
+    add_node!(ret, Whitespace(1), s)
+    add_node!(ret, fst[end], s, join_lines = true)
+    fst[end] = ret
+end
+
+"""
+    move_at_sign_to_the_end(fst::FST, s::State)
+
+NOTE: Assumes `fst` is the caller name of a macrocall such as
+`@macro` or `Module.@macro`.
+
+Moves `@` to the last indentifier.
+
+Example:
+
+```julia
+@Module.macro
+```
+
+to
+
+```julia
+Module.@macro
+```
+"""
+function move_at_sign_to_the_end(fst::FST, s::State)
+    t = FST[]
+    f = (t) -> (n, s) -> is_leaf(n) && push!(t, n)
+    walk(f(t), fst, s)
+
+    macroname = FST(CSTParser.MacroName, fst.indent)
+    for (i, n) in enumerate(t)
+        if n.val == "@"
+            continue
+        elseif i < length(t)
+            add_node!(macroname, n, s, join_lines = true)
+        else
+            at = FST(CSTParser.PUNCTUATION, n.startline, n.endline, "@")
+            add_node!(macroname, at, s, join_lines = true)
+            add_node!(macroname, n, s, join_lines = true)
+        end
+    end
+
+    return macroname
+end
diff --git a/src/pretty.jl b/src/pretty.jl
index da2997ac..4d0e20e7 100644
--- a/src/pretty.jl
+++ b/src/pretty.jl
@@ -410,17 +410,25 @@ function p_macrocall(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
                     add_node!(t, Whitespace(1), s)
                 end
             else
-                add_node!(t, n, s, join_lines = true, max_padding = 0)
+                padding = n.endline == n.startline ? -1 : 0
+                add_node!(t, n, s, join_lines = true, max_padding = padding)
                 i < length(cst) && add_node!(t, Whitespace(1), s)
             end
         else
             if has_closer
                 add_node!(t, n, s, join_lines = true)
             else
-                add_node!(t, n, s, join_lines = true, max_padding = 0)
+                padding = n.endline == n.startline ? -1 : 0
+                add_node!(t, n, s, join_lines = true, max_padding = padding)
             end
         end
     end
+    # move placement of @ to the end
+    #
+    # @Module.macro -> Module.@macro
+    t[1] = move_at_sign_to_the_end(t[1], s)
+
+    !has_closer && (t.typ = MacroBlock)
     t
 end
 p_macrocall(style::S, cst::CSTParser.EXPR, s::State) where {S<:AbstractStyle} =
@@ -542,12 +550,9 @@ function p_functiondef(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
             add_node!(t, pretty(style, cst[4], s), s, join_lines = true)
         else
             s.indent += s.indent_size
-            add_node!(
-                t,
-                pretty(style, cst[3], s, ignore_single_line = true),
-                s,
-                max_padding = s.indent_size,
-            )
+            n = pretty(style, cst[3], s, ignore_single_line = true)
+            s.opts.always_use_return && prepend_return!(n, s)
+            add_node!(t, n, s, max_padding = s.indent_size)
             s.indent -= s.indent_size
             add_node!(t, pretty(style, cst[4], s), s)
         end
@@ -578,12 +583,9 @@ function p_struct(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
         add_node!(t, pretty(style, cst[4], s), s, join_lines = true)
     else
         s.indent += s.indent_size
-        add_node!(
-            t,
-            pretty(style, cst[3], s, ignore_single_line = true),
-            s,
-            max_padding = s.indent_size,
-        )
+        n = pretty(style, cst[3], s, ignore_single_line = true)
+        annotate_typefields_with_any!(n, s)
+        add_node!(t, n, s, max_padding = s.indent_size)
         s.indent -= s.indent_size
         add_node!(t, pretty(style, cst[4], s), s)
     end
@@ -606,12 +608,9 @@ function p_mutable(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
         add_node!(t, pretty(style, cst[5], s), s, join_lines = true)
     else
         s.indent += s.indent_size
-        add_node!(
-            t,
-            pretty(style, cst[4], s, ignore_single_line = true),
-            s,
-            max_padding = s.indent_size,
-        )
+        n = pretty(style, cst[4], s, ignore_single_line = true)
+        annotate_typefields_with_any!(n, s)
+        add_node!(t, n, s, max_padding = s.indent_size)
         s.indent -= s.indent_size
         add_node!(t, pretty(style, cst[5], s), s)
     end
@@ -888,12 +887,9 @@ function p_do(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
     end
     if cst[4].typ === CSTParser.Block
         s.indent += s.indent_size
-        add_node!(
-            t,
-            pretty(style, cst[4], s, ignore_single_line = true),
-            s,
-            max_padding = s.indent_size,
-        )
+        n = pretty(style, cst[4], s, ignore_single_line = true)
+        s.opts.always_use_return && prepend_return!(n, s)
+        add_node!(t, n, s, max_padding = s.indent_size)
         s.indent -= s.indent_size
     end
     add_node!(t, pretty(style, cst.args[end], s), s)
@@ -1075,11 +1071,7 @@ function p_colonopcall(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
     t = FST(cst, nspaces(s))
     nospace = !s.opts.whitespace_ops_in_indices
     for a in cst
-        if a.typ === CSTParser.BinaryOpCall
-            n = pretty(style, a, s, nonest = true, nospace = nospace)
-        elseif a.typ === CSTParser.InvisBrackets
-            n = pretty(style, a, s, nonest = true, nospace = nospace)
-        elseif a.typ === CSTParser.ChainOpCall || a.typ === CSTParser.Comparison
+        if is_opcall(a)
             n = pretty(style, a, s, nonest = true, nospace = nospace)
         else
             n = pretty(style, a, s)
@@ -1140,11 +1132,7 @@ function p_binaryopcall(
     end
     nospace_args = s.opts.whitespace_ops_in_indices ? false : nospace
 
-    if cst[1].typ === CSTParser.BinaryOpCall
-        n = pretty(style, cst[1], s, nonest = nonest, nospace = nospace_args)
-    elseif cst[1].typ === CSTParser.InvisBrackets
-        n = pretty(style, cst[1], s, nonest = nonest, nospace = nospace_args)
-    elseif cst[1].typ === CSTParser.ChainOpCall || cst[1].typ === CSTParser.Comparison
+    if is_opcall(cst[1])
         n = pretty(style, cst[1], s, nonest = nonest, nospace = nospace_args)
     else
         n = pretty(style, cst[1], s)
@@ -1154,6 +1142,7 @@ function p_binaryopcall(
        s.opts.whitespace_ops_in_indices &&
        !is_leaf(cst[1]) &&
        !is_iterable(cst[1])
+
         paren = FST(CSTParser.PUNCTUATION, n.startline, n.startline, "(")
         add_node!(t, paren, s)
         add_node!(t, n, s, join_lines = true)
@@ -1168,7 +1157,7 @@ function p_binaryopcall(
     nest = (nestable(style, cst) && !nonest) || nrhs
 
     if op.fullspan == 0 && cst[3].typ === CSTParser.IDENTIFIER
-        # do nothing
+        # noop
     elseif op.kind === Tokens.EX_OR
         add_node!(t, Whitespace(1), s)
         add_node!(t, pretty(style, op, s), s, join_lines = true)
@@ -1187,11 +1176,7 @@ function p_binaryopcall(
         nest ? add_node!(t, Placeholder(1), s) : add_node!(t, Whitespace(1), s)
     end
 
-    if cst[3].typ === CSTParser.BinaryOpCall
-        n = pretty(style, cst[3], s, nonest = nonest, nospace = nospace_args)
-    elseif cst[3].typ === CSTParser.InvisBrackets
-        n = pretty(style, cst[3], s, nonest = nonest, nospace = nospace_args)
-    elseif cst[3].typ === CSTParser.ChainOpCall || cst[3].typ === CSTParser.Comparison
+    if is_opcall(cst[3])
         n = pretty(style, cst[3], s, nonest = nonest, nospace = nospace_args)
     else
         n = pretty(style, cst[3], s)
@@ -1237,21 +1222,18 @@ function p_whereopcall(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
     add_node!(t, pretty(style, cst[2], s), s, join_lines = true)
     add_node!(t, Whitespace(1), s)
 
-    # Used to mark where `B` starts.
-    add_node!(t, Placeholder(0), s)
-
     args = get_args(cst.args[3:end])
     nest = length(args) > 0 && !(length(args) == 1 && unnestable_arg(args[1]))
     add_braces =
         !CSTParser.is_lbrace(cst[3]) &&
         cst.parent.typ !== CSTParser.Curly &&
-        cst[3].typ !== CSTParser.Curly && cst[3].typ !== CSTParser.BracesCat
+        cst[3].typ !== CSTParser.Curly &&
+        cst[3].typ !== CSTParser.BracesCat
 
     brace = FST(CSTParser.PUNCTUATION, t.endline, t.endline, "{")
     add_braces && add_node!(t, brace, s, join_lines = true)
 
     nws = s.opts.whitespace_typedefs ? 1 : 0
-    # @debug "" nest in_braces cst[3].val == "{" cst.args[end].val
     for (i, a) in enumerate(cst.args[3:end])
         if is_opener(a) && nest
             add_node!(t, pretty(style, a, s), s, join_lines = true)
@@ -1401,13 +1383,7 @@ function p_invisbrackets(
     for (i, a) in enumerate(cst)
         if a.typ === CSTParser.Block
             add_node!(t, pretty(style, a, s, from_quote = true), s, join_lines = true)
-        elseif a.typ === CSTParser.BinaryOpCall
-            n = pretty(style, a, s, nonest = nonest, nospace = nospace)
-            add_node!(t, n, s, join_lines = true)
-        elseif a.typ === CSTParser.InvisBrackets
-            n = pretty(style, a, s, nonest = nonest, nospace = nospace)
-            add_node!(t, n, s, join_lines = true)
-        elseif a.typ === CSTParser.ChainOpCall || a.typ === CSTParser.Comparison
+        elseif is_opcall(a)
             n = pretty(style, a, s, nonest = nonest, nospace = nospace)
             add_node!(t, n, s, join_lines = true)
         elseif is_opener(a) && nest
@@ -1566,10 +1542,10 @@ function p_parameters(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
         if i == length(cst) && CSTParser.is_comma(a)
             # do nothing
         elseif CSTParser.is_comma(a) && i < length(cst) && !is_punc(cst[i+1])
-            add_node!(t, n, s, join_lines = true)
-            add_node!(t, Placeholder(1), s)
+            push!(t.nodes, n)
+            push!(t.nodes, Placeholder(1))
         else
-            add_node!(t, n, s, join_lines = true)
+            push!(t.nodes, n)
         end
     end
     t
@@ -1577,6 +1553,7 @@ end
 p_parameters(style::S, cst::CSTParser.EXPR, s::State) where {S<:AbstractStyle} =
     p_parameters(DefaultStyle(style), cst, s)
 
+
 # Import, Export, Using
 function p_import(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
     style = getstyle(ds)
@@ -1622,13 +1599,7 @@ function p_ref(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
         elseif CSTParser.is_comma(a) && i < length(cst) && !is_punc(cst[i+1])
             add_node!(t, pretty(style, a, s), s, join_lines = true)
             add_node!(t, Placeholder(1), s)
-        elseif a.typ === CSTParser.BinaryOpCall
-            n = pretty(style, a, s, nonest = true, nospace = nospace)
-            add_node!(t, n, s, join_lines = true)
-        elseif a.typ === CSTParser.InvisBrackets
-            n = pretty(style, a, s, nonest = true, nospace = nospace)
-            add_node!(t, n, s, join_lines = true)
-        elseif a.typ === CSTParser.ChainOpCall || a.typ === CSTParser.Comparison
+        elseif is_opcall(a)
             n = pretty(style, a, s, nonest = true, nospace = nospace)
             add_node!(t, n, s, join_lines = true)
         else
diff --git a/src/styles/yas.jl b/src/styles/yas.jl
index fab332d4..c1e0d6ea 100644
--- a/src/styles/yas.jl
+++ b/src/styles/yas.jl
@@ -1,39 +1,28 @@
-# Experimental
-#
-# YAS style !!!
-
 """
-    YASStyle
+    YASStyle()
 
-**This style is EXPERIMENTAL !!!**
-
-Formatting style based on https://github.com/jrevels/YASGuide.
+Formatting style based on https://github.com/jrevels/YASGuide
+and https://github.com/domluna/JuliaFormatter.jl/issues/198.
 
 Recommended options are:
 
-- `always_for_in` = true,
-- `whitespace_ops_in_indices` = true,
-- `whitespace_typedefs` = false,
-- `remove_extra_newlines` = true,
+- `always_for_in` = true
+- `whitespace_ops_in_indices` = true
+- `whitespace_typedefs` = false
+- `remove_extra_newlines` = true
+- `import_to_using` = true
+- `pipe_to_function_call` = true
+- `short_to_long_function_def` = true
+- `always_use_return` = true
 """
 struct YASStyle <: AbstractStyle end
 @inline getstyle(s::YASStyle) = s
 
-yasformat(s::AbstractString; kwargs...) = format_text(
-    s;
-    kwargs...,
-    always_for_in = true,
-    whitespace_ops_in_indices = true,
-    whitespace_typedefs = false,
-    remove_extra_newlines = true,
-    style = YASStyle(),
-)
-
-# function nestable(::YASStyle, cst::CSTParser.EXPR)
-#     CSTParser.defines_function(cst) && cst[1].typ !== CSTParser.UnaryOpCall && return false
-#     nest_assignment(cst) && return false
-#     return true
-# end
+function nestable(::YASStyle, cst::CSTParser.EXPR)
+    (CSTParser.defines_function(cst) || nest_assignment(cst)) && return false
+    (cst[2].kind === Tokens.PAIR_ARROW || cst[2].kind === Tokens.ANON_FUNC) && return false
+    return true
+end
 
 function p_kw(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     t = FST(cst, nspaces(s))
@@ -43,6 +32,27 @@ function p_kw(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     t
 end
 
+function p_import(ys::YASStyle, cst::CSTParser.EXPR, s::State)
+    t = FST(cst, nspaces(s))
+    add_node!(t, pretty(ys, cst[1], s), s)
+    add_node!(t, Whitespace(1), s)
+
+    for (i, a) in enumerate(cst.args[2:end])
+        if CSTParser.is_comma(a)
+            add_node!(t, pretty(ys, a, s), s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
+        elseif CSTParser.is_colon(a)
+            add_node!(t, pretty(ys, a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
+        else
+            add_node!(t, pretty(ys, a, s), s, join_lines = true)
+        end
+    end
+    t
+end
+@inline p_using(ys::YASStyle, cst::CSTParser.EXPR, s::State) = p_import(ys, cst, s)
+@inline p_export(ys::YASStyle, cst::CSTParser.EXPR, s::State) = p_import(ys, cst, s)
+
 function p_curly(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     t = FST(cst, nspaces(s))
     for (i, a) in enumerate(cst)
@@ -63,15 +73,37 @@ end
 function p_tupleh(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     t = FST(cst, nspaces(s))
     for (i, a) in enumerate(cst)
-        n = pretty(ys, a, s)
         if CSTParser.is_comma(a) && i + 1 == length(cst)
             if n_args(cst) == 1
-                add_node!(t, n, s, join_lines = true)
+                add_node!(t, pretty(ys, a, s), s, join_lines = true)
             elseif !is_closer(cst[i+1])
-                add_node!(t, n, s, join_lines = true)
+                add_node!(t, pretty(ys, a, s), s, join_lines = true)
                 add_node!(t, Placeholder(1), s)
             end
         elseif CSTParser.is_comma(a) && i < length(cst) && !is_punc(cst[i+1])
+            add_node!(t, pretty(ys, a, s), s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
+        elseif a.typ === CSTParser.BinaryOpCall && a[2].kind === Tokens.EQ
+            add_node!(t, pretty(ys, a, s, nospace = true), s, join_lines = true)
+        else
+            add_node!(t, pretty(ys, a, s), s, join_lines = true)
+        end
+    end
+    t
+end
+
+function p_tupleh(ys::YASStyle, nodes::Vector{CSTParser.EXPR}, s::State)
+    t = FST(CSTParser.TupleH, nspaces(s))
+    for (i, a) in enumerate(nodes)
+        n = pretty(ys, a, s)
+        if CSTParser.is_comma(a) && i + 1 == length(nodes)
+            if n_args(nodes) == 1
+                add_node!(t, n, s, join_lines = true)
+            elseif !is_closer(nodes[i+1])
+                add_node!(t, n, s, join_lines = true)
+                add_node!(t, Placeholder(1), s)
+            end
+        elseif CSTParser.is_comma(a) && i < length(nodes) && !is_punc(nodes[i+1])
             add_node!(t, n, s, join_lines = true)
             add_node!(t, Placeholder(1), s)
         else
@@ -81,6 +113,23 @@ function p_tupleh(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     t
 end
 
+# InvisBrackets
+function p_invisbrackets(
+    ys::YASStyle,
+    cst::CSTParser.EXPR,
+    s::State;
+    nonest = false,
+    nospace = false,
+)
+    t = p_invisbrackets(DefaultStyle(ys), cst, s, nonest = nonest, nospace = nospace)
+    for (i, n) in enumerate(t.nodes)
+        if n.typ === PLACEHOLDER
+            t[i] = Whitespace(length(n))
+        end
+    end
+    t
+end
+
 function p_call(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     t = FST(cst, nspaces(s))
     for (i, a) in enumerate(cst)
@@ -96,9 +145,75 @@ function p_call(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     end
     t
 end
-@inline p_ref(ys::YASStyle, cst::CSTParser.EXPR, s::State) = p_call(ys, cst, s)
 @inline p_vect(ys::YASStyle, cst::CSTParser.EXPR, s::State) = p_call(ys, cst, s)
-@inline p_comprehension(ys::YASStyle, cst::CSTParser.EXPR, s::State) = p_call(ys, cst, s)
+
+function p_ref(ys::YASStyle, cst::CSTParser.EXPR, s::State)
+    t = FST(cst, nspaces(s))
+    nospace = !s.opts.whitespace_ops_in_indices
+
+    for (i, a) in enumerate(cst)
+        if CSTParser.is_comma(a) && i < length(cst) && !is_punc(cst[i+1])
+            add_node!(t, pretty(ys, a, s), s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
+        elseif a.typ === CSTParser.BinaryOpCall ||
+               a.typ === CSTParser.InvisBrackets ||
+               a.typ === CSTParser.ChainOpCall ||
+               a.typ === CSTParser.Comparison
+
+            n = pretty(ys, a, s, nonest = true, nospace = nospace)
+            add_node!(t, n, s, join_lines = true)
+        else
+            add_node!(t, pretty(ys, a, s), s, join_lines = true)
+        end
+    end
+    t
+end
+
+function p_comprehension(ys::YASStyle, cst::CSTParser.EXPR, s::State)
+    t = FST(cst, nspaces(s))
+
+    if is_block(cst[2])
+        t.force_nest = true
+    elseif cst[2].typ === CSTParser.Generator && is_block(cst[2][1])
+        t.force_nest = true
+    end
+
+    add_node!(t, pretty(ys, cst[1], s), s, join_lines = true)
+    add_node!(t, pretty(ys, cst[2], s), s, join_lines = true)
+    add_node!(t, pretty(ys, cst[3], s), s, join_lines = true)
+    t
+end
+
+function p_typedcomprehension(ys::YASStyle, cst::CSTParser.EXPR, s::State)
+    t = FST(cst, nspaces(s))
+
+    if is_block(cst[3])
+        t.force_nest = true
+    elseif cst[3].typ === CSTParser.Generator && is_block(cst[3][1])
+        t.force_nest = true
+    end
+
+    add_node!(t, pretty(ys, cst[1], s), s, join_lines = true)
+    add_node!(t, pretty(ys, cst[2], s), s, join_lines = true)
+    add_node!(t, pretty(ys, cst[3], s), s, join_lines = true)
+    add_node!(t, pretty(ys, cst[4], s), s, join_lines = true)
+    t
+end
+
+function p_macrocall(ys::YASStyle, cst::CSTParser.EXPR, s::State)
+    fst = p_macrocall(DefaultStyle(ys), cst, s)
+    fst.typ == CSTParser.MacroCall || return fst
+    is_closer(cst.args[end]) || return fst
+
+    # remove initial and last placeholders
+    # @call(PLACEHOLDER, args..., PLACEHOLDER) -> @call(args...)
+    idx = findfirst(n -> n.typ === PLACEHOLDER, fst.nodes)
+    idx !== nothing && (fst[idx] = Whitespace(0))
+    idx = findlast(n -> n.typ === PLACEHOLDER, fst.nodes)
+    idx !== nothing && (fst[idx] = Whitespace(0))
+
+    return fst
+end
 
 
 function p_parameters(ys::YASStyle, cst::CSTParser.EXPR, s::State)
@@ -127,7 +242,8 @@ function p_whereopcall(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     add_braces =
         !CSTParser.is_lbrace(cst[3]) &&
         cst.parent.typ !== CSTParser.Curly &&
-        cst[3].typ !== CSTParser.Curly && cst[3].typ !== CSTParser.BracesCat
+        cst[3].typ !== CSTParser.Curly &&
+        cst[3].typ !== CSTParser.BracesCat
 
     brace = FST(CSTParser.PUNCTUATION, t.endline, t.endline, "{")
     add_braces && add_node!(t, brace, s, join_lines = true)
@@ -136,7 +252,11 @@ function p_whereopcall(ys::YASStyle, cst::CSTParser.EXPR, s::State)
         n = a.typ === CSTParser.BinaryOpCall ? pretty(ys, a, s, nospace = true) :
             pretty(ys, a, s)
 
-        if CSTParser.is_comma(a) && is_closer(cst[i+1])
+        if CSTParser.is_comma(a) && i + 2 == length(cst) - 1
+            continue
+        elseif CSTParser.is_comma(a) && i + 2 < length(cst) && !is_punc(cst[i+3])
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         else
             add_node!(t, n, s, join_lines = true)
         end
@@ -150,17 +270,42 @@ end
 function p_generator(ys::YASStyle, cst::CSTParser.EXPR, s::State)
     t = FST(cst, nspaces(s))
     for (i, a) in enumerate(cst)
-        n = a.typ === CSTParser.BinaryOpCall ? pretty(ys, a, s, nonest = true) :
-            pretty(ys, a, s)
+        # n = a.typ === CSTParser.BinaryOpCall ? pretty(ys, a, s, nonest = true) :
+        #     pretty(ys, a, s)
+        n = pretty(ys, a, s)
         if a.typ === CSTParser.KEYWORD
-            add_node!(t, Whitespace(1), s)
+            incomp = parent_is(
+                a,
+                is_iterable,
+                ignore_typs = (
+                    CSTParser.InvisBrackets,
+                    CSTParser.Generator,
+                    CSTParser.Flatten,
+                    CSTParser.Filter,
+                ),
+            )
+
+            if is_block(cst[i-1])
+                add_node!(t, Newline(), s)
+            elseif incomp
+                add_node!(t, Placeholder(1), s)
+            else
+                add_node!(t, Whitespace(1), s)
+            end
             add_node!(t, n, s, join_lines = true)
-            add_node!(t, Placeholder(1), s)
+            add_node!(t, Whitespace(1), s)
+
             if a.kind === Tokens.FOR
                 for j = i+1:length(cst)
                     eq_to_in_normalization!(cst[j], s.opts.always_for_in)
                 end
             end
+
+            if !is_gen(cst.args[i+1])
+                tup = p_tupleh(ys, cst.args[i+1:length(cst)], s)
+                add_node!(t, tup, s, join_lines = true)
+                return t
+            end
         elseif CSTParser.is_comma(a) && i < length(cst) && !is_punc(cst[i+1])
             add_node!(t, n, s, join_lines = true)
             add_node!(t, Placeholder(1), s)
@@ -168,32 +313,36 @@ function p_generator(ys::YASStyle, cst::CSTParser.EXPR, s::State)
             add_node!(t, n, s, join_lines = true)
         end
     end
+
     t
 end
 @inline p_filter(ys::YASStyle, cst::CSTParser.EXPR, s::State) = p_generator(ys, cst, s)
+@inline p_flatten(ys::YASStyle, cst::CSTParser.EXPR, s::State) = p_generator(ys, cst, s)
 
 #
 # Nesting
 #
 
 function n_call!(ys::YASStyle, fst::FST, s::State)
-    line_offset = s.line_offset
-    fst.indent = line_offset + sum(length.(fst[1:2]))
+    fst.indent = s.line_offset + sum(length.(fst[1:2]))
 
     for (i, n) in enumerate(fst.nodes)
         if n.typ === NEWLINE
             s.line_offset = fst.indent
         elseif n.typ === PLACEHOLDER
-            nest_if_over_margin!(ys, fst, s, i)
+            si = findnext(n -> n.typ === PLACEHOLDER, fst.nodes, i + 1)
+            nest_if_over_margin!(ys, fst, s, i; stop_idx = si)
         elseif n.typ === TRAILINGSEMICOLON
             n.val = ""
             n.len = 0
             nest!(ys, n, s)
-        elseif n.typ === CSTParser.Parameters || n.typ === CSTParser.Generator
+        elseif is_gen(n)
             n.indent = fst.indent
             n.extra_margin = 1
             nest!(ys, n, s)
         else
+            diff = fst.indent - fst[i].indent
+            add_indent!(n, s, diff)
             n.extra_margin = 1
             nest!(ys, n, s)
         end
@@ -201,26 +350,31 @@ function n_call!(ys::YASStyle, fst::FST, s::State)
 end
 @inline n_curly!(ys::YASStyle, fst::FST, s::State) = n_call!(ys, fst, s)
 @inline n_ref!(ys::YASStyle, fst::FST, s::State) = n_call!(ys, fst, s)
+@inline n_macrocall!(ys::YASStyle, fst::FST, s::State) = n_call!(ys, fst, s)
+@inline n_typedcomprehension!(ys::YASStyle, fst::FST, s::State) = n_call!(ys, fst, s)
 
 function n_tupleh!(ys::YASStyle, fst::FST, s::State)
-    line_offset = s.line_offset
-    fst.typ !== CSTParser.Parameters && (fst.indent = line_offset)
+    # @info "" fst.typ fst.indent s.line_offset
+    fst.indent = s.line_offset
     length(fst.nodes) > 0 && is_opener(fst[1]) && (fst.indent += 1)
 
     for (i, n) in enumerate(fst.nodes)
         if n.typ === NEWLINE
             s.line_offset = fst.indent
         elseif n.typ === PLACEHOLDER
-            nest_if_over_margin!(ys, fst, s, i)
+            si = findnext(n -> n.typ === PLACEHOLDER, fst.nodes, i + 1)
+            nest_if_over_margin!(ys, fst, s, i; stop_idx = si)
         elseif n.typ === TRAILINGSEMICOLON
             n.val = ""
             n.len = 0
             nest!(ys, n, s)
-        elseif n.typ === CSTParser.Generator
+        elseif is_gen(n)
             n.indent = fst.indent
             n.extra_margin = 1
             nest!(ys, n, s)
         else
+            diff = fst.indent - fst[i].indent
+            add_indent!(n, s, diff)
             n.extra_margin = 1
             nest!(ys, n, s)
         end
@@ -232,11 +386,69 @@ end
 @inline n_invisbrackets!(ys::YASStyle, fst::FST, s::State) = n_tupleh!(ys, fst, s)
 @inline n_comprehension!(ys::YASStyle, fst::FST, s::State) = n_tupleh!(ys, fst, s)
 
-function n_import!(ys::YASStyle, fst::FST, s::State)
-    fst.indent = s.line_offset + sum(length.(fst[1:2]))
+function n_generator!(ys::YASStyle, fst::FST, s::State)
+    diff = s.line_offset - fst[1].indent
+
+    # if the first argument is not a leaf
+    # aligns it to be inside the generator
+    # expression
+    add_indent!(fst[1], s, diff)
+
+    for (i, n) in enumerate(fst.nodes)
+        if n.typ === NEWLINE
+            s.line_offset = fst.indent
+        elseif n.typ === PLACEHOLDER
+            si = findnext(n -> n.typ === PLACEHOLDER, fst.nodes, i + 1)
+            nest_if_over_margin!(ys, fst, s, i; stop_idx = si)
+        elseif is_gen(n)
+            n.indent = fst.indent
+            n.extra_margin = 1
+            nest!(ys, n, s)
+        else
+            n.extra_margin = 1
+            nest!(ys, n, s)
+        end
+    end
+end
+@inline n_filter!(ys::YASStyle, fst::FST, s::State) = n_generator!(ys, fst, s)
+@inline n_flatten!(ys::YASStyle, fst::FST, s::State) = n_generator!(ys, fst, s)
+
+function n_whereopcall!(ys::YASStyle, fst::FST, s::State)
+    fst.indent = s.line_offset
+    # after "A where "
+    Blen = sum(length.(fst[2:end]))
+    fst[1].extra_margin = Blen + fst.extra_margin
+
+    for (i, n) in enumerate(fst.nodes)
+        if n.typ === NEWLINE
+            s.line_offset = fst.indent
+        elseif n.typ === PLACEHOLDER
+            si = findnext(n -> n.typ === PLACEHOLDER, fst.nodes, i + 1)
+            nest_if_over_margin!(ys, fst, s, i; stop_idx = si)
+        elseif is_opener(n) && n.val == "{"
+            fst.indent = s.line_offset + 1
+            nest!(ys, n, s)
+        elseif i == 1
+            nest!(ys, n, s)
+        else
+            n.extra_margin = 1
+            nest!(ys, n, s)
+        end
+    end
+end
+
+function n_using!(ys::YASStyle, fst::FST, s::State)
+    idx = findfirst(n -> n.val == ":", fst.nodes)
+    fst.indent = s.line_offset
+    if idx === nothing
+        fst.indent += sum(length.(fst[1:2]))
+    else
+        fst.indent += sum(length.(fst[1:idx+1]))
+    end
     for (i, n) in enumerate(fst.nodes)
         if n.typ === PLACEHOLDER
-            nest_if_over_margin!(ys, fst, s, i)
+            si = findnext(n -> n.typ === PLACEHOLDER, fst.nodes, i + 1)
+            nest_if_over_margin!(ys, fst, s, i; stop_idx = si)
         elseif n.typ === NEWLINE
             s.line_offset = fst.indent
         else
@@ -244,10 +456,28 @@ function n_import!(ys::YASStyle, fst::FST, s::State)
         end
     end
 end
-@inline n_export!(ys::YASStyle, fst::FST, s::State) = n_import!(ys, fst, s)
-@inline n_using!(ys::YASStyle, fst::FST, s::State) = n_import!(ys, fst, s)
+@inline n_export!(ys::YASStyle, fst::FST, s::State) = n_using!(ys, fst, s)
+@inline n_import!(ys::YASStyle, fst::FST, s::State) = n_using!(ys, fst, s)
 
-n_whereopcall!(ys::YASStyle, fst::FST, s::State) =
-    nest!(ys, fst.nodes, s, fst.indent, extra_margin = fst.extra_margin)
 n_chainopcall!(ys::YASStyle, fst::FST, s::State) =
-    n_block!(DefaultStyle(ys), fst, s, custom_indent = s.line_offset)
+    n_block!(DefaultStyle(ys), fst, s, indent = s.line_offset)
+n_comparison!(ys::YASStyle, fst::FST, s::State) =
+    n_block!(DefaultStyle(ys), fst, s, indent = s.line_offset)
+
+function n_binaryopcall!(ys::YASStyle, fst::FST, s::State)
+    idx = findfirst(n -> n.typ === PLACEHOLDER, fst.nodes)
+
+    if idx !== nothing
+        n_binaryopcall!(DefaultStyle(ys), fst, s)
+        return
+    end
+
+    # line_offset = s.line_offset
+    walk(reset_line_offset!, fst.nodes[1:end-1], s, fst.indent)
+    nest!(ys, fst[end], s)
+    # s.line_offset = line_offset
+    # nest!(ys, fst[1], s)
+    # s.line_offset = line_offset
+    # walk(reset_line_offset!, fst, s)
+
+end


Test patch:
diff --git a/test/files/Docs.jl b/test/files/Docs.jl
index 24e7b58f..e9d74d4b 100644
--- a/test/files/Docs.jl
+++ b/test/files/Docs.jl
@@ -220,8 +220,8 @@ function doc!(__module__::Module, b::Binding, str::DocStr, @nospecialize sig = U
         # We allow for docstrings to be updated, but print a warning since it is possible
         # that over-writing a docstring *may* have been accidental.  The warning
         # is suppressed for symbols in Main, for interactive use (#23011).
-        __module__ ==
-        Main || @warn "Replacing docs for `$b :: $sig` in module `$(__module__)`"
+        __module__ == Main ||
+        @warn "Replacing docs for `$b :: $sig` in module `$(__module__)`"
     else
         # The ordering of docstrings for each Binding is defined by the order in which they
         # are initially added. Replacing a specific docstring does not change it's ordering.
@@ -518,12 +518,14 @@ const BINDING_HEADS = [:const, :global, :(=)]
 # For the special `:@mac` / `:(Base.@mac)` syntax for documenting a macro after definition.
 isquotedmacrocall(@nospecialize x) =
     isexpr(x, :copyast, 1) &&
-    isa(x.args[1], QuoteNode) && isexpr(x.args[1].value, :macrocall, 2)
+    isa(x.args[1], QuoteNode) &&
+    isexpr(x.args[1].value, :macrocall, 2)
 # Simple expressions / atoms the may be documented.
 isbasicdoc(@nospecialize x) = isexpr(x, :.) || isa(x, Union{QuoteNode,Symbol})
 is_signature(@nospecialize x) =
     isexpr(x, :call) ||
-    (isexpr(x, :(::), 2) && isexpr(x.args[1], :call)) || isexpr(x, :where)
+    (isexpr(x, :(::), 2) && isexpr(x.args[1], :call)) ||
+    isexpr(x, :where)
 
 function docm(source::LineNumberNode, mod::Module, ex)
     @nospecialize ex
diff --git a/test/files/reverse.jl b/test/files/reverse.jl
index 819217d8..46976f23 100644
--- a/test/files/reverse.jl
+++ b/test/files/reverse.jl
@@ -65,7 +65,8 @@ end
 
 is_literal_getindex(ex) =
     iscall(ex, Base, :getindex) &&
-    length(ex.args) == 3 && ex.args[3] isa Union{Integer,QuoteNode}
+    length(ex.args) == 3 &&
+    ex.args[3] isa Union{Integer,QuoteNode}
 
 function instrument_getindex!(ir, v, ex)
     is_literal_getindex(ex) ?
@@ -75,7 +76,8 @@ end
 
 is_literal_iterate(ex) =
     iscall(ex, Base, :indexed_iterate) &&
-    length(ex.args) >= 3 && ex.args[3] isa Union{Integer,QuoteNode}
+    length(ex.args) >= 3 &&
+    ex.args[3] isa Union{Integer,QuoteNode}
 
 function instrument_iterate!(ir, v, ex)
     is_literal_iterate(ex) ?
diff --git a/test/runtests.jl b/test/runtests.jl
index 91cf8877..13e206f4 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,77 +1,43 @@
 using JuliaFormatter
-using JuliaFormatter: DefaultStyle
+using JuliaFormatter: DefaultStyle, YASStyle, Options
 using CSTParser
 using Test
 
-fmt1(
-    s,
-    i = 4,
-    m = 80,
-    always_for_in = false,
-    whitespace_typedefs = false,
-    whitespace_ops_in_indices = false,
-    remove_extra_newlines = false,
-) = JuliaFormatter.format_text(
-    s,
-    indent = i,
-    margin = m,
-    always_for_in = always_for_in,
-    whitespace_typedefs = whitespace_typedefs,
-    whitespace_ops_in_indices = whitespace_ops_in_indices,
-    remove_extra_newlines = remove_extra_newlines,
-)
+fmt1(s; i = 4, m = 80, kwargs...) =
+    JuliaFormatter.format_text(s; kwargs..., indent = i, margin = m)
+fmt1(s, i, m; kwargs...) = fmt1(s; kwargs..., i = i, m = m)
 
 # Verifies formatting the formatted text
 # results in the same output
-function fmt(
-    s;
-    i = 4,
-    m = 80,
-    always_for_in = false,
-    whitespace_typedefs = false,
-    whitespace_ops_in_indices = false,
-    remove_extra_newlines = false,
-)
-    s1 = fmt1(
-        s,
-        i,
-        m,
-        always_for_in,
-        whitespace_typedefs,
-        whitespace_ops_in_indices,
-        remove_extra_newlines,
-    )
-    fmt1(
-        s1,
-        i,
-        m,
-        always_for_in,
-        whitespace_typedefs,
-        whitespace_ops_in_indices,
-        remove_extra_newlines,
-    )
+function fmt(s; i = 4, m = 80, kwargs...)
+    s1 = fmt1(s; kwargs..., i = i, m = m)
+    return fmt1(s1; kwargs..., i = i, m = m)
 end
-fmt(s, i, m) = fmt(s; i = i, m = m)
-fmt1(s, i, m) = fmt1(s, i, m, false, false, false)
+fmt(s, i, m; kwargs...) = fmt(s; kwargs..., i = i, m = m)
 
-function run_pretty(text::String, print_width::Int; style = DefaultStyle())
+function run_pretty(
+    text::String,
+    print_width::Int;
+    opts = Options(),
+    style = DefaultStyle(),
+)
     d = JuliaFormatter.Document(text)
-    s = JuliaFormatter.State(d, 4, print_width, JuliaFormatter.Options())
+    s = JuliaFormatter.State(d, 4, print_width, opts)
     x = CSTParser.parse(text, true)
     t = JuliaFormatter.pretty(style, x, s)
     t
 end
 
-function run_nest(text::String, print_width::Int; style = DefaultStyle())
+function run_nest(text::String, print_width::Int; opts = Options(), style = DefaultStyle())
     d = JuliaFormatter.Document(text)
-    s = JuliaFormatter.State(d, 4, print_width, JuliaFormatter.Options())
+    s = JuliaFormatter.State(d, 4, print_width, opts)
     x = CSTParser.parse(text, true)
     t = JuliaFormatter.pretty(style, x, s)
     JuliaFormatter.nest!(style, t, s)
-    s
+    t, s
 end
 
-@testset "All" begin
+@testset "Default" begin
 
     @testset "basic" begin
         @test fmt("") == ""
@@ -337,55 +303,6 @@ end
         @test fmt(str_) == str
     end
 
-    @testset "always eq to in" begin
-        str_ = """
-        for i = 1:n
-            println(i)
-        end"""
-        str = """
-        for i in 1:n
-            println(i)
-        end"""
-        @test fmt(str_, always_for_in = true) == str
-        @test fmt(str, always_for_in = true) == str
-
-        str_ = """
-        for i = I1, j in I2
-            println(i, j)
-        end"""
-        str = """
-        for i in I1, j in I2
-            println(i, j)
-        end"""
-        @test fmt(str_, always_for_in = true) == str
-        @test fmt(str, always_for_in = true) == str
-
-        str_ = """
-        for i = 1:30, j = 100:-2:1
-            println(i, j)
-        end"""
-        str = """
-        for i in 1:30, j in 100:-2:1
-            println(i, j)
-        end"""
-        @test fmt(str_, always_for_in = true) == str
-        @test fmt(str, always_for_in = true) == str
-
-        str_ = "[(i,j) for i=I1,j=I2]"
-        str = "[(i, j) for i in I1, j in I2]"
-        @test fmt(str_, always_for_in = true) == str
-        @test fmt(str, always_for_in = true) == str
-
-        str_ = "((i,j) for i=I1,j=I2)"
-        str = "((i, j) for i in I1, j in I2)"
-        @test fmt(str_, always_for_in = true) == str
-        @test fmt(str, always_for_in = true) == str
-
-        str_ = "[(i, j) for i = 1:2:10, j = 100:-1:10]"
-        str = "[(i, j) for i in 1:2:10, j in 100:-1:10]"
-        @test fmt(str_, always_for_in = true) == str
-        @test fmt(str, always_for_in = true) == str
-    end
 
     @testset "tuples" begin
         @test fmt("(a,)") == "(a,)"
@@ -961,6 +878,16 @@ end
 
         str = """@warn("Text")"""
         @test fmt(str) == str
+
+        str_ = "@Module.macro"
+        str = "Module.@macro"
+        @test fmt(str_) == str
+        @test fmt(str) == str
+
+        str_ = "\$Module.@macro"
+        str = "\$Module.@macro"
+        @test fmt(str_) == str
+        @test fmt(str) == str
     end
 
     @testset "macro block" begin
@@ -976,6 +903,12 @@ end
         end"""
         t = run_pretty(str, 80)
         @test length(t) == 41
+
+        str_ = "__module__ == Main || @warn \"Replacing docs for `\$b :: \$sig` in module `\$(__module__)`\""
+        str = """
+        __module__ == Main ||
+        @warn \"Replacing docs for `\$b :: \$sig` in module `\$(__module__)`\""""
+        @test fmt(str_, 4, length(str_) - 1) == str
     end
 
     @testset "begin" begin
@@ -1324,37 +1257,57 @@ end
     @testset "structs" begin
         str = """
         struct name
-            arg
+            arg::Any
         end"""
-        @test fmt("""
+
+        str_ = """
         struct name
             arg
-        end""") == str
-        @test fmt("""
+        end"""
+        @test fmt(str_) == str
+
+        str_ = """
         struct name
         arg
-        end""") == str
-        @test fmt("""
+        end"""
+        @test fmt(str_) == str
+
+        str_ = """
         struct name
                 arg
-            end""") == str
+            end"""
+        @test fmt(str_) == str
+
+        t = run_pretty(str_, 80)
+        @test length(t) == 12
 
         str = """
         mutable struct name
-            arg
+            reallylongfieldname::Any
         end"""
-        @test fmt("""
+
+        str_ = """
         mutable struct name
-            arg
-        end""") == str
-        @test fmt("""
+            reallylongfieldname
+        end"""
+        @test fmt(str_) == str
+
+        str_ = """
         mutable struct name
-        arg
-        end""") == str
-        @test fmt("""
+        reallylongfieldname
+        end"""
+        @test fmt(str_) == str
+
+        str_ = """
         mutable struct name
-                arg
-            end""") == str
+                reallylongfieldname
+            end"""
+        @test fmt(str_) == str
+
+        t = run_pretty(str_, 80)
+        @test length(t) == 28
+
+
     end
 
     @testset "try" begin
@@ -2432,7 +2385,7 @@ end
 
         str = """
         struct Foo
-            body
+            body::Any
         end"""
         @test fmt("struct Foo\n    body  end") == str
 
@@ -2453,7 +2406,7 @@ end
 
         str = """
         mutable struct Foo
-            body
+            body::Any
         end"""
         @test fmt("mutable struct Foo\n    body  end") == str
 
@@ -2670,6 +2623,35 @@ end
             baz"""
         @test fmt(str_, 4, 11) == str
 
+        str_ = """
+        using A,
+
+        B, C"""
+        str = "using A, B, C"
+        @test fmt(str_) == str
+
+        str_ = """
+        using A,
+                  # comment
+        B, C"""
+        str = """
+        using A,
+          # comment
+          B,
+          C"""
+        @test fmt(str_, 2, 80) == str
+
+        str_ = """
+        using A,  #inline
+                  # comment
+        B, C#inline"""
+        str = """
+        using A,  #inline
+          # comment
+          B,
+          C#inline"""
+        @test fmt(str_, 2, 80) == str
+
         str = """
         @somemacro function (fcall_ | fcall_)
             body_
@@ -2993,98 +2975,98 @@ end
 
     @testset "nesting line offset" begin
         str = "a - b + c * d"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 5
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 1
 
         str = "c ? e1 : e2"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 2
-        s = run_nest(str, 8)
+        _, s = run_nest(str, 8)
         @test s.line_offset == 2
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 2
 
         str = "c1 ? e1 : c2 ? e2 : c3 ? e3 : c4 ? e4 : e5"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 32
-        s = run_nest(str, 30)
+        _, s = run_nest(str, 30)
         @test s.line_offset == 22
-        s = run_nest(str, 20)
+        _, s = run_nest(str, 20)
         @test s.line_offset == 12
-        s = run_nest(str, 10)
+        _, s = run_nest(str, 10)
         @test s.line_offset == 2
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 2
 
         str = "f(a, b, c) where {A,B,C}"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 15
-        s = run_nest(str, 14)
+        _, s = run_nest(str, 14)
         @test s.line_offset == 1
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 1
 
         str = "f(a, b, c) where Union{A,B,C}"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 20
-        s = run_nest(str, 19)
+        _, s = run_nest(str, 19)
         @test s.line_offset == 1
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 1
 
         str = "f(a, b, c) where {A}"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         # adds surrounding {...} after `where`
         @test s.line_offset == length(str)
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 1
 
         str = "f(a, b, c) where {A<:S}"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 14
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 1
 
         str = "f(a, b, c) where Union{A,B,Union{C,D,E}}"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 31
-        s = run_nest(str, 30)
+        _, s = run_nest(str, 30)
         @test s.line_offset == 1
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 1
 
         str = "f(a, b, c) where {A,{B, C, D},E}"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 1
 
         str = "(a, b, c, d)"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 1
 
         str = "a, b, c, d"
-        s = run_nest(str, 100)
+        _, s = run_nest(str, 100)
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 1
 
         str = """
@@ -3094,21 +3076,21 @@ end
                 name_::T_ => (name, T)
                 x_ => (x, :Any)
             end"""
-        s = run_nest(str, 96)
+        _, s = run_nest(str, 96)
         @test s.line_offset == 3
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 7
 
         str = "prettify(ex; lines = false) = ex |> (lines ? identity : striplines) |> flatten |> unresolve |> resyntax |> alias_gensyms"
-        s = run_nest(str, 80)
+        _, s = run_nest(str, 80)
         @test s.line_offset == 17
 
         str = "foo() = a + b"
-        s = run_nest(str, length(str))
+        _, s = run_nest(str, length(str))
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 9
-        s = run_nest(str, 1)
+        _, s = run_nest(str, 1)
         @test s.line_offset == 5
 
         str_ = """
@@ -3145,30 +3127,30 @@ end
 
 
         str = "export @esc, isexpr, isline, iscall, rmlines, unblock, block, inexpr, namify, isdef"
-        s = run_nest(str, length(str))
+        _, s = run_nest(str, length(str))
         @test s.line_offset == length(str)
-        s = run_nest(str, length(str) - 1)
+        _, s = run_nest(str, length(str) - 1)
         @test s.line_offset == 74
-        s = run_nest(str, 73)
+        _, s = run_nest(str, 73)
         @test s.line_offset == 9
 
         # https://github.com/domluna/JuliaFormatter.jl/issues/9#issuecomment-481607068
         str = """this_is_a_long_variable_name = Dict{Symbol,Any}(:numberofpointattributes => NAttributes,
                :numberofpointmtrs => NMTr, :numberofcorners => NSimplex, :firstnumber => Cint(1),
                :mesh_dim => Cint(3),)"""
-        s = run_nest(str, 80)
+        _, s = run_nest(str, 80)
         @test s.line_offset == 1
 
         str = """this_is_a_long_variable_name = (:numberofpointattributes => NAttributes,
                :numberofpointmtrs => NMTr, :numberofcorners => NSimplex, :firstnumber => Cint(1),
                :mesh_dim => Cint(3),)"""
-        s = run_nest(str, 80)
+        _, s = run_nest(str, 80)
         @test s.line_offset == 1
 
         str = "import A: foo, bar, baz"
-        s = run_nest(str, 22)
+        _, s = run_nest(str, 22)
         @test s.line_offset == 17
-        s = run_nest(str, 16)
+        _, s = run_nest(str, 16)
         @test s.line_offset == 7
 
     end
@@ -4096,7 +4078,7 @@ some_function(
             end for i = 1:1
         ]"""
         @test fmt(str_) == str
-        s = run_nest(str_, 100)
+        _, s = run_nest(str_, 100)
         @test s.line_offset == 1
 
         str_ = """
@@ -4110,7 +4092,7 @@ some_function(
             end for i = 1:1
         ]"""
         @test fmt(str_) == str
-        s = run_nest(str_, 100)
+        _, s = run_nest(str_, 100)
         @test s.line_offset == 1
 
         str_ = """
@@ -4124,7 +4106,7 @@ some_function(
             end for i = 1:1
         ]"""
         @test fmt(str_) == str
-        s = run_nest(str_, 100)
+        _, s = run_nest(str_, 100)
         @test s.line_offset == 1
 
     end
@@ -4211,71 +4193,6 @@ some_function(
 
     end
 
-    @testset "whitespace typedefs option" begin
-        str_ = "Foo{A,B,C}"
-        str = "Foo{A, B, C}"
-        @test fmt(str_, whitespace_typedefs = true) == str
-
-        str_ = """
-        struct Foo{A<:Bar,Union{B<:Fizz,C<:Buzz},<:Any}
-            a::A
-        end"""
-        str = """
-        struct Foo{A <: Bar, Union{B <: Fizz, C <: Buzz}, <:Any}
-            a::A
-        end"""
-        @test fmt(str_, whitespace_typedefs = true) == str
-
-        str_ = """
-        function foo() where {A,B,C{D,E,F{G,H,I},J,K},L,M<:N,Y>:Z}
-            body
-        end
-        """
-        str = """
-        function foo() where {A, B, C{D, E, F{G, H, I}, J, K}, L, M <: N, Y >: Z}
-            body
-        end
-        """
-        @test fmt(str_, whitespace_typedefs = true) == str
-
-        str_ = "foo() where {A,B,C{D,E,F{G,H,I},J,K},L,M<:N,Y>:Z} = body"
-        str = "foo() where {A, B, C{D, E, F{G, H, I}, J, K}, L, M <: N, Y >: Z} = body"
-        @test fmt(str_, whitespace_typedefs = true) == str
-    end
-
-    @testset "whitespace ops in indices option" begin
-        str = "arr[1 + 2]"
-        @test fmt("arr[1+2]", m = 1, whitespace_ops_in_indices = true) == str
-
-        str = "arr[(1 + 2)]"
-        @test fmt("arr[(1+2)]", m = 1, whitespace_ops_in_indices = true) == str
-
-        str_ = "arr[1:2*num_source*num_dump-1]"
-        str = "arr[1:(2 * num_source * num_dump - 1)]"
-        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
-
-        str_ = "arr[2*num_source*num_dump-1:1]"
-        str = "arr[(2 * num_source * num_dump - 1):1]"
-        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
-
-        str = "arr[(a + b):c]"
-        @test fmt("arr[(a+b):c]", m = 1, whitespace_ops_in_indices = true) == str
-
-        str = "arr[a in b]"
-        @test fmt(str, m = 1, whitespace_ops_in_indices = true) == str
-
-        str_ = "a:b+c:d-e"
-        str = "a:(b + c):(d - e)"
-        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
-
-        # issue 180
-        str_ = "s[m+i+1]"
-        str = "s[m+i+1]"
-        @test fmt(str, m = 1) == str
-
-        str = "s[m + i + 1]"
-        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
-    end
 
     @testset "BracesCat / Issue 150" begin
         str_ = "const SymReg{B,MT} = ArrayReg{B,Basic,MT} where {MT <:AbstractMatrix{Basic}}"
@@ -4744,4 +4661,699 @@ some_function(
 
     end
 
+
+end
+
+@testset "Format Options" begin
+    @testset "whitespace in typedefs" begin
+        str_ = "Foo{A,B,C}"
+        str = "Foo{A, B, C}"
+        @test fmt(str_, whitespace_typedefs = true) == str
+
+        str_ = """
+        struct Foo{A<:Bar,Union{B<:Fizz,C<:Buzz},<:Any}
+            a::A
+        end"""
+        str = """
+        struct Foo{A <: Bar, Union{B <: Fizz, C <: Buzz}, <:Any}
+            a::A
+        end"""
+        @test fmt(str_, whitespace_typedefs = true) == str
+
+        str_ = """
+        function foo() where {A,B,C{D,E,F{G,H,I},J,K},L,M<:N,Y>:Z}
+            body
+        end
+        """
+        str = """
+        function foo() where {A, B, C{D, E, F{G, H, I}, J, K}, L, M <: N, Y >: Z}
+            body
+        end
+        """
+        @test fmt(str_, whitespace_typedefs = true) == str
+
+        str_ = "foo() where {A,B,C{D,E,F{G,H,I},J,K},L,M<:N,Y>:Z} = body"
+        str = "foo() where {A, B, C{D, E, F{G, H, I}, J, K}, L, M <: N, Y >: Z} = body"
+        @test fmt(str_, whitespace_typedefs = true) == str
+    end
+
+    @testset "whitespace ops in indices" begin
+        str = "arr[1 + 2]"
+        @test fmt("arr[1+2]", m = 1, whitespace_ops_in_indices = true) == str
+
+        str = "arr[(1 + 2)]"
+        @test fmt("arr[(1+2)]", m = 1, whitespace_ops_in_indices = true) == str
+
+        str_ = "arr[1:2*num_source*num_dump-1]"
+        str = "arr[1:(2 * num_source * num_dump - 1)]"
+        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
+
+        str_ = "arr[2*num_source*num_dump-1:1]"
+        str = "arr[(2 * num_source * num_dump - 1):1]"
+        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
+
+        str = "arr[(a + b):c]"
+        @test fmt("arr[(a+b):c]", m = 1, whitespace_ops_in_indices = true) == str
+
+        str = "arr[a in b]"
+        @test fmt(str, m = 1, whitespace_ops_in_indices = true) == str
+
+        str_ = "a:b+c:d-e"
+        str = "a:(b + c):(d - e)"
+        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
+
+        # issue 180
+        str_ = "s[m+i+1]"
+        str = "s[m+i+1]"
+        @test fmt(str, m = 1) == str
+
+        str = "s[m + i + 1]"
+        @test fmt(str_, m = 1, whitespace_ops_in_indices = true) == str
+    end
+
+    @testset "rewrite import to using" begin
+        str_ = "import A"
+        str = "using A: A"
+        @test fmt(str_, import_to_using = true) == str
+
+        str_ = """
+        import A,
+
+        B, C"""
+        str = """
+        using A: A
+        using B: B
+        using C: C"""
+        @test_broken fmt(str_, import_to_using = true) == str
+
+        str_ = """
+        import A,
+               # comment
+        B, C"""
+        str = """
+        using A: A
+        # comment
+        using B: B
+        using C: C"""
+        @test fmt(str_, import_to_using = true) == str
+
+        str_ = """
+        import A, # inline
+               # comment
+        B, C # inline"""
+        str = """
+        using A: A # inline
+        # comment
+        using B: B
+        using C: C # inline"""
+        @test fmt(str_, import_to_using = true) == str
+
+        str_ = """
+        import ..A, .B, ...C"""
+        str = """
+        using ..A: A
+        using .B: B
+        using ...C: C"""
+        @test fmt(str_, import_to_using = true) == str
+        t = run_pretty(str_, 80, opts = Options(import_to_using = true))
+        @test t.len == 13
+    end
+
+    @testset "always convert `=` to `in` (for loops)" begin
+        str_ = """
+        for i = 1:n
+            println(i)
+        end"""
+        str = """
+        for i in 1:n
+            println(i)
+        end"""
+        @test fmt(str_, always_for_in = true) == str
+        @test fmt(str, always_for_in = true) == str
+
+        str_ = """
+        for i = I1, j in I2
+            println(i, j)
+        end"""
+        str = """
+        for i in I1, j in I2
+            println(i, j)
+        end"""
+        @test fmt(str_, always_for_in = true) == str
+        @test fmt(str, always_for_in = true) == str
+
+        str_ = """
+        for i = 1:30, j = 100:-2:1
+            println(i, j)
+        end"""
+        str = """
+        for i in 1:30, j in 100:-2:1
+            println(i, j)
+        end"""
+        @test fmt(str_, always_for_in = true) == str
+        @test fmt(str, always_for_in = true) == str
+
+        str_ = "[(i,j) for i=I1,j=I2]"
+        str = "[(i, j) for i in I1, j in I2]"
+        @test fmt(str_, always_for_in = true) == str
+        @test fmt(str, always_for_in = true) == str
+
+        str_ = "((i,j) for i=I1,j=I2)"
+        str = "((i, j) for i in I1, j in I2)"
+        @test fmt(str_, always_for_in = true) == str
+        @test fmt(str, always_for_in = true) == str
+
+        str_ = "[(i, j) for i = 1:2:10, j = 100:-1:10]"
+        str = "[(i, j) for i in 1:2:10, j in 100:-1:10]"
+        @test fmt(str_, always_for_in = true) == str
+        @test fmt(str, always_for_in = true) == str
+    end
+
+    @testset "rewrite x |> f to f(x)" begin
+        @test fmt("x |> f", pipe_to_function_call = true) == "f(x)"
+
+        str_ = "var = func1(arg1) |> func2 |> func3 |> func4 |> func5"
+        str = "var = func5(func4(func3(func2(func1(arg1)))))"
+        @test fmt(str_, pipe_to_function_call = true) == str
+        @test fmt(str_, pipe_to_function_call = true, margin = 1) == fmt(str)
+    end
+
+    @testset "function shortdef to longdef" begin
+        str_ = "foo(a) = bodybodybody"
+        str = """
+        function foo(a)
+            bodybodybody
+        end"""
+        @test fmt(str_, 4, length(str_), short_to_long_function_def = true) == str_
+        @test fmt(str_, 4, length(str_) - 1, short_to_long_function_def = true) == str
+
+        str_ = "foo(a::T) where {T} = bodybodybodybodybodybodyb"
+        str = """
+        function foo(a::T) where {T}
+            bodybodybodybodybodybodyb
+        end"""
+        @test fmt(str_, 4, length(str_), short_to_long_function_def = true) == str_
+        @test fmt(str_, 4, length(str_) - 1, short_to_long_function_def = true) == str
+
+        str_ = "foo(a::T)::R where {T} = bodybodybodybodybodybodybody"
+        str = """
+        function foo(a::T)::R where {T}
+            bodybodybodybodybodybodybody
+        end"""
+        @test fmt(str_, 4, length(str_), short_to_long_function_def = true) == str_
+        @test fmt(str_, 4, length(str_) - 1, short_to_long_function_def = true) == str
+    end
+
+    @testset "always use return" begin
+        str_ = "foo(a) = bodybodybody"
+        str = """
+        function foo(a)
+            return bodybodybody
+        end"""
+        @test fmt(
+            str_,
+            4,
+            length(str_) - 1,
+            short_to_long_function_def = true,
+            always_use_return = true,
+        ) == str
+
+        str_ = """
+        function foo()
+            expr1
+            expr2
+        end"""
+        str = """
+        function foo()
+            expr1
+            return expr2
+        end"""
+        @test fmt(str_, 4, length(str_) - 1, always_use_return = true) == str
+
+        str_ = """
+        macro foo()
+            expr1
+            expr2
+        end"""
+        str = """
+        macro foo()
+            expr1
+            return expr2
+        end"""
+        @test fmt(str_, 4, length(str_) - 1, always_use_return = true) == str
+
+        str_ = """
+        map(arg1, arg2) do x, y
+            expr1
+            expr2
+        end"""
+        str = """
+        map(arg1, arg2) do x, y
+            expr1
+            return expr2
+        end"""
+        @test fmt(str_, 4, length(str_) - 1, always_use_return = true) == str
+    end
+
+end
+
+yasfmt1(s, i, m; kwargs...) = fmt1(s; kwargs..., i = i, m = m, style = YASStyle())
+yasfmt(s, i, m; kwargs...) = fmt(s; kwargs..., i = i, m = m, style = YASStyle())
+
+@testset "YAS style" begin
+    @testset "basic" begin
+        str_ = "foo(; k =v)"
+        str = "foo(; k=v)"
+        @test yasfmt(str_, 4, 80) == str
+
+        str_ = "( k1 =v1,  k2=v2)"
+        str = "(k1=v1, k2=v2)"
+        @test yasfmt(str_, 4, 80) == str
+
+        str_ = "a = (arg1, arg2, arg3)"
+        str = """
+        a = (arg1, arg2,
+             arg3)"""
+        @test yasfmt(str_, 4, length(str_) - 1) == str
+        @test yasfmt(str_, 4, 16) == str
+
+        str = """
+        a = (arg1,
+             arg2,
+             arg3)"""
+        @test yasfmt(str_, 4, 15) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = "a = [arg1, arg2, arg3]"
+        str = """
+        a = [arg1, arg2,
+             arg3]"""
+        @test yasfmt(str_, 4, length(str_) - 1) == str
+        @test yasfmt(str_, 4, 16) == str
+
+        str = """
+        a = [arg1,
+             arg2,
+             arg3]"""
+        @test yasfmt(str_, 4, 15) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = "a = {arg1, arg2, arg3}"
+        str = """
+        a = {arg1,arg2,arg3}"""
+        @test yasfmt(str_, 4, 20) == str
+
+        str = """
+        a = {arg1,arg2,
+             arg3}"""
+        @test yasfmt(str_, 4, 19) == str
+        @test yasfmt(str_, 4, 15) == str
+
+        str = """
+        a = {arg1,
+             arg2,
+             arg3}"""
+        @test yasfmt(str_, 4, 14) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = "a = Union{arg1, arg2, arg3}"
+        str = """
+        a = Union{arg1,arg2,arg3}"""
+        @test yasfmt(str_, 4, 25) == str
+
+        str = """
+        a = Union{arg1,arg2,
+                  arg3}"""
+        @test yasfmt(str_, 4, 24) == str
+        @test yasfmt(str_, 4, 20) == str
+
+        str = """
+        a = Union{arg1,
+                  arg2,
+                  arg3}"""
+        @test yasfmt(str_, 4, 19) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = "a = fcall(arg1,arg2,arg3)"
+        str = """
+        a = fcall(arg1, arg2, arg3)"""
+        @test yasfmt(str_, 4, length(str)) == str
+
+        str = """
+        a = fcall(arg1, arg2,
+                  arg3)"""
+        @test yasfmt(str_, 4, 26) == str
+        @test yasfmt(str_, 4, 21) == str
+
+        str = """
+        a = fcall(arg1,
+                  arg2,
+                  arg3)"""
+        @test yasfmt(str_, 4, 20) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = "a = @call(arg1,arg2,arg3)"
+        str = """
+        a = @call(arg1, arg2, arg3)"""
+        @test yasfmt(str_, 4, length(str)) == str
+
+        str = """
+        a = @call(arg1, arg2,
+                  arg3)"""
+        @test yasfmt(str_, 4, 26) == str
+        @test yasfmt(str_, 4, 21) == str
+
+        str = """
+        a = @call(arg1,
+                  arg2,
+                  arg3)"""
+        @test yasfmt(str_, 4, 20) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = "a = array[arg1,arg2,arg3]"
+        str = """
+        a = array[arg1, arg2, arg3]"""
+        @test yasfmt(str_, 4, length(str)) == str
+
+        str = """
+        a = array[arg1, arg2,
+                  arg3]"""
+        @test yasfmt(str_, 4, 26) == str
+        @test yasfmt(str_, 4, 21) == str
+
+        str = """
+        a = array[arg1,
+                  arg2,
+                  arg3]"""
+        @test yasfmt(str_, 4, 20) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = """
+        using Cassette: A, B, C"""
+        str = """
+        using Cassette: A,
+                        B,
+                        C"""
+        @test yasfmt(str_, 4, 1) == str
+    end
+
+    # more complicated samples
+    @testset "pretty" begin
+        str_ = "comp = [a * b for a in 1:10, b in 11:20]"
+        str = """
+        comp = [a * b
+                for a in 1:10, b in 11:20]"""
+        @test yasfmt(str_, 2, length(str_) - 1, always_for_in = true) == str
+        @test yasfmt(str_, 2, 34, always_for_in = true) == str
+
+        str = """
+        comp = [a * b
+                for a in 1:10,
+                    b in 11:20]"""
+        @test yasfmt(str_, 2, 33, always_for_in = true) == str
+
+        str = """
+        comp = [a *
+                b
+                for a in
+                    1:10,
+                    b in
+                    11:20]"""
+        @test yasfmt(str_, 2, 1, always_for_in = true) == str
+
+        str_ = "comp = Typed[a * b for a in 1:10, b in 11:20]"
+        str = """
+        comp = Typed[a * b
+                     for a in 1:10, b in 11:20]"""
+        @test yasfmt(str_, 2, length(str_) - 1, always_for_in = true) == str
+        @test yasfmt(str_, 2, 39, always_for_in = true) == str
+
+        str = """
+        comp = Typed[a * b
+                     for a in 1:10,
+                         b in 11:20]"""
+        @test yasfmt(str_, 2, 38, always_for_in = true) == str
+
+        str = """
+        comp = Typed[a *
+                     b
+                     for a in
+                         1:10,
+                         b in
+                         11:20]"""
+        @test yasfmt(str_, 2, 1, always_for_in = true) == str
+
+        str_ = "foo(arg1, arg2, arg3) == bar(arg1, arg2, arg3)"
+        str = """
+        foo(arg1, arg2, arg3) ==
+        bar(arg1, arg2, arg3)"""
+        @test yasfmt(str, 2, length(str_)) == str_
+        @test yasfmt(str_, 2, length(str_) - 1) == str
+        @test yasfmt(str_, 2, 24) == str
+
+        str = """
+        foo(arg1, arg2,
+            arg3) ==
+        bar(arg1, arg2, arg3)"""
+        @test yasfmt(str_, 2, 23) == str
+        @test yasfmt(str_, 2, 21) == str
+
+        str = """
+        foo(arg1, arg2,
+            arg3) ==
+        bar(arg1, arg2,
+            arg3)"""
+        @test yasfmt(str_, 2, 20) == str
+        @test yasfmt(str_, 2, 15) == str
+
+        str = """
+        foo(arg1,
+            arg2,
+            arg3) ==
+        bar(arg1,
+            arg2,
+            arg3)"""
+        @test yasfmt(str_, 2, 14) == str
+        @test yasfmt(str_, 2, 1) == str
+
+        str_ = """
+        function func(arg1::Type1, arg2::Type2, arg3) where {Type1,Type2}
+          body
+        end"""
+        str = """
+        function func(arg1::Type1, arg2::Type2,
+                      arg3) where {Type1,Type2}
+          body
+        end"""
+        @test yasfmt(str_, 2, 64) == str
+        @test yasfmt(str_, 2, 39) == str
+
+        str = """
+        function func(arg1::Type1,
+                      arg2::Type2,
+                      arg3) where {Type1,
+                                   Type2}
+          body
+        end"""
+        @test yasfmt(str_, 2, 31) == str
+        @test yasfmt(str_, 2, 1) == str
+
+        str_ = """
+        @test TimeSpan(spike_annotation) == TimeSpan(first(spike_annotation), last(spike_annotation))"""
+        str = """
+        @test TimeSpan(spike_annotation) ==
+              TimeSpan(first(spike_annotation), last(spike_annotation))"""
+        @test yasfmt(str_, 4, length(str_) - 1) == str
+        @test yasfmt(str_, 4, 63) == str
+        str_ = """
+        @test TimeSpan(spike_annotation) == TimeSpan(first(spike_annotation), last(spike_annotation))"""
+        str = """
+        @test TimeSpan(spike_annotation) ==
+              TimeSpan(first(spike_annotation),
+                       last(spike_annotation))"""
+        @test yasfmt(str_, 4, 62) == str
+
+
+        str_ = raw"""ecg_signal = signal_from_template(eeg_signal; channel_names=[:avl, :avr], file_extension=Symbol("lpcm.zst"))"""
+        str = raw"""
+        ecg_signal = signal_from_template(eeg_signal; channel_names=[:avl, :avr],
+                                          file_extension=Symbol("lpcm.zst"))"""
+        @test yasfmt(str_, 4, length(str_) - 1) == str
+        @test yasfmt(str_, 4, 73) == str
+
+        str = raw"""
+        ecg_signal = signal_from_template(eeg_signal;
+                                          channel_names=[:avl, :avr],
+                                          file_extension=Symbol("lpcm.zst"))"""
+        @test yasfmt(str_, 4, 72) == str
+        str = raw"""
+        ecg_signal = signal_from_template(eeg_signal;
+                                          channel_names=[:avl,
+                                                         :avr],
+                                          file_extension=Symbol("lpcm.zst"))"""
+        @test yasfmt(str_, 4, 1) == str
+
+
+    end
+
+    @testset "inline comments with arguments" begin
+        str_ = """
+        var = fcall(arg1,
+            arg2, arg3, # comment
+                            arg4, arg5)"""
+        str = """
+        var = fcall(arg1, arg2, arg3, # comment
+                    arg4, arg5)"""
+        @test yasfmt(str_, 4, 80) == str
+        @test yasfmt(str_, 4, 29) == str
+
+        str = """
+        var = fcall(arg1, arg2,
+                    arg3, # comment
+                    arg4, arg5)"""
+        @test yasfmt(str_, 4, 28) == str
+        @test yasfmt(str_, 4, 23) == str
+
+        str = """
+        var = fcall(arg1,
+                    arg2,
+                    arg3, # comment
+                    arg4,
+                    arg5)"""
+        @test yasfmt(str_, 4, 22) == str
+        @test yasfmt(str_, 4, 1) == str
+
+        str_ = """
+        comp = [
+        begin
+                    x = a * b + c
+                    y = x^2 + 3x # comment 1
+            end
+                       for a in 1:10,  # comment 2
+                    b in 11:20,
+           c in 300:400]"""
+
+        str = """
+        comp = [begin
+                  x = a * b + c
+                  y = x^2 + 3x # comment 1
+                end
+                for a = 1:10,  # comment 2
+                    b = 11:20, c = 300:400]"""
+        @test yasfmt(str_, 2, 80) == str
+        @test yasfmt(str_, 2, 35) == str
+
+        str = """
+        comp = [begin
+                  x = a * b + c
+                  y = x^2 + 3x # comment 1
+                end
+                for a = 1:10,  # comment 2
+                    b = 11:20,
+                    c = 300:400]"""
+        @test yasfmt(str_, 2, 34) == str
+
+        str_ = """
+        ys = ( if p1(x)
+                 f1(x)
+        elseif p2(x)
+            f2(x)
+        else
+            f3(x)
+        end for    x in xs)
+        """
+        str = """
+        ys = (if p1(x)
+                f1(x)
+              elseif p2(x)
+                f2(x)
+              else
+                f3(x)
+              end
+              for x in xs)
+        """
+        @test yasfmt(str_, 2, 80) == str
+
+
+        str_ = """spike_annotation = first(ann for ann in recording.annotations if ann.value == "epileptiform_spike")"""
+        str = """
+        spike_annotation = first(ann
+                                 for ann in recording.annotations
+                                 if ann.value == "epileptiform_spike")"""
+        @test yasfmt(str_, 2, 80) == str
+
+        # only that
+        str_ = "foo(a, b) = (arg1, arg2, arg3)"
+        str = """
+        foo(a, b) = (arg1, arg2,
+                     arg3)"""
+        @test yasfmt(str_, 2, length(str_) - 1) == str
+
+        str = """
+        foo(a, b) = (arg1,
+                     arg2,
+                     arg3)"""
+        @test yasfmt(str_, 2, 1) == str
+
+        str_ = """
+        fooooooooooooooooooo(arg1, arg2, 
+        x -> begin
+        body
+        end
+        )"""
+        str = """
+        fooooooooooooooooooo(arg1, arg2,
+                             x -> begin
+                                 body
+                             end)"""
+        @test yasfmt(str_, 4, 32) == str
+
+    end
+
+    @testset "inline comments with arguments" begin
+        # parsing error is newline is placed front of `for` here
+        str_ = "var = (x, y) for x = 1:10, y = 1:10"
+        str = """
+        var = (x, y) for x = 1:10,
+                         y = 1:10"""
+        @test yasfmt(str_, 4, length(str_) - 1) == str
+    end
+
+    @testset "invisbrackets" begin
+        str_ = """
+        if ((
+          aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ||
+          aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ||
+          aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+        ))
+          nothing
+        end"""
+        str = """
+        if ((aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ||
+             aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ||
+             aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa))
+          nothing
+        end"""
+        @test yasfmt(str_, 2, 92) == str
+    end
+
+    @testset "#189" begin
+        str_ = """
+    D2 = [
+            (b_hat * y - delta_hat[i] * y) * gamma[i] + (b * y_hat - delta[i] * y_hat) *
+                                                            gamma_hat[i] + (b_hat - y_hat) *
+                                                                           delta[i] + (b - y) *
+                                                                                      delta_hat[i] - delta[i] * delta_hat[i]
+            for i = 1:8
+        ]"""
+        str = """
+        D2 = [(b_hat * y - delta_hat[i] * y) * gamma[i] +
+              (b * y_hat - delta[i] * y_hat) * gamma_hat[i] +
+              (b_hat - y_hat) * delta[i] +
+              (b - y) * delta_hat[i] - delta[i] * delta_hat[i]
+              for i = 1:8]"""
+        @test yasfmt(str_, 2, 60) == str
+
+    end
+
 end