diff --git a/src/Fantomas.Core.Tests/ControlStructureTests.fs b/src/Fantomas.Core.Tests/ControlStructureTests.fs
index 8f55dc26f..000b1ff60 100644
--- a/src/Fantomas.Core.Tests/ControlStructureTests.fs
+++ b/src/Fantomas.Core.Tests/ControlStructureTests.fs
@@ -644,7 +644,7 @@ let genPropertyWithGetSet astContext (b1, b2) rangeOfMember =
genPreXmlDoc px
+> genAttributes astContext ats
+> genMemberFlags astContext mf1
- +> ifElse isInline (!- "inline ") sepNone
+ +> ifElse isInline (!-"inline ") sepNone
+> opt sepSpace ao genAccess
assert (ps1 |> Seq.map fst |> Seq.forall Option.isNone)
diff --git a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj
index f764d1de9..6f606dc0c 100644
--- a/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj
+++ b/src/Fantomas.Core.Tests/Fantomas.Core.Tests.fsproj
@@ -134,6 +134,7 @@
+
diff --git a/src/Fantomas.Core.Tests/InterpolatedStringTests.fs b/src/Fantomas.Core.Tests/InterpolatedStringTests.fs
index a960bb7d1..47665d4ef 100644
--- a/src/Fantomas.Core.Tests/InterpolatedStringTests.fs
+++ b/src/Fantomas.Core.Tests/InterpolatedStringTests.fs
@@ -165,20 +165,6 @@ $\"\"\"one: {1}<
>two: {2}\"\"\"
"
-[]
-let ``prefix application, 1414`` () =
- formatSourceString
- """
-!- $".{s}"
-"""
- config
- |> prepend newline
- |> should
- equal
- """
-!- $".{s}"
-"""
-
[]
let ``format in FillExpr, 1549`` () =
formatSourceString
diff --git a/src/Fantomas.Core.Tests/LambdaTests.fs b/src/Fantomas.Core.Tests/LambdaTests.fs
index 517732422..45d1011d2 100644
--- a/src/Fantomas.Core.Tests/LambdaTests.fs
+++ b/src/Fantomas.Core.Tests/LambdaTests.fs
@@ -354,7 +354,7 @@ let genMemberFlagsForMemberBinding astContext (mf: MemberFlags) (rangeOfBindingA
| Token { TokenInfo = { TokenName = "MEMBER" } } -> r.StartLine = rangeOfBindingAndRhs.StartLine
| _ -> false)
- |> Option.defaultValue (!- "override ")
+ |> Option.defaultValue (!-"override ")
<| ctx)
<| ctx
"""
diff --git a/src/Fantomas.Core.Tests/LetBindingTests.fs b/src/Fantomas.Core.Tests/LetBindingTests.fs
index 046f6fb41..60cb6dca2 100644
--- a/src/Fantomas.Core.Tests/LetBindingTests.fs
+++ b/src/Fantomas.Core.Tests/LetBindingTests.fs
@@ -1342,7 +1342,7 @@ let internal sepSpace =
then
ctx
else
- (!- " ") ctx
+ (!-" ") ctx
"""
[]
diff --git a/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs b/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs
index 09397a552..c0df14649 100644
--- a/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs
+++ b/src/Fantomas.Core.Tests/MultiLineLambdaClosingNewlineTests.fs
@@ -268,7 +268,7 @@ let expr =
es
(fun e ->
match e with
- | Paren(_, Lambda _, _) -> !- "lambda"
+ | Paren(_, Lambda _, _) -> !-"lambda"
| _ -> genExpr astContext e
)
"""
diff --git a/src/Fantomas.Core.Tests/OperatorTests.fs b/src/Fantomas.Core.Tests/OperatorTests.fs
index 75d2c59ad..ef0184895 100644
--- a/src/Fantomas.Core.Tests/OperatorTests.fs
+++ b/src/Fantomas.Core.Tests/OperatorTests.fs
@@ -5,41 +5,6 @@ open FsUnit
open Fantomas.Core.Tests.TestHelpers
open Fantomas.Core
-[]
-let ``should format prefix operators`` () =
- formatSourceString
- """let x = -y
-let z = !!x
- """
- config
- |> should
- equal
- """let x = -y
-let z = !!x
-"""
-
-[]
-let ``should keep triple ~~~ operator`` () =
- formatSourceString
- """x ~~~FileAttributes.ReadOnly
- """
- config
- |> should
- equal
- """x ~~~FileAttributes.ReadOnly
-"""
-
-[]
-let ``should keep single triple ~~~ operator`` () =
- formatSourceString
- """~~~FileAttributes.ReadOnly
- """
- config
- |> should
- equal
- """~~~FileAttributes.ReadOnly
-"""
-
[]
let ``should keep parens around ? operator definition`` () =
formatSourceString
@@ -62,17 +27,6 @@ let ``should keep parens around ?<- operator definition`` () =
"""let (?<-) f s = f s
"""
-[]
-let ``should keep parens around !+ prefix operator definition`` () =
- formatSourceString
- """let (!+) x = Include x
- """
- config
- |> should
- equal
- """let (!+) x = Include x
-"""
-
[]
let ``should keep parens around ++ infix operator definition`` () =
formatSourceString
@@ -474,19 +428,6 @@ let result =
(typ.GetInterface(typeof.FullName) = null)
"""
-[]
-let ``operator before verbatim string add extra space, 736`` () =
- formatSourceString
- """Target M.Tools (fun _ -> !! @"Tools\Tools.sln" |> rebuild)
-"""
- config
- |> prepend newline
- |> should
- equal
- """
-Target M.Tools (fun _ -> !! @"Tools\Tools.sln" |> rebuild)
-"""
-
[]
let ``function call before pipe operator, 754`` () =
formatSourceString
@@ -1178,42 +1119,6 @@ module Foo =
| false -> id)
"""
-let operator_application_literal_values =
- [ "-86y"
- "86uy"
- "-86s"
- "86us"
- "-86"
- "-86l"
- "86u"
- "86ul"
- "-123n"
- "0x00002D3Fun"
- "-86L"
- "86UL"
- "-4.41F"
- "-4.14"
- "-12456I"
- "-0.7833M"
- "'a'"
- "\"text\""
- "'a'B"
- "\"text\"B" ]
-
-[]
-let ``operators maintain spacing from literal values`` (literalValue: string) =
- formatSourceString
- $"""
-let subtractTwo = + %s{literalValue}
-"""
- config
- |> prepend newline
- |> should
- equal
- $"""
-let subtractTwo = + %s{literalValue}
-"""
-
[]
let ``qualified name to active pattern, 1937`` () =
formatSourceString
@@ -1515,25 +1420,3 @@ let allDecls =
@+ iimplsLs
@+ ctorLs
"""
-
-[]
-let ``adding space after prefix operator breaks code, 2796`` () =
- formatSourceString
- """
-let inline (~%%) id = int id
-
-let f a b = a + b
-
-let foo () = f %%"17" %%"42"
-"""
- config
- |> prepend newline
- |> should
- equal
- """
-let inline (~%%) id = int id
-
-let f a b = a + b
-
-let foo () = f %%"17" %%"42"
-"""
diff --git a/src/Fantomas.Core.Tests/PrefixTests.fs b/src/Fantomas.Core.Tests/PrefixTests.fs
new file mode 100644
index 000000000..df39c3032
--- /dev/null
+++ b/src/Fantomas.Core.Tests/PrefixTests.fs
@@ -0,0 +1,210 @@
+module Fantomas.Core.Tests.PrefixTests
+
+open NUnit.Framework
+open FsUnit
+open Fantomas.Core.Tests.TestHelpers
+
+[]
+let ``should format prefix operators`` () =
+ formatSourceString
+ """let x = -y
+let z = !!x
+ """
+ config
+ |> should
+ equal
+ """let x = -y
+let z = !!x
+"""
+
+[]
+let ``should keep triple ~~~ operator`` () =
+ formatSourceString
+ """x ~~~FileAttributes.ReadOnly
+ """
+ config
+ |> should
+ equal
+ """x ~~~FileAttributes.ReadOnly
+"""
+
+[]
+let ``should keep single triple ~~~ operator`` () =
+ formatSourceString
+ """~~~FileAttributes.ReadOnly
+ """
+ config
+ |> should
+ equal
+ """~~~FileAttributes.ReadOnly
+"""
+
+[]
+let ``operator before verbatim string add extra space, 736`` () =
+ formatSourceString
+ """Target M.Tools (fun _ -> !! @"Tools\Tools.sln" |> rebuild)
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ """
+Target M.Tools (fun _ -> !! @"Tools\Tools.sln" |> rebuild)
+"""
+
+[]
+let ``should keep parens around !+ prefix operator definition`` () =
+ formatSourceString
+ """let (!+) x = Include x
+ """
+ config
+ |> should
+ equal
+ """let (!+) x = Include x
+"""
+
+[]
+let ``adding space after prefix operator breaks code, 2796`` () =
+ formatSourceString
+ """
+let inline (~%%) id = int id
+
+let f a b = a + b
+
+let foo () = f %%"17" %%"42"
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ """
+let inline (~%%) id = int id
+
+let f a b = a + b
+
+let foo () = f %%"17" %%"42"
+"""
+
+[]
+let ``tilde unary operator with literal and variable, 3131`` () =
+ formatSourceString
+ """
+let x = ~~1
+let y = ~~x
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ """
+let x = ~~1
+let y = ~~x
+"""
+
+[]
+let ``prefix application with interpolated string, 1414`` () =
+ formatSourceString
+ """
+!- $".{s}"
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ """
+!- $".{s}"
+"""
+
+let operator_application_literal_values_with_sign =
+ [ "-86y"
+ "-86s"
+ "-86"
+ "-86l"
+ "-123n"
+ "-86L"
+ "-4.41F"
+ "-4.14"
+ "-12456I"
+ "-0.7833M"
+ "+46y"
+ "+46s"
+ "+46"
+ "+46l"
+ "+423n"
+ "+46L"
+ "+3.41F"
+ "+3.14"
+ "+32456I"
+ "+0.7833M" ]
+
+[]
+let ``operators maintain spacing from literal values which start with + or -`` (literalValue: string) =
+ formatSourceString
+ $"""
+let subtractTwo = + %s{literalValue}
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ $"""
+let subtractTwo = + %s{literalValue}
+"""
+
+let operator_application_literal_values_without_sign =
+ [ "86uy"
+ "86us"
+ "86u"
+ "86ul"
+ "0x00002D3Fun"
+ "86UL"
+ "'a'"
+ "\"text\""
+ "'a'B"
+ "\"text\"B" ]
+
+[]
+let ``no space added between prefix operators and literal values that do not start with a symbol``
+ (literalValue: string)
+ =
+ formatSourceString
+ $"""
+let subtractTwo = + %s{literalValue}
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ $"""
+let subtractTwo = +%s{literalValue}
+"""
+
+[]
+let ``add space between prefix and quotation`` () =
+ formatSourceString
+ """
+let _ = + <@ 1 @>
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ """
+let _ = + <@ 1 @>
+"""
+
+[]
+let ``add space between prefix and measure literal that starts with a symbol`` () =
+ formatSourceString
+ """
+let _ = - +1
+let _ = - -1
+"""
+ config
+ |> prepend newline
+ |> should
+ equal
+ """
+let _ = - +1
+let _ = - -1
+"""
diff --git a/src/Fantomas.Core.Tests/SpaceBeforeLowercaseInvocationTests.fs b/src/Fantomas.Core.Tests/SpaceBeforeLowercaseInvocationTests.fs
index 73df26b7d..f14ede490 100644
--- a/src/Fantomas.Core.Tests/SpaceBeforeLowercaseInvocationTests.fs
+++ b/src/Fantomas.Core.Tests/SpaceBeforeLowercaseInvocationTests.fs
@@ -217,9 +217,9 @@ let ``ignore setting when function call is the argument of prefix application``
|> should
equal
"""
-!- String.Empty.padLeft(braceSize + spaceAround)
-(!- System.String.Empty.padRight(delta)) ({ ctx with RecordBraceStart = rest })
-!- meh()
+!-String.Empty.padLeft(braceSize + spaceAround)
+(!-System.String.Empty.padRight(delta)) ({ ctx with RecordBraceStart = rest })
+!-meh()
"""
[]
diff --git a/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs b/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs
index 23843758a..04983e94f 100644
--- a/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs
+++ b/src/Fantomas.Core.Tests/SpaceBeforeUppercaseInvocationTests.fs
@@ -251,9 +251,9 @@ let ``ignore setting when function call is the argument of prefix application, 1
|> should
equal
"""
-!- String.Empty.PadLeft(braceSize + spaceAround)
-(!- System.String.Empty.PadRight(delta)) ({ ctx with RecordBraceStart = rest })
-!- Meh()
+!-String.Empty.PadLeft(braceSize + spaceAround)
+(!-System.String.Empty.PadRight(delta)) ({ ctx with RecordBraceStart = rest })
+!-Meh()
"""
[]
diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs
index 2f74ee753..b65149d47 100644
--- a/src/Fantomas.Core/CodePrinter.fs
+++ b/src/Fantomas.Core/CodePrinter.fs
@@ -753,33 +753,41 @@ let genExpr (e: Expr) =
|> genNode node
| Expr.Dynamic node -> genExpr node.FuncExpr +> !- "?" +> genExpr node.ArgExpr |> genNode node
| Expr.PrefixApp node ->
- let fallback = genSingleTextNode node.Operator +> genExpr node.Expr
+ let genWithoutSpace = genSingleTextNode node.Operator +> genExpr node.Expr
+ let genWithSpace = genSingleTextNode node.Operator +> sepSpace +> genExpr node.Expr
+
+ let rec (|ConstantWithSign|_|) =
+ function
+ | Constant.FromText(stn) ->
+ match stn.Text.[0] with
+ | '@'
+ | '-'
+ | '+' -> Some()
+ | _ -> None
+ | Constant.Measure measure -> (|ConstantWithSign|_|) measure.Constant
+ | Constant.Unit _ -> None
match node.Expr with
- | Expr.Constant _
- | Expr.InterpolatedStringExpr _ when not (String.startsWithOrdinal "%" node.Operator.Text) ->
- genSingleTextNode node.Operator +> sepSpace +> genExpr node.Expr
+ // E.g. + <@ 1 @>
+ | Expr.Quote _ -> genWithSpace
+ // E.g. !! @"foobar", because !!@ would be mistaken for an operator.
+ | Expr.Constant(ConstantWithSign) -> genWithSpace
+ // !- $"blah{s}"
+ | Expr.InterpolatedStringExpr _ -> genWithSpace
+ // We don't respect SpaceBeforeLowercaseInvocation here because it can alter the meaning of the code.
+ // E.g. !-meh()
| Expr.AppSingleParenArg appNode ->
genSingleTextNode node.Operator
- +> sepSpace
+> genExpr appNode.FunctionExpr
+> genExpr appNode.ArgExpr
+ // E.g. !-Foo.Meh(a)
| Expr.AppLongIdentAndSingleParenArg appNode ->
let mOptVarNode = appNode.FunctionName.Range
genSingleTextNode node.Operator
- +> sepSpace
+> genExpr (Expr.OptVar(ExprOptVarNode(false, appNode.FunctionName, mOptVarNode)))
+> genExpr appNode.ArgExpr
- | Expr.App appNode ->
- match appNode.Arguments with
- | [ Expr.Constant(Constant.Unit _) as argExpr ] ->
- genSingleTextNode node.Operator
- +> sepSpace
- +> genExpr appNode.FunctionExpr
- +> genExpr argExpr
- | _ -> fallback
- | _ -> fallback
+ | _ -> genWithoutSpace
|> genNode node
| Expr.SameInfixApps node ->
let headIsSynExprLambdaOrIfThenElse = isLambdaOrIfThenElse node.LeadingExpr