diff --git a/elm-format.cabal b/elm-format.cabal index 85caf2e7e..0fe62bee2 100644 --- a/elm-format.cabal +++ b/elm-format.cabal @@ -91,6 +91,7 @@ library ElmFormat.Filesystem ElmFormat.FileStore ElmFormat.Operation + ElmFormat.Render.GroupBinops ElmFormat.Version Flags Messages.Formatter.Format diff --git a/src/ElmFormat/Render/Box.hs b/src/ElmFormat/Render/Box.hs index 7aae8c950..d3a043668 100644 --- a/src/ElmFormat/Render/Box.hs +++ b/src/ElmFormat/Render/Box.hs @@ -21,6 +21,7 @@ import Data.Maybe (fromMaybe, maybeToList) import qualified Data.Text as Text import qualified ElmFormat.Render.ElmStructure as ElmStructure import qualified ElmFormat.Render.Markdown +import qualified ElmFormat.Render.GroupBinops as GroupBinops import qualified ElmFormat.Version import qualified Parse.Parse as Parse import qualified Reporting.Annotation as RA @@ -791,7 +792,7 @@ formatDefinition elmVersion name args comments expr = body = stack1 $ concat [ map formatComment comments - , [ formatExpression elmVersion SyntaxSeparated expr ] + , [ formatExpression elmVersion SyntaxSeparated (GroupBinops.extractAnds expr) ] -- TODO: use extractAnds for all expressions ] in ElmStructure.definition "=" True diff --git a/src/ElmFormat/Render/GroupBinops.hs b/src/ElmFormat/Render/GroupBinops.hs new file mode 100644 index 000000000..5b434263e --- /dev/null +++ b/src/ElmFormat/Render/GroupBinops.hs @@ -0,0 +1,72 @@ +module ElmFormat.Render.GroupBinops (extractAnds) where + +import AST.V0_16 +import AST.Expression +import Reporting.Annotation +import qualified AST.Variable as Var +import qualified Reporting.Region as Region + + +-- TODO: handle Ors also +-- TODO: ensure it only groups comparison operators? +-- TODO: make it work for expression that aren't directly the body of a definition +extractAnds :: Expr -> Expr +extractAnds expr = + case expr of + A loc (Binops left ops multiline) -> + A loc $ done multiline $ foldl step (left, [], []) ops + + _ -> + expr + + +type State = (Expr, [(Comments, Var.Ref, Comments, Expr)], [(Expr, Var.Ref)]) + +shiftReverse :: [(a, b)] -> a -> [(b, a)] -> (a, [(b, a)]) +shiftReverse [] last acc = (last, acc) +shiftReverse ((prev, op):rest) last acc = + shiftReverse rest prev ((op, last) : acc) + + +step :: State -> (Comments, Var.Ref, Comments, Expr) -> State +step (left, inners, conditions) (pre, op, post, next) = -- TODO: Handle comments + case op of + Var.OpRef (SymbolIdentifier opSymbol) -> + if opSymbol == "&&" || opSymbol == "<|" then + ( next, [], (Var.OpRef (SymbolIdentifier opSymbol), packageCondition left inners) : conditions ) + else + ( left, (pre, op, post, next) : inners, conditions ) + + _ -> + ( left, (pre, op, post, next) : inners, conditions ) + + +packageCondition :: Expr -> [(Comments, Var.Ref, Comments, Expr)] -> Expr +packageCondition left [] = left +packageCondition left inners = + noRegion $ Binops left (reverse inners) False + + +done :: Bool -> State -> Expr' +done multiline (left, inners, []) = (Binops left (reverse inners) multiline) +done multiline (left, inners, conditions) = + let + finalConditions = + reverse $ packageCondition left inners : conditions + + buildOp right = + ([], Var.OpRef (SymbolIdentifier "&&"), [], right) + in + case finalConditions of + first : r -> + (Binops first (fmap buildOp r) multiline) + + +nowhere :: Region.Position +nowhere = + Region.Position 0 0 + + +noRegion :: a -> Located a +noRegion = + at nowhere nowhere diff --git a/tests/run-tests.sh b/tests/run-tests.sh index 1813cfc56..dae4140f9 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -297,6 +297,8 @@ echo "# elm-format test suite" checkWaysToRun +checkTransformation 0.18 AddParensForAndOr.elm + checkGood 0.16 Simple.elm checkGood 0.16 AllSyntax/0.16/AllSyntax.elm checkGoodAllSyntax 0.16 Module diff --git a/tests/test-files/transform/AddParensForAndOr.elm b/tests/test-files/transform/AddParensForAndOr.elm new file mode 100644 index 000000000..0506231d3 --- /dev/null +++ b/tests/test-files/transform/AddParensForAndOr.elm @@ -0,0 +1,43 @@ + + +addParensToAnd = + 1 == 2 && 3 /= 4 && 5 < 6 && 7 > 8 && 9 <= 10 && 11 >= 12 + +addParensToAndWithComplexConditions = + 1 == 2 + 3 && 4 /= 5 && 6 < 7 + 8 && 9 > 10 && 11 <= 12 && 13 >= 14 + 15 + +addParensToAndMultiline = + 1 + == 2 && 3 /= 4 && 5 < 6 && 7 > 8 && 9 <= 10 && 11 >= 12 + +makesGroupedExpressionsSingleLine = + 1 + == + 2 + + + 7 + * + 5 + - + 6 + + + -8 + && + 3 + /= + 4 + + +doesntAddParensAroundParens = + (1 == 2) && (3 /= 4) && 5/= 6 && (7 /= 8) + +keepsNewLinesInExistingParens = + (1 + == 2 + + 3 * 4 - 5 + 6) && 7 /= 8 + +doesntGroupPipes f = + f <| True && False + +doesntGroupPipes2 f = + True && False |> f diff --git a/tests/test-files/transform/AddParensForAndOr.formatted.elm b/tests/test-files/transform/AddParensForAndOr.formatted.elm new file mode 100644 index 000000000..5936c6a71 --- /dev/null +++ b/tests/test-files/transform/AddParensForAndOr.formatted.elm @@ -0,0 +1,38 @@ +module Main exposing (..) + + +addParensToAnd = + (1 == 2) && (3 /= 4) && (5 < 6) && (7 > 8) && (9 <= 10) && (11 >= 12) + + +addParensToAndWithComplexConditions = + (1 == 2 + 3) && (4 /= 5) && (6 < 7 + 8) && (9 > 10) && (11 <= 12) && (13 >= 14 + 15) + + +addParensToAndMultiline = + (1 == 2) + && (3 /= 4) + && (5 < 6) + && (7 > 8) + && (9 <= 10) + && (11 >= 12) + + +makesGroupedExpressionsSingleLine = + (1 == 2 + 7 * 5 - 6 + -8) + && (3 /= 4) + + +doesntAddParensAroundParens = + (1 == 2) && (3 /= 4) && (5 /= 6) && (7 /= 8) + + +keepsNewLinesInExistingParens = + (1 + == 2 + + 3 + * 4 + - 5 + + 6 + ) + && (7 /= 8)