import Test.Hspec import Language.Dart.Pretty import Language.Dart.Syntax intExp :: Integer -> Expression intExp = Literal' . IntegerLiteral ident :: String -> Identifier ident = SimpleIdentifier' . SimpleIdentifier identExp :: String -> Expression identExp = Identifier' . ident tyName :: String -> TypeName tyName name = TypeName (ident name) Nothing strType = tyName "String" numType = tyName "num" formal :: TypeName -> String -> FormalParameter formal ty p = NormalFormalParameter' $ SimpleFormalParameter Nothing [] (FVTType ty) (SimpleIdentifier p) posFormal :: TypeName -> String -> FormalParameter posFormal ty p = DefaultFormalParameter parameter Positional Nothing where parameter = (SimpleFormalParameter Nothing [] (FVTType ty) (SimpleIdentifier p)) var :: String -> FormalParameter var p = NormalFormalParameter' $ SimpleFormalParameter Nothing [] FVTVar (SimpleIdentifier p) strLit :: String -> StringLiteral strLit = SingleStringLiteral' . SimpleStringLiteral strLitExp :: String -> Expression strLitExp = Literal' . StringLiteral' . strLit nullExp :: Expression nullExp = Literal' NullLiteral funStmt :: String -> Statement funStmt name = ExpressionStatement ( InvocationExpression ( MethodInvocation Nothing (SimpleIdentifier name) Nothing (ArgumentList []))) funStmt1 :: String -> Expression -> Statement funStmt1 name arg1 = ExpressionStatement ( InvocationExpression ( MethodInvocation Nothing (SimpleIdentifier name) Nothing (ArgumentList [arg1]))) prettyPrintSpec :: Spec prettyPrintSpec = describe "prettyPrint" $ do it "preserves operator precedence" $ do let l = BinaryExpression (intExp 1) "+" (intExp 2) r = BinaryExpression (intExp 3) "-" (intExp 2) e = BinaryExpression l "*" r prettyPrint e `shouldBe` "(1 + 2) * (3 - 2)" it "prints an import directive" $ do let libraryUri = strLit "package:analyzer/src/dart/ast/ast.dart" as = SimpleIdentifier "a" imp = ImportDirective Nothing [] libraryUri [] False (Just as) [ ShowCombinator [SimpleIdentifier "TypeName"] , ShowCombinator [SimpleIdentifier "ArgumentList"] ] prettyPrint imp `shouldBe` "import \"package:analyzer/src/dart/ast/ast.dart\" as a show TypeName, show ArgumentList;" it "prints a function declaration" $ do let formals = FormalParameterList [formal numType "aNumber"] arg = Literal' . StringLiteral' . SingleStringLiteral' $ StringInterpolation [ InterpolationString "The number is " , InterpolationExpression (identExp "aNumber") , InterpolationString "."] body = BlockFunctionBody Sync (Block [ ExpressionStatement ( InvocationExpression ( MethodInvocation Nothing (SimpleIdentifier "print") Nothing (ArgumentList [arg])))]) funExp = FunctionExpression Nothing formals body funDecl = FunctionDeclaration Nothing [] False Nothing Empty (SimpleIdentifier "printNumber") funExp prettyPrint funDecl `shouldBe` "printNumber(num aNumber)\n\ \{\n\ \ print(\"The number is ${aNumber}.\");\n\ \}" it "prints an optional position parameter" $ do let formals = FormalParameterList [formal strType "from", formal strType "msg", posFormal strType "device"] str1 = Literal' . StringLiteral' . SingleStringLiteral' $ StringInterpolation [ InterpolationExpression (identExp "from") , InterpolationString " says " , InterpolationExpression (identExp "msg") ] str2 = Literal' . StringLiteral' . SingleStringLiteral' $ StringInterpolation [ InterpolationExpression (identExp "result") , InterpolationString " with a " , InterpolationExpression (identExp "device") ] body = BlockFunctionBody Sync (Block [ VariableDeclarationStatement ( VariableDeclarationList Nothing [] FCVTVar [VariableDeclaration (SimpleIdentifier "result") (Just str1)]) , IfStatement (BinaryExpression (identExp "device") "!=" nullExp) (Block' (Block [ ExpressionStatement ( AssignmentExpression (identExp "result") "=" str2)])) Nothing , ReturnStatement (Just (identExp "result")) ]) funExp = FunctionExpression Nothing formals body funDecl = FunctionDeclaration Nothing [] False (Just strType) Empty (SimpleIdentifier "say") funExp prettyPrint funDecl `shouldBe` "String say(String from, String msg, [String device])\n\ \{\n\ \ var result = \"${from} says ${msg}\";\n\ \ if (device != null)\n\ \ {\n\ \ result = \"${result} with a ${device}\";\n\ \ }\n\ \ return result;\n\ \}" it "prints an expression function" $ do let body = ExpressionFunctionBody False ( ConditionalExpression (BinaryExpression (identExp "msg") "==" nullExp) (InvocationExpression (MethodInvocation (Just SuperExpression) (SimpleIdentifier "toString") Nothing (ArgumentList []))) (identExp "msg")) funExp = FunctionExpression Nothing (FormalParameterList []) body funDecl = FunctionDeclaration Nothing [] False (Just strType) Empty (SimpleIdentifier "toString") funExp prettyPrint funDecl `shouldBe` "String toString() => msg == null ? super.toString() : msg;" it "prints an enum" $ do let enumConstDecls = [ EnumConstantDeclaration Nothing [] (SimpleIdentifier "red") , EnumConstantDeclaration Nothing [] (SimpleIdentifier "green") , EnumConstantDeclaration Nothing [] (SimpleIdentifier "blue") ] enumDecl = EnumDeclaration Nothing [] (SimpleIdentifier "Color") enumConstDecls prettyPrint enumDecl `shouldBe` "enum Color\n\ \{\n\ \ red,\n\ \ green,\n\ \ blue\n\ \}" it "prints a for loop" $ do let variableList = VariableDeclarationList Nothing [] FCVTVar [VariableDeclaration (SimpleIdentifier "i") (Just (intExp 0))] condition = BinaryExpression (identExp "i") "<" (intExp 5) updater = PostfixExpression (identExp "i") "++" body = (Block' (Block [ ExpressionStatement ( InvocationExpression ( MethodInvocation (Just (identExp "message")) (SimpleIdentifier "write") Nothing (ArgumentList [strLitExp "!"])))])) forLoop = ForStatement (Just variableList) Nothing (Just condition) [updater] body prettyPrint forLoop `shouldBe` "for (var i = 0; i < 5; i++)\n\ \{\n\ \ message.write(\"!\");\n\ \}" it "prints a list literal" $ do let litExp = Literal' . TypedLiteral $ ListLiteral False Nothing [intExp 0, intExp 1, intExp 2] prettyPrint litExp `shouldBe` "[0, 1, 2]" it "prints parameterized types" $ do let consName = ConstructorName (TypeName (SimpleIdentifier' (SimpleIdentifier "Map")) (Just (TypeArgumentList [tyName "int", tyName "View"]))) Nothing let exp = InstanceCreationExpression NCNew consName (ArgumentList []) prettyPrint exp `shouldBe` "new Map()" it "prints a switch statement" $ do let switchMembers = [ SwitchCase [] (strLitExp "CLOSED") [funStmt "executeClosed", (BreakStatement Nothing)] , SwitchCase [] (strLitExp "PENDING") [funStmt "executePending", (BreakStatement Nothing)] , SwitchCase [] (strLitExp "APPROVED") [funStmt "executeApproved", (BreakStatement Nothing)] , SwitchCase [] (strLitExp "DENIED") [funStmt "executeDenied", (BreakStatement Nothing)] , SwitchCase [] (strLitExp "OPEN") [funStmt "executeOpen", (BreakStatement Nothing)] , SwitchDefault [] [funStmt "executeUnknown"] ] switch = SwitchStatement (identExp "command") switchMembers prettyPrint switch `shouldBe` "switch (command)\n\ \{\n\ \ case \"CLOSED\":\n\ \ executeClosed();\n\ \ break;\n\ \ case \"PENDING\":\n\ \ executePending();\n\ \ break;\n\ \ case \"APPROVED\":\n\ \ executeApproved();\n\ \ break;\n\ \ case \"DENIED\":\n\ \ executeDenied();\n\ \ break;\n\ \ case \"OPEN\":\n\ \ executeOpen();\n\ \ break;\n\ \ default:\n\ \ executeUnknown();\n\ \}" it "prints a try statement" $ do let arg = Literal' . StringLiteral' . SingleStringLiteral' $ StringInterpolation [ InterpolationString "Error: " , InterpolationExpression (identExp "e") ] try = TryStatement (Block [funStmt "breedMoreLlamas"]) [CatchClause Nothing (SimpleIdentifier "e") Nothing (Block [funStmt1 "print" arg])] (Just (Block [funStmt "cleanLlamaStalls"])) prettyPrint try `shouldBe` "try\n\ \{\n\ \ breedMoreLlamas();\n\ \}\n\ \catch (e)\n\ \{\n\ \ print(\"Error: ${e}\");\n\ \}\n\ \finally\n\ \{\n\ \ cleanLlamaStalls();\n\ \}" it "prints a constructor" $ do let varDeclList ty name = VariableDeclarationList Nothing [] (FCVTType (tyName ty)) [VariableDeclaration (SimpleIdentifier name) Nothing] returnType = SimpleIdentifier' (SimpleIdentifier "Point") consFormals1 = FormalParameterList [ NormalFormalParameter' (FieldFormalParameter Nothing [] Nothing True (SimpleIdentifier "x")) , NormalFormalParameter' (FieldFormalParameter Nothing [] Nothing True (SimpleIdentifier "y")) ] consFormals2 = FormalParameterList [ NormalFormalParameter' ( FieldFormalParameter Nothing [] (Just (FCVTType (tyName "Map"))) False (SimpleIdentifier "json")) ] methodFormals = FormalParameterList [ NormalFormalParameter' ( FieldFormalParameter Nothing [] (Just (FCVTType (tyName "Point"))) False (SimpleIdentifier "other")) ] consBody = BlockFunctionBody Sync (Block [ ExpressionStatement ( AssignmentExpression (identExp "x") "=" (IndexExpressionForTarget (identExp "json") (strLitExp "x"))) , ExpressionStatement ( AssignmentExpression (identExp "y") "=" (IndexExpressionForTarget (identExp "json") (strLitExp "y"))) ]) exp1 = BinaryExpression (identExp "x") "-" (PropertyAccess (identExp "other") (SimpleIdentifier "x")) exp2 = BinaryExpression (identExp "y") "-" (PropertyAccess (identExp "other") (SimpleIdentifier "y")) methodBody = BlockFunctionBody Sync (Block [ VariableDeclarationStatement ( VariableDeclarationList Nothing [] FCVTVar [VariableDeclaration (SimpleIdentifier "dx") (Just exp1)]) , VariableDeclarationStatement ( VariableDeclarationList Nothing [] FCVTVar [VariableDeclaration (SimpleIdentifier "dy") (Just exp2)]) , ReturnStatement (Just ( InvocationExpression ( MethodInvocation Nothing (SimpleIdentifier "sqrt") Nothing (ArgumentList [BinaryExpression (BinaryExpression (identExp "dx") "*" (identExp "dx")) "+" (BinaryExpression (identExp "dy") "*" (identExp "dy"))])))) ]) classMembers = [ FieldDeclaration Nothing [] False (varDeclList "num" "x") , FieldDeclaration Nothing [] False (varDeclList "num" "y") , ConstructorDeclaration Nothing [] False False False returnType Nothing consFormals1 [] Nothing Nothing , ConstructorDeclaration Nothing [] False False False returnType (Just (SimpleIdentifier "fromJson")) consFormals2 [] Nothing (Just consBody) , MethodDeclaration Nothing [] False Nothing (Just (tyName "num")) Empty False (SimpleIdentifier "distanceTo") Nothing (Just methodFormals) methodBody ] clazz = ClassDeclaration Nothing [] False (SimpleIdentifier "Point") Nothing Nothing Nothing Nothing classMembers prettyPrint clazz `shouldBe` "class Point\n\ \{\n\ \ num x;\n\ \ num y;\n\ \ Point(this.x, this.y);\n\ \ Point.fromJson(Map json)\n\ \ {\n\ \ x = json[\"x\"];\n\ \ y = json[\"y\"];\n\ \ }\n\ \ num distanceTo(Point other)\n\ \ {\n\ \ var dx = x - other.x;\n\ \ var dy = y - other.y;\n\ \ return sqrt(dx * dx + dy * dy);\n\ \ }\n\ \}" it "prints the initializer lists of a constructor" $ do let varDeclList ty name = VariableDeclarationList Nothing [] (FCVTType (tyName ty)) [VariableDeclaration (SimpleIdentifier name) Nothing] returnType = SimpleIdentifier' (SimpleIdentifier "Point") formals = FormalParameterList [ NormalFormalParameter' (FieldFormalParameter Nothing [] Nothing True (SimpleIdentifier "x")) , NormalFormalParameter' (FieldFormalParameter Nothing [] Nothing True (SimpleIdentifier "y")) ] exp = InvocationExpression ( MethodInvocation Nothing (SimpleIdentifier "sqrt") Nothing (ArgumentList [BinaryExpression (BinaryExpression (identExp "x") "*" (identExp "x")) "+" (BinaryExpression (identExp "y") "*" (identExp "y"))])) classMembers = [ FieldDeclaration Nothing [] False (varDeclList "num" "x") , FieldDeclaration Nothing [] False (varDeclList "num" "y") , FieldDeclaration Nothing [] False (varDeclList "num" "distanceFromOrigin") , ConstructorDeclaration Nothing [] False False False returnType Nothing formals [ ConstructorFieldInitializer False (SimpleIdentifier "x") (identExp "x") , ConstructorFieldInitializer False (SimpleIdentifier "y") (identExp "y") , ConstructorFieldInitializer False (SimpleIdentifier "distanceFromOrigin") exp ] Nothing Nothing ] clazz = ClassDeclaration Nothing [] False (SimpleIdentifier "Point") Nothing Nothing Nothing Nothing classMembers prettyPrint clazz `shouldBe` "class Point\n\ \{\n\ \ num x;\n\ \ num y;\n\ \ num distanceFromOrigin;\n\ \ Point(this.x, this.y) : x = x,\n\ \ y = y,\n\ \ distanceFromOrigin = sqrt(x * x + y * y);\n\ \}" it "prints the implement clause" $ do let implements = ImplementsClause [tyName "Comparable", tyName "Location"] clazz = ClassDeclaration Nothing [] False (SimpleIdentifier "Point") Nothing Nothing Nothing (Just implements) [] prettyPrint clazz `shouldBe` "class Point implements Comparable, Location\n\ \{\n\ \}" it "prints the metadata" $ do let annotation = Annotation (SimpleIdentifier' (SimpleIdentifier "override")) Nothing Nothing methodFormals = FormalParameterList [ NormalFormalParameter' ( FieldFormalParameter Nothing [] (Just (FCVTType (tyName "Invocation"))) False (SimpleIdentifier "mirror")) ] methodDecl = MethodDeclaration Nothing [annotation] False Nothing (Just (tyName "void")) Empty False (SimpleIdentifier "noSuchMethod") Nothing (Just methodFormals) (BlockFunctionBody Sync (Block [])) prettyPrint methodDecl `shouldBe` "@override\n\ \void noSuchMethod(Invocation mirror)\n\ \{\n\ \}" main :: IO () main = hspec $ do prettyPrintSpec