From c541461ee234c4425a6721e2fac74e0b1f5067a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lef=C3=A8vre?= Date: Sat, 16 Nov 2019 18:42:22 +0100 Subject: [PATCH] Allow single-line `if` expressions Resolves #209 --- parser/src/AST/Expression.hs | 2 +- parser/src/AST/Json.hs | 2 +- parser/src/Parse/Expression.hs | 13 +- src/AST/MapExpr.hs | 4 +- src/ElmFormat/Render/Box.hs | 154 ++++++++++++------ tests/Parse/ExpressionTest.hs | 8 +- .../good/Elm-0.19/AllSyntax/Expressions.elm | 17 +- .../transform/Elm-0.18/Examples.formatted.elm | 20 +-- .../Elm-0.19/IfElseIfAlwaysMultiLine.elm | 1 + .../IfElseIfAlwaysMultiLine.formatted.elm | 12 ++ 10 files changed, 149 insertions(+), 84 deletions(-) create mode 100644 tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.elm create mode 100644 tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.formatted.elm diff --git a/parser/src/AST/Expression.hs b/parser/src/AST/Expression.hs index 6d6816f36..c329cfb63 100644 --- a/parser/src/AST/Expression.hs +++ b/parser/src/AST/Expression.hs @@ -59,7 +59,7 @@ data Expr' | AccessFunction LowercaseIdentifier | Lambda [(Comments, Pattern.Pattern)] Comments Expr Bool - | If IfClause [(Comments, IfClause)] (Comments, Expr) + | If IfClause [(Comments, IfClause)] (Comments, Expr) Multiline | Let [LetDeclaration] Comments Expr | Case (Commented Expr, Bool) [(Commented Pattern.Pattern, (Comments, Expr))] diff --git a/parser/src/AST/Json.hs b/parser/src/AST/Json.hs index 079cb473c..da9fde759 100644 --- a/parser/src/AST/Json.hs +++ b/parser/src/AST/Json.hs @@ -373,7 +373,7 @@ instance ToJSON Expr where , ("body", showJSON body) ] - If (Commented _ cond' _, Commented _ thenBody' _) rest' (_, elseBody) -> + If (Commented _ cond' _, Commented _ thenBody' _) rest' (_, elseBody) _ -> let ifThenElse :: Expr -> Expr -> [(Comments, IfClause)] -> JSValue ifThenElse cond thenBody rest = diff --git a/parser/src/Parse/Expression.hs b/parser/src/Parse/Expression.hs index 6df39a3e5..07df5d043 100644 --- a/parser/src/Parse/Expression.hs +++ b/parser/src/Parse/Expression.hs @@ -191,11 +191,14 @@ ifExpr elmVersion = >> whitespace in do - first <- ifClause elmVersion - rest <- many (try $ (,) <$> elseKeyword <*> ifClause elmVersion) - final <- (,) <$> elseKeyword <*> expr elmVersion - - return $ E.If first rest final + (first, firstMultiline) <- trackNewline $ ifClause elmVersion + (rest, restMultiline) <- trackNewline $ many (try $ (,) <$> elseKeyword <*> ifClause elmVersion) + (final, finalMultiline) <- trackNewline $ (,) <$> elseKeyword <*> expr elmVersion + + return $ E.If first rest final $ + case (firstMultiline, restMultiline, finalMultiline) of + (JoinAll, JoinAll, JoinAll) -> JoinAll + _ -> SplitAll ifClause :: ElmVersion -> IParser E.IfClause diff --git a/src/AST/MapExpr.hs b/src/AST/MapExpr.hs index d6035ea0c..6a9cce143 100644 --- a/src/AST/MapExpr.hs +++ b/src/AST/MapExpr.hs @@ -81,8 +81,8 @@ instance MapExpr Expr' where AccessFunction _ -> expr Lambda params pre body multi -> Lambda params pre (mapExpr f body) multi - If c1 elseIfs els -> - If (mapExpr f c1) (mapExpr f elseIfs) (mapExpr f els) + If c1 elseIfs els multiline -> + If (mapExpr f c1) (mapExpr f elseIfs) (mapExpr f els) multiline Let decls pre body -> Let (mapExpr f decls) pre body Case cond branches -> diff --git a/src/ElmFormat/Render/Box.hs b/src/ElmFormat/Render/Box.hs index ab0d96660..213de20ef 100644 --- a/src/ElmFormat/Render/Box.hs +++ b/src/ElmFormat/Render/Box.hs @@ -1407,57 +1407,9 @@ formatExpression' elmVersion importInfo context aexpr = (fmap (formatPreCommentedExpression elmVersion importInfo SpaceSeparated) args) |> expressionParens SpaceSeparated context - AST.Expression.If if' elseifs (elsComments, els) -> - let - opening key cond = - case (key, cond) of - (SingleLine key', SingleLine cond') -> - line $ row - [ key' - , space - , cond' - , space - , keyword "then" - ] - _ -> - stack1 - [ key - , cond |> indent - , line $ keyword "then" - ] - - formatIf (cond, body) = - stack1 - [ opening (line $ keyword "if") $ formatCommentedExpression elmVersion importInfo SyntaxSeparated cond - , indent $ formatCommented_ True (formatExpression elmVersion importInfo SyntaxSeparated) body - ] - - formatElseIf (ifComments, (cond, body)) = - let - key = - case (formatHeadCommented id (ifComments, line $ keyword "if")) of - SingleLine key' -> - line $ row [ keyword "else", space, key' ] - key' -> - stack1 - [ line $ keyword "else" - , key' - ] - in - stack1 - [ blankLine - , opening key $ formatCommentedExpression elmVersion importInfo SyntaxSeparated cond - , indent $ formatCommented_ True (formatExpression elmVersion importInfo SyntaxSeparated) body - ] - in - formatIf if' - |> andThen (map formatElseIf elseifs) - |> andThen - [ blankLine - , line $ keyword "else" - , indent $ formatCommented_ True (formatExpression elmVersion importInfo SyntaxSeparated) (AST.Commented elsComments els []) - ] - |> expressionParens AmbiguousEnd context + AST.Expression.If if' elseifs els multiline -> + formatIfExpression elmVersion importInfo if' elseifs els multiline + |> expressionParens AmbiguousEnd context AST.Expression.Let defs bodyComments expr -> let @@ -2120,6 +2072,106 @@ formatComment comment = AST.CommentTrickBlock c -> mustBreak $ row [ punc "{--", literal c, punc "-}" ] +formatIfExpression :: + ElmVersion + -> ImportInfo + -> AST.Expression.IfClause + -> [(AST.Comments, AST.Expression.IfClause)] + -> (AST.Comments, AST.Expression.Expr) + -> AST.Multiline + -> Box +formatIfExpression elmVersion importInfo if' elseifs (elsComments, els) multiline = + let + (cond, (AST.Commented preThen thenExpr postThen)) = + if' + + then' = + concat $ + [ Maybe.maybeToList $ formatComments preThen + , [ formatExpression elmVersion importInfo SyntaxSeparated thenExpr ] + , Maybe.maybeToList $ formatComments postThen + ] + + else' = + concat $ + [ Maybe.maybeToList $ formatComments elsComments + , [ formatExpression elmVersion importInfo SyntaxSeparated els ] + ] + in + case + ( multiline + , formatCommentedExpression elmVersion importInfo SyntaxSeparated cond + , allSingles then' + , elseifs + , allSingles else' + ) + of + (AST.JoinAll, SingleLine cond', Right singleThens, [], Right singleElses) -> + line $ row $ concat + [ [ keyword "if", space, cond', space, keyword "then", space ] + , List.intersperse space singleThens + , [ space, keyword "else", space ] + , List.intersperse space singleElses + ] + + (_, ifCond, thenBody, _, elseBody) -> + formatIfExpressionMultiline elmVersion importInfo ifCond thenBody elseifs elseBody + + +formatIfExpressionMultiline :: + ElmVersion + -> ImportInfo + -> Box + -> Either [Box] [Line] + -> [(AST.Comments, AST.Expression.IfClause)] + -> Either [Box] [Line] + -> Box +formatIfExpressionMultiline elmVersion importInfo ifCond thenBody elseifs elseBody = + let + opening (SingleLine key) (SingleLine cond') = + line $ row [ key, space, cond', space, keyword "then" ] + opening key cond' = + stack1 + [ key + , cond' |> indent + , line $ keyword "then" + ] + + boxesOrLinesToBox boxesOrLines = + ElmStructure.forceableSpaceSepOrStack1 True $ + case boxesOrLines of + Right singleLines -> map line singleLines + Left boxes -> boxes + + formatElseIf (ifComments, (cond', body')) = + let + key = + case (formatHeadCommented id (ifComments, line $ keyword "if")) of + SingleLine key' -> + line $ row [ keyword "else", space, key' ] + key' -> + stack1 + [ line $ keyword "else" + , key' + ] + in + stack1 + [ blankLine + , opening key $ formatCommentedExpression elmVersion importInfo SyntaxSeparated cond' + , indent $ formatCommented_ True (formatExpression elmVersion importInfo SyntaxSeparated) body' + ] + in + stack1 + [ opening (line $ keyword "if") ifCond + , indent $ boxesOrLinesToBox thenBody + ] + |> andThen (map formatElseIf elseifs) + |> andThen + [ blankLine + , line $ keyword "else" + , indent $ boxesOrLinesToBox elseBody + ] + formatLiteral :: ElmVersion -> AST.Literal -> Box formatLiteral elmVersion lit = diff --git a/tests/Parse/ExpressionTest.hs b/tests/Parse/ExpressionTest.hs index 0d91dbe63..7322f4100 100644 --- a/tests/Parse/ExpressionTest.hs +++ b/tests/Parse/ExpressionTest.hs @@ -259,10 +259,10 @@ tests = ] , testGroup "if statement" - [ example "" "if x then y else z" $ at 1 1 1 19 (If (Commented [] (at 1 4 1 5 (VarExpr (VarRef [] $ LowercaseIdentifier "x"))) [],Commented [] (at 1 11 1 12 (VarExpr (VarRef [] $ LowercaseIdentifier "y"))) []) [] ([],at 1 18 1 19 (VarExpr (VarRef [] $ LowercaseIdentifier "z")))) - , example "comments" "if{-A-}x{-B-}then{-C-}y{-D-}else{-E-}if{-F-}x_{-G-}then{-H-}y_{-I-}else{-J-}z" $ at 1 1 1 78 (If (Commented [BlockComment ["A"]] (at 1 8 1 9 (VarExpr (VarRef [] $ LowercaseIdentifier "x"))) [BlockComment ["B"]],Commented [BlockComment ["C"]] (at 1 23 1 24 (VarExpr (VarRef [] $ LowercaseIdentifier "y"))) [BlockComment ["D"]]) [([BlockComment ["E"]],(Commented [BlockComment ["F"]] (at 1 45 1 47 (VarExpr (VarRef [] $ LowercaseIdentifier "x_"))) [BlockComment ["G"]],Commented [BlockComment ["H"]] (at 1 61 1 63 (VarExpr (VarRef [] $ LowercaseIdentifier "y_"))) [BlockComment ["I"]]))] ([BlockComment ["J"]],at 1 77 1 78 (VarExpr (VarRef [] $ LowercaseIdentifier "z")))) - , example "else if" "if x1 then y1 else if x2 then y2 else if x3 then y3 else z" $ at 1 1 1 59 (If (Commented [] (at 1 4 1 6 (VarExpr (VarRef [] $ LowercaseIdentifier "x1"))) [],Commented [] (at 1 12 1 14 (VarExpr (VarRef [] $ LowercaseIdentifier "y1"))) []) [([],(Commented [] (at 1 23 1 25 (VarExpr (VarRef [] $ LowercaseIdentifier "x2"))) [],Commented [] (at 1 31 1 33 (VarExpr (VarRef [] $ LowercaseIdentifier "y2"))) [])),([],(Commented [] (at 1 42 1 44 (VarExpr (VarRef [] $ LowercaseIdentifier "x3"))) [],Commented [] (at 1 50 1 52 (VarExpr (VarRef [] $ LowercaseIdentifier "y3"))) []))] ([],at 1 58 1 59 (VarExpr (VarRef [] $ LowercaseIdentifier "z")))) - , example "newlines" "if\n x\n then\n y\n else\n z" $ at 1 1 6 3 (If (Commented [] (at 2 2 2 3 (VarExpr (VarRef [] $ LowercaseIdentifier "x"))) [],Commented [] (at 4 2 4 3 (VarExpr (VarRef [] $ LowercaseIdentifier "y"))) []) [] ([],at 6 2 6 3 (VarExpr (VarRef [] $ LowercaseIdentifier "z")))) + [ example "" "if x then y else z" $ at 1 1 1 19 (If (Commented [] (at 1 4 1 5 (VarExpr (VarRef [] $ LowercaseIdentifier "x"))) [],Commented [] (at 1 11 1 12 (VarExpr (VarRef [] $ LowercaseIdentifier "y"))) []) [] ([],at 1 18 1 19 (VarExpr (VarRef [] $ LowercaseIdentifier "z"))) JoinAll) + , example "comments" "if{-A-}x{-B-}then{-C-}y{-D-}else{-E-}if{-F-}x_{-G-}then{-H-}y_{-I-}else{-J-}z" $ at 1 1 1 78 (If (Commented [BlockComment ["A"]] (at 1 8 1 9 (VarExpr (VarRef [] $ LowercaseIdentifier "x"))) [BlockComment ["B"]],Commented [BlockComment ["C"]] (at 1 23 1 24 (VarExpr (VarRef [] $ LowercaseIdentifier "y"))) [BlockComment ["D"]]) [([BlockComment ["E"]],(Commented [BlockComment ["F"]] (at 1 45 1 47 (VarExpr (VarRef [] $ LowercaseIdentifier "x_"))) [BlockComment ["G"]],Commented [BlockComment ["H"]] (at 1 61 1 63 (VarExpr (VarRef [] $ LowercaseIdentifier "y_"))) [BlockComment ["I"]]))] ([BlockComment ["J"]],at 1 77 1 78 (VarExpr (VarRef [] $ LowercaseIdentifier "z"))) JoinAll) + , example "else if" "if x1 then y1 else if x2 then y2 else if x3 then y3 else z" $ at 1 1 1 59 (If (Commented [] (at 1 4 1 6 (VarExpr (VarRef [] $ LowercaseIdentifier "x1"))) [],Commented [] (at 1 12 1 14 (VarExpr (VarRef [] $ LowercaseIdentifier "y1"))) []) [([],(Commented [] (at 1 23 1 25 (VarExpr (VarRef [] $ LowercaseIdentifier "x2"))) [],Commented [] (at 1 31 1 33 (VarExpr (VarRef [] $ LowercaseIdentifier "y2"))) [])),([],(Commented [] (at 1 42 1 44 (VarExpr (VarRef [] $ LowercaseIdentifier "x3"))) [],Commented [] (at 1 50 1 52 (VarExpr (VarRef [] $ LowercaseIdentifier "y3"))) []))] ([],at 1 58 1 59 (VarExpr (VarRef [] $ LowercaseIdentifier "z"))) JoinAll) + , example "newlines" "if\n x\n then\n y\n else\n z" $ at 1 1 6 3 (If (Commented [] (at 2 2 2 3 (VarExpr (VarRef [] $ LowercaseIdentifier "x"))) [],Commented [] (at 4 2 4 3 (VarExpr (VarRef [] $ LowercaseIdentifier "y"))) []) [] ([],at 6 2 6 3 (VarExpr (VarRef [] $ LowercaseIdentifier "z"))) SplitAll) ] , testGroup "let statement" diff --git a/tests/test-files/good/Elm-0.19/AllSyntax/Expressions.elm b/tests/test-files/good/Elm-0.19/AllSyntax/Expressions.elm index 96e70aabb..ab55a2c36 100644 --- a/tests/test-files/good/Elm-0.19/AllSyntax/Expressions.elm +++ b/tests/test-files/good/Elm-0.19/AllSyntax/Expressions.elm @@ -273,6 +273,16 @@ lambdaWithMultilinePattern = ifStatement = let a = + if True then 1 else 2 + + b = + if True then + 1 + + else + 2 + + c = if True then 1 @@ -282,7 +292,10 @@ ifStatement = else 3 - b = + d = + if {- A -} True {- B -} then {- C -} 1 {- D -} else {- E -} 2 + + e = if {- C -} True {- D -} then {- E -} 1 @@ -297,7 +310,7 @@ ifStatement = {- L -} 3 - c = + f = if --C True diff --git a/tests/test-files/transform/Elm-0.18/Examples.formatted.elm b/tests/test-files/transform/Elm-0.18/Examples.formatted.elm index dbafc1d62..e304fb527 100644 --- a/tests/test-files/transform/Elm-0.18/Examples.formatted.elm +++ b/tests/test-files/transform/Elm-0.18/Examples.formatted.elm @@ -2,13 +2,7 @@ module Main exposing (bar, multilineList, ratio) ratio = - graphHeight - / (if range == 0 then - 0.1 - - else - toFloat range - ) + graphHeight / (if range == 0 then 0.1 else toFloat range) @@ -16,17 +10,7 @@ ratio = bar = - if - if a then - True - - else - False - then - "a" - - else - "b" + if if a then True else False then "a" else "b" multilineList = diff --git a/tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.elm b/tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.elm new file mode 100644 index 000000000..b75e6f61e --- /dev/null +++ b/tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.elm @@ -0,0 +1 @@ +x = if True then 1 else if False then 2 else 3 diff --git a/tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.formatted.elm b/tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.formatted.elm new file mode 100644 index 000000000..d7b7e2d6c --- /dev/null +++ b/tests/test-files/transform/Elm-0.19/IfElseIfAlwaysMultiLine.formatted.elm @@ -0,0 +1,12 @@ +module Main exposing (x) + + +x = + if True then + 1 + + else if False then + 2 + + else + 3