wasp/waspc/test/Analyzer/EvaluatorTest.hs
Filip Sodić 86e0893d6f
Separate user code into client, server, shared (#753)
* Separate ext code to client and server

* Use skeleton in createNewProject and refactor

* Refactor Lib.hs to use ExceptT

* Fix formatting

* Pop up returns

* Extract liftIO and add a do block

Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com>

* Address some review comments

* Add skeleton comment

* Extract common CommandError message

* Separate skeleton comment into two rows

* Move server and client dirs into src

* Simplify maybeToEither

* Further refactor Lib.hs

* Further simplify skeleton comment

* Add shared code directory to project structure

* Update e2e test inputs

* Update e2e test outputs

* Fix formatting

* Fix bug in compile function

Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>

* Change map to fmap in compile function

* Fix formatting

* Force git to include empty directories

* Remove extra empty line from .gitkeep files

* Watch shared directory for changes

* Fix regular and e2e tests

* Fix cli template packaging and update todoApp

* Add a shared function demo to todoApp

* Update waspc and e2e tests

* Fix compiler warnings and rename function

* Rename mkError to mkParserError

* Remove redundant empty line

* Fix test warnings

* Fix formatting

* Fix directory tree watching on wasp start

* Implement review feedback

Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com>
Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
2022-11-11 17:20:49 +01:00

261 lines
8.5 KiB
Haskell

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module Analyzer.EvaluatorTest where
import qualified Data.Aeson as Aeson
import Data.Data (Data)
import Data.List.Split (splitOn)
import Data.Maybe (fromJust)
import qualified StrongPath as SP
import Test.Tasty.Hspec
import Text.Read (readMaybe)
import Wasp.Analyzer.Evaluator
import qualified Wasp.Analyzer.Evaluator.Evaluation as E
import qualified Wasp.Analyzer.Evaluator.EvaluationError as EvaluationError
import Wasp.Analyzer.Parser (parseStatements)
import qualified Wasp.Analyzer.Type as T
import Wasp.Analyzer.TypeChecker (typeCheck)
import qualified Wasp.Analyzer.TypeChecker.AST as TypedAST
import qualified Wasp.Analyzer.TypeDefinitions as TD
import Wasp.Analyzer.TypeDefinitions.Class.HasCustomEvaluation (HasCustomEvaluation (..))
import Wasp.Analyzer.TypeDefinitions.TH
import Wasp.AppSpec.Core.Decl (IsDecl)
import Wasp.AppSpec.Core.Ref (Ref (..))
import Wasp.AppSpec.ExtImport (ExtImport (..), ExtImportName (..))
import Wasp.AppSpec.JSON (JSON (..))
fromRight :: Show a => Either a b -> b
fromRight (Right x) = x
fromRight (Left e) = error $ show e
------- Simple -------
newtype Simple = Simple String deriving (Eq, Show, Data)
instance IsDecl Simple
makeDeclType ''Simple
------- Fields -------
data Fields = Fields {a :: String, b :: Maybe Double} deriving (Eq, Show, Data)
instance IsDecl Fields
makeDeclType ''Fields
------ Business ------
data Person = Person {name :: String, age :: Integer} deriving (Eq, Show, Data)
instance IsDecl Person
makeDeclType ''Person
data BusinessType = Manufacturer | Seller | Store deriving (Eq, Show, Data)
makeEnumType ''BusinessType
data Business = Business
{ employees :: [Ref Person],
worth :: Double,
businessType :: BusinessType,
location :: Maybe String
}
deriving (Eq, Show, Data)
instance IsDecl Business
makeDeclType ''Business
-------- Special --------
data Special = Special {imps :: [ExtImport], json :: JSON} deriving (Eq, Show)
instance IsDecl Special
makeDeclType ''Special
------ HasCustomEvaluation ------
data SemanticVersion = SemanticVersion Int Int Int
deriving (Eq, Show, Data)
instance HasCustomEvaluation SemanticVersion where
waspType = T.StringType
evaluation = E.evaluation' . TypedAST.withCtx $ \ctx -> \case
TypedAST.StringLiteral str -> case splitOn "." str of
[major, minor, patch] ->
maybe
( Left $
EvaluationError.mkEvaluationError ctx $
EvaluationError.ParseError $
EvaluationError.EvaluationParseError
"Failed parsing semantic version -> some part is not int"
)
pure
$ do
majorInt <- readMaybe @Int major
minorInt <- readMaybe @Int minor
patchInt <- readMaybe @Int patch
return $ SemanticVersion majorInt minorInt patchInt
_ ->
Left $
EvaluationError.mkEvaluationError ctx $
EvaluationError.ParseError $
EvaluationError.EvaluationParseError
"Failed parsing semantic version -> it doesn't have 3 comma separated parts."
expr ->
Left $
EvaluationError.mkEvaluationError ctx $
EvaluationError.ExpectedType T.StringType (TypedAST.exprType expr)
data Custom = Custom
{version :: SemanticVersion}
deriving (Eq, Show, Data)
instance IsDecl Custom
makeDeclType ''Custom
--------------------------------
------ Tuples ------
data Tuples = Tuples
{ pair :: (String, Integer),
triple :: (String, Integer, Integer),
quadruple :: (String, Integer, Integer, [Bool])
}
deriving (Eq, Show, Data)
instance IsDecl Tuples
makeDeclType ''Tuples
--------------------
-------- Special --------
data AllJson = AllJson
{ objectValue :: JSON,
arrayValue :: JSON,
stringValue :: JSON,
nullValue :: JSON,
booleanValue :: JSON
}
deriving (Eq, Show)
instance IsDecl AllJson
makeDeclType ''AllJson
eval :: TD.TypeDefinitions -> [String] -> Either EvaluationError [Decl]
eval typeDefs source = evaluate typeDefs . fromRight . typeCheck typeDefs . fromRight . parseStatements $ unlines source
spec_Evaluator :: Spec
spec_Evaluator = do
describe "Analyzer.Evaluator" $
describe "evaluate" $ do
it "Evaluates a simple declaration" $ do
let typeDefs = TD.addDeclType @Simple $ TD.empty
let decls = eval typeDefs ["simple Test \"hello wasp\""]
fmap takeDecls decls
`shouldBe` Right [("Test", Simple "hello wasp")]
it "Evaluates a declaration with a dictionary" $ do
let typeDefs = TD.addDeclType @Fields $ TD.empty
let decls = eval typeDefs ["fields Test { a: \"hello wasp\", b: 3.14 }"]
fmap takeDecls decls
`shouldBe` Right [("Test", Fields {a = "hello wasp", b = Just 3.14})]
it "Evaluates a declaration with missing optional fields" $ do
let typeDefs = TD.addDeclType @Fields $ TD.empty
let decls = eval typeDefs ["fields Test { a: \"hello wasp\" }"]
fmap takeDecls decls
`shouldBe` Right [("Test", Fields {a = "hello wasp", b = Nothing})]
it "Evaluates a complicated example" $ do
let typeDefs =
TD.addDeclType @Business $
TD.addEnumType @BusinessType $
TD.addDeclType @Person $ TD.empty
let source =
[ "person Tim { name: \"Tim Stocker\", age: 40 }",
"person John { name: \"John Cashier\", age: 23 }",
"business Grocer { employees: [Tim, John], businessType: Store, worth: 115 }"
]
fmap takeDecls (eval typeDefs source)
`shouldBe` Right
[ ( "Grocer",
Business
{ employees = [Ref "Tim", Ref "John"],
businessType = Store,
worth = 115.0,
location = Nothing
}
)
]
it "Evaluates ExtImports and JSON" $ do
let typeDefs = TD.addDeclType @Special $ TD.empty
let source =
[ "special Test {",
" imps: [import { field } from \"@server/main.js\", import main from \"@server/main.js\"],",
" json: {=json { \"key\": 1 } json=}",
"}"
]
fmap takeDecls (eval typeDefs source)
`shouldBe` Right
[ ( "Test",
Special
[ ExtImport (ExtImportField "field") (fromJust $ SP.parseRelFileP "main.js"),
ExtImport (ExtImportModule "main") (fromJust $ SP.parseRelFileP "main.js")
]
(JSON $ Aeson.object ["key" Aeson..= (1 :: Integer)])
)
]
it "Evaluates JSON quoters and they show correctly" $ do
let typeDefs = TD.addDeclType @AllJson $ TD.empty
let source =
[ "allJson Test {",
" objectValue: {=json { \"key\": 1 } json=},",
" arrayValue: {=json [1, 2, 3] json=},",
" stringValue: {=json \"hello\" json=},",
" nullValue: {=json null json=},",
" booleanValue: {=json false json=},",
"}"
]
let Right [("Test", allJson)] = takeDecls <$> eval typeDefs source
show (objectValue allJson) `shouldBe` "{\"key\":1}"
show (arrayValue allJson) `shouldBe` "[1,2,3]"
show (stringValue allJson) `shouldBe` "\"hello\""
show (nullValue allJson) `shouldBe` "null"
show (booleanValue allJson) `shouldBe` "false"
it "Evaluates a declaration with a field that has custom evaluation" $ do
let typeDefs = TD.addDeclType @Custom $ TD.empty
let decls = eval typeDefs ["custom Test { version: \"1.2.3\" }"]
fmap takeDecls decls
`shouldBe` Right [("Test", Custom {version = SemanticVersion 1 2 3})]
it "Evaluates a declaration with fields that are tuples" $ do
let typeDefs = TD.addDeclType @Tuples $ TD.empty
let source =
[ "tuples Tuples {",
" pair: (\"foo\", 1),",
" triple: (\"foo\", 1, 2),",
" quadruple: (\"foo\", 1, 2, [true, false])",
"}"
]
fmap takeDecls (eval typeDefs source)
`shouldBe` Right
[ ( "Tuples",
Tuples
{ pair = ("foo", 1),
triple = ("foo", 1, 2),
quadruple = ("foo", 1, 2, [True, False])
}
)
]