keiro-dsl-0.11.0.0: test/haskell-name/Main.hs
module Main (main) where
import Data.List (permutations)
import Data.List.NonEmpty qualified as NE
import Data.Text (Text)
import Keiro.Dsl.HaskellName
import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (elements, forAll)
main :: IO ()
main = hspec $ do
describe "checked generated Haskell names" $ do
mapM_ pinnedExample pinnedExamples
it "rejects empty, leading, trailing, repeated, and all-underscore input" $ do
mapM_ (shouldReject . logicalSite) ["", "_foo", "foo_", "foo__bar", "_"]
it "admits hyphens only for wire words" $ do
derivePair LogicalWireWord (logicalSite "incident-paging")
`shouldBe` Right ("IncidentPaging", "incidentPaging")
deriveHaskellName LogicalIdentifier (logicalSite "incident-paging")
`shouldSatisfy` isLeft
it "rejects keywords created by normalization" $ do
deriveHaskellName LogicalIdentifier (logicalSite "Module")
`shouldBe` Left (ReservedGeneratedOccurrence (logicalSite "Module") "module")
it "admits contextual words accepted by the generated-language contract" $
mapM_
(\word -> checkedLowerOccurrence (logicalSite word) word `shouldSatisfy` isRight)
contextualWords
it "refuses every term-level word rejected by the generated-language contract" $
mapM_
(\word -> checkedLowerOccurrence (logicalSite word) word `shouldBe` Left (ReservedGeneratedOccurrence (logicalSite word) word))
reservedWords
it "checks explicit consumer names without recasing them" $ do
fmap renderModuleName (checkedModuleName explicitSite "Consumer.Legacy_name.Types")
`shouldBe` Right "Consumer.Legacy_name.Types"
checkedModuleName explicitSite "consumer.Types" `shouldSatisfy` isLeft
it "compares module paths case-insensitively" $ do
let first = occurrence ContextModuleSite ModuleSpace "Generated.Foo" "Foo" "foo"
second = occurrence ContextModuleSite ModuleSpace "generated.foo" "foo" "FOO"
detectNameCollisions [first, second] `shouldSatisfy` (not . null)
it "permits the same selector on different generated records" $ do
let first = scopedField "Generated.Foo" "Command" "value" "command.value"
second = scopedField "Generated.Foo" "Event" "value" "event.value"
detectNameCollisions [first, second] `shouldBe` []
prop "collision evidence is independent of source traversal order" $
forAll (elements (permutations collisionInventory)) $ \ordered ->
detectNameCollisions ordered == detectNameCollisions collisionInventory
mapM_ collisionKindExample [minBound .. maxBound]
pinnedExample :: (Text, Text, Text) -> SpecWith ()
pinnedExample (raw, upper, lower) =
it (show raw <> " -> " <> show upper <> " / " <> show lower) $
derivePair LogicalIdentifier (logicalSite raw) `shouldBe` Right (upper, lower)
pinnedExamples :: [(Text, Text, Text)]
pinnedExamples =
[ ("foo_bar", "FooBar", "fooBar"),
("fooBar", "FooBar", "fooBar"),
("ThingID", "ThingID", "thingID"),
("HTTP_server2", "HTTPServer2", "httpServer2"),
("version2_event", "Version2Event", "version2Event")
]
derivePair :: NameSourceKind -> NameSite -> Either HaskellNameError (Text, Text)
derivePair source site = do
derived <- deriveHaskellName source site
pure (renderUpperCamelName (upperCamel derived), renderLowerCamelName (lowerCamel derived))
shouldReject :: NameSite -> Expectation
shouldReject site = deriveHaskellName LogicalIdentifier site `shouldSatisfy` isLeft
isLeft :: Either a b -> Bool
isLeft = \case
Left _ -> True
Right _ -> False
isRight :: Either a b -> Bool
isRight = \case
Left _ -> False
Right _ -> True
contextualWords :: [Text]
contextualWords = ["as", "family", "mdo", "proc", "qualified", "rec", "safe", "signature", "stock", "unsafe", "via"]
reservedWords :: [Text]
reservedWords =
[ "case",
"class",
"data",
"default",
"deriving",
"do",
"else",
"foreign",
"forall",
"if",
"import",
"in",
"infix",
"infixl",
"infixr",
"instance",
"let",
"module",
"newtype",
"of",
"then",
"type",
"where"
]
logicalSite :: Text -> NameSite
logicalSite raw = NameSite GeneratedTypeSite raw ("owner:" <> raw) 1
explicitSite :: NameSite
explicitSite = NameSite ImportAliasSite "consumer-reference" "consumer" 1
occurrence :: NameSiteKind -> HaskellOccurrenceSpace -> Text -> Text -> Text -> PlannedOccurrence
occurrence kind space target rendered owner =
plannedOccurrence target space "" rendered (NameSite kind owner owner 1)
scopedField :: Text -> Text -> Text -> Text -> PlannedOccurrence
scopedField target scope rendered owner =
plannedOccurrence target FieldSpace scope rendered (NameSite GeneratedFieldSite owner owner 1)
collisionInventory :: [PlannedOccurrence]
collisionInventory =
[ occurrence GeneratedValueSite ValueSpace "Generated.Foo" "fooBar" "foo_bar",
occurrence GeneratedValueSite ValueSpace "Generated.Foo" "fooBar" "fooBar"
]
collisionKindExample :: NameSiteKind -> SpecWith ()
collisionKindExample kind =
it ("detects a normalized collision for " <> show kind) $ do
let first = occurrence kind ValueSpace "Generated.Foo" "sameName" "first"
second = occurrence kind ValueSpace "Generated.Foo" "sameName" "second"
case detectNameCollisions [second, first] of
[NormalizedNameCollision _ sites] -> map siteOwner (NE.toList sites) `shouldBe` ["first", "second"]
other -> expectationFailure ("expected one collision, got " <> show other)