Repository: domluna/JuliaFormatter.jl (julia)

Issue:
`//` rational number constructor should use more spaces
The `//` rational number constructor doesn't use spaces when its arguments are complex expressions:
```Julia
julia> format_text("((-1)^s * factorial(i) * factorial(n)) // (2^n * factorial(i+n))")
"((-1)^s * factorial(i) * factorial(n))//(2^n * factorial(i + n))"
```
Both numerator and denominator are long expressions that contains spaces, yet the `//` operator binds tightly without spaces. This also leads to problems with line breaks – line breaks near the `//` won't happen.

It's fine if simple expressions such as `1//2` are rendered without spaces, but that looks strange for more complex expressions.

Pull request description:
closes #297

Patch:
diff --git a/src/styles/blue/pretty.jl b/src/styles/blue/pretty.jl
index 1abd1bb4c..1240dcdee 100644
--- a/src/styles/blue/pretty.jl
+++ b/src/styles/blue/pretty.jl
@@ -77,3 +77,106 @@ function p_call(bs::BlueStyle, cst::CSTParser.EXPR, s::State)
     end
     t
 end
+
+function p_binaryopcall(
+    bs::BlueStyle,
+    cst::CSTParser.EXPR,
+    s::State;
+    nonest = false,
+    nospace = false,
+)
+    style = getstyle(bs)
+    t = FST(cst, nspaces(s))
+    op = cst[2]
+    nonest = nonest || op.kind === Tokens.COLON
+    if cst.parent.typ === CSTParser.Curly &&
+       op.kind in (Tokens.ISSUBTYPE, Tokens.ISSUPERTYPE) &&
+       !s.opts.whitespace_typedefs
+        nospace = true
+    elseif op.kind === Tokens.COLON
+        nospace = true
+    end
+    nospace_args = s.opts.whitespace_ops_in_indices ? false : nospace
+
+    if is_opcall(cst[1])
+        n = pretty(style, cst[1], s, nonest = nonest, nospace = nospace_args)
+    else
+        n = pretty(style, cst[1], s)
+    end
+
+    if op.kind === Tokens.COLON &&
+       s.opts.whitespace_ops_in_indices &&
+       !is_leaf(cst[1]) &&
+       !is_iterable(cst[1])
+
+        paren = FST(CSTParser.PUNCTUATION, -1, n.startline, n.startline, "(")
+        add_node!(t, paren, s)
+        add_node!(t, n, s, join_lines = true)
+        paren = FST(CSTParser.PUNCTUATION, -1, n.startline, n.startline, ")")
+        add_node!(t, paren, s, join_lines = true)
+    else
+        add_node!(t, n, s)
+    end
+
+    nrhs = nest_rhs(cst)
+    nrhs && (t.nest_behavior = AlwaysNest)
+    nest = (nestable(style, cst) && !nonest) || nrhs
+
+    if op.fullspan == 0
+        # Do nothing - represents a binary op with no textual representation.
+        # For example: `2a`, which is equivalent to `2 * a`.
+    elseif op.kind === Tokens.EX_OR
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(style, op, s), s, join_lines = true)
+    elseif (
+        is_number(cst[1]) ||
+        op.kind === Tokens.FWDFWD_SLASH ||
+        op.kind === Tokens.CIRCUMFLEX_ACCENT
+    ) && op.dot
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(style, op, s), s, join_lines = true)
+        nest ? add_node!(t, Placeholder(1), s) : add_node!(t, Whitespace(1), s)
+    elseif op.kind !== Tokens.IN && (
+        nospace || (
+            op.kind !== Tokens.ANON_FUNC && CSTParser.precedence(op) in (
+                CSTParser.ColonOp,
+                CSTParser.RationalOp,
+                CSTParser.PowerOp,
+                CSTParser.DeclarationOp,
+                CSTParser.DotOp,
+            )
+        )
+    )
+        add_node!(t, pretty(style, op, s), s, join_lines = true)
+    else
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(style, op, s), s, join_lines = true)
+        nest ? add_node!(t, Placeholder(1), s) : add_node!(t, Whitespace(1), s)
+    end
+
+    if is_opcall(cst[3])
+        n = pretty(style, cst[3], s, nonest = nonest, nospace = nospace_args)
+    else
+        n = pretty(style, cst[3], s)
+    end
+
+    if op.kind === Tokens.COLON &&
+       s.opts.whitespace_ops_in_indices &&
+       !is_leaf(cst[3]) &&
+       !is_iterable(cst[3])
+        paren = FST(CSTParser.PUNCTUATION, -1, n.startline, n.startline, "(")
+        add_node!(t, paren, s, join_lines = true)
+        add_node!(t, n, s, join_lines = true)
+        paren = FST(CSTParser.PUNCTUATION, -1, n.startline, n.startline, ")")
+        add_node!(t, paren, s, join_lines = true)
+    else
+        add_node!(t, n, s, join_lines = true)
+    end
+
+    if nest
+        # for indent, will be converted to `indent_size` if needed
+        insert!(t.nodes, length(t.nodes), Placeholder(0))
+    end
+
+    t
+end
diff --git a/src/styles/default/pretty.jl b/src/styles/default/pretty.jl
index 67969107b..1fd7f7af2 100644
--- a/src/styles/default/pretty.jl
+++ b/src/styles/default/pretty.jl
@@ -1408,11 +1408,7 @@ function p_binaryopcall(
     elseif op.kind === Tokens.EX_OR
         add_node!(t, Whitespace(1), s)
         add_node!(t, pretty(style, op, s), s, join_lines = true)
-    elseif (
-        is_number(cst[1]) ||
-        op.kind === Tokens.FWDFWD_SLASH ||
-        op.kind === Tokens.CIRCUMFLEX_ACCENT
-    ) && op.dot
+    elseif (is_number(cst[1]) || op.kind === Tokens.CIRCUMFLEX_ACCENT) && op.dot
         add_node!(t, Whitespace(1), s)
         add_node!(t, pretty(style, op, s), s, join_lines = true)
         nest ? add_node!(t, Placeholder(1), s) : add_node!(t, Whitespace(1), s)
@@ -1420,7 +1416,6 @@ function p_binaryopcall(
         nospace || (
             op.kind !== Tokens.ANON_FUNC && CSTParser.precedence(op) in (
                 CSTParser.ColonOp,
-                CSTParser.RationalOp,
                 CSTParser.PowerOp,
                 CSTParser.DeclarationOp,
                 CSTParser.DotOp,


Test patch:
diff --git a/test/blue_style.jl b/test/blue_style.jl
index 2c400dc33..aca635a56 100644
--- a/test/blue_style.jl
+++ b/test/blue_style.jl
@@ -1,4 +1,10 @@
 @testset "Blue Style" begin
+    @testset "ops" begin
+        # `//` and `^` are binary ops without whitespace around them
+        @test fmt("1 // 2 + 3 ^ 4", style = BlueStyle()) == "1//2 + 3^4"
+        @test fmt("a.//10", style = BlueStyle()) == "a .// 10"
+    end
+
     @testset "nest to one line" begin
         str_ = """
         var = [arg1, #com
diff --git a/test/default_style.jl b/test/default_style.jl
index aac68ff00..f7ab29174 100644
--- a/test/default_style.jl
+++ b/test/default_style.jl
@@ -377,8 +377,7 @@
         str = "!(typ <: ArithmeticTypes)"
         @test fmt(str) == str
 
-        # `//` and `^` are binary ops without whitespace around them
-        @test fmt("1 // 2 + 3 ^ 4") == "1//2 + 3^4"
+        @test fmt("1 // 2 + 3 ^ 4") == "1 // 2 + 3^4"
 
         # Function def