From 8411186bc03f572c0b13628c992012b6efbd2735 Mon Sep 17 00:00:00 2001 From: Kalle Virtaneva Date: Wed, 30 Jul 2025 08:02:44 -0400 Subject: [PATCH 1/5] tests: add a test case to replicate issue with Maybe's in tuples --- test/Generic.hs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/Generic.hs b/test/Generic.hs index ea6d05b..e03f354 100644 --- a/test/Generic.hs +++ b/test/Generic.hs @@ -25,6 +25,13 @@ $(deriveTypeScript defaultOptions ''Complex3) data Complex4 k = Product4 { record4 :: Map Text k } $(deriveTypeScript defaultOptions ''Complex4) +-- Test for Maybe inside tuple +data TestMaybeTuple + = ConWithMaybe Text [Int] (Maybe [String]) + | SimpleConstructor Text + deriving (Show, Eq) +$(deriveTypeScript defaultOptions ''TestMaybeTuple) + tests :: SpecWith () tests = describe "Generic instances" $ do it [i|Complex makes the declaration and types correctly|] $ do @@ -57,5 +64,15 @@ tests = describe "Generic instances" $ do ,TSTypeAlternatives "Complex4" ["T"] ["IProduct4"] Nothing ] + it [i|TestMaybeTuple should handle Maybe in tuples correctly|] $ do + (getTypeScriptDeclarationsRecursively (Proxy :: Proxy TestMaybeTuple)) `shouldBe` [ + TSTypeAlternatives "IConWithMaybe" [] ["[string, number[], string[]]"] Nothing -- This is what currently happens + -- The correct output should be: ["[string, number[], string[] | null]"] + ,TSTypeAlternatives "ISimpleConstructor" [] ["string"] Nothing + ,TSInterfaceDeclaration "IConWithMaybe" [] [TSField False "tag" "\"ConWithMaybe\"" Nothing, TSField False "contents" "IConWithMaybe" Nothing] Nothing + ,TSInterfaceDeclaration "ISimpleConstructor" [] [TSField False "tag" "\"SimpleConstructor\"" Nothing, TSField False "contents" "string" Nothing] Nothing + ,TSTypeAlternatives "TestMaybeTuple" [] ["IConWithMaybe","ISimpleConstructor"] Nothing + ] + main :: IO () main = hspec tests From 4bf5fb515ff9d1363b60e7cf53a699b026b44970 Mon Sep 17 00:00:00 2001 From: kallevmercury <164934876+kallevmercury@users.noreply.github.com> Date: Wed, 30 Jul 2025 08:50:51 -0400 Subject: [PATCH 2/5] chore: simplify test --- test/Generic.hs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/Generic.hs b/test/Generic.hs index e03f354..463aab8 100644 --- a/test/Generic.hs +++ b/test/Generic.hs @@ -66,10 +66,7 @@ tests = describe "Generic instances" $ do it [i|TestMaybeTuple should handle Maybe in tuples correctly|] $ do (getTypeScriptDeclarationsRecursively (Proxy :: Proxy TestMaybeTuple)) `shouldBe` [ - TSTypeAlternatives "IConWithMaybe" [] ["[string, number[], string[]]"] Nothing -- This is what currently happens - -- The correct output should be: ["[string, number[], string[] | null]"] - ,TSTypeAlternatives "ISimpleConstructor" [] ["string"] Nothing - ,TSInterfaceDeclaration "IConWithMaybe" [] [TSField False "tag" "\"ConWithMaybe\"" Nothing, TSField False "contents" "IConWithMaybe" Nothing] Nothing + TSInterfaceDeclaration "IConWithMaybe" [] [TSField False "tag" "\"ConWithMaybe\"" Nothing, TSField False "contents" "[string, number[], string[] | null]" Nothing] Nothing ,TSInterfaceDeclaration "ISimpleConstructor" [] [TSField False "tag" "\"SimpleConstructor\"" Nothing, TSField False "contents" "string" Nothing] Nothing ,TSTypeAlternatives "TestMaybeTuple" [] ["IConWithMaybe","ISimpleConstructor"] Nothing ] From 674ffb552ce8230f9d345d6391263f4e2f1c3a08 Mon Sep 17 00:00:00 2001 From: Kalle Virtaneva Date: Wed, 30 Jul 2025 09:16:03 -0400 Subject: [PATCH 3/5] fix: possible fix for the Maybe tuple -> nullable TS list issue --- src/Data/Aeson/TypeScript/Instances.hs | 14 +++++++++++--- src/Data/Aeson/TypeScript/Types.hs | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Data/Aeson/TypeScript/Instances.hs b/src/Data/Aeson/TypeScript/Instances.hs index 988aec0..fe2a7e3 100644 --- a/src/Data/Aeson/TypeScript/Instances.hs +++ b/src/Data/Aeson/TypeScript/Instances.hs @@ -100,6 +100,14 @@ instance TypeScript Word32 where instance TypeScript Word64 where getTypeScriptType _ = "number" +-- | Helper function to get TypeScript type with proper null handling for optional types +getTypeScriptTypeWithNull :: forall a. TypeScript a => Proxy a -> String +getTypeScriptTypeWithNull p = + let baseType = getTypeScriptType p + in if getTypeScriptOptional p + then baseType <> " | null" + else baseType + instance {-# OVERLAPPABLE #-} (TypeScript a) => TypeScript [a] where getTypeScriptType _ = (getTypeScriptType (Proxy :: Proxy a)) ++ "[]" getParentTypes _ = [TSType (Proxy :: Proxy a)] @@ -122,20 +130,20 @@ instance (TypeScript a, TypeScript b) => TypeScript (Either a b) where ] instance (TypeScript a, TypeScript b) => TypeScript (a, b) where - getTypeScriptType _ = [i|[#{getTypeScriptType (Proxy :: Proxy a)}, #{getTypeScriptType (Proxy :: Proxy b)}]|] + getTypeScriptType _ = [i|[#{getTypeScriptTypeWithNull (Proxy :: Proxy a)}, #{getTypeScriptTypeWithNull (Proxy :: Proxy b)}]|] getParentTypes _ = L.nub [ (TSType (Proxy :: Proxy a)) , (TSType (Proxy :: Proxy b)) ] instance (TypeScript a, TypeScript b, TypeScript c) => TypeScript (a, b, c) where - getTypeScriptType _ = [i|[#{getTypeScriptType (Proxy :: Proxy a)}, #{getTypeScriptType (Proxy :: Proxy b)}, #{getTypeScriptType (Proxy :: Proxy c)}]|] + getTypeScriptType _ = [i|[#{getTypeScriptTypeWithNull (Proxy :: Proxy a)}, #{getTypeScriptTypeWithNull (Proxy :: Proxy b)}, #{getTypeScriptTypeWithNull (Proxy :: Proxy c)}]|] getParentTypes _ = L.nub [ (TSType (Proxy :: Proxy a)) , (TSType (Proxy :: Proxy b)) , (TSType (Proxy :: Proxy c)) ] instance (TypeScript a, TypeScript b, TypeScript c, TypeScript d) => TypeScript (a, b, c, d) where - getTypeScriptType _ = [i|[#{getTypeScriptType (Proxy :: Proxy a)}, #{getTypeScriptType (Proxy :: Proxy b)}, #{getTypeScriptType (Proxy :: Proxy c)}, #{getTypeScriptType (Proxy :: Proxy d)}]|] + getTypeScriptType _ = [i|[#{getTypeScriptTypeWithNull (Proxy :: Proxy a)}, #{getTypeScriptTypeWithNull (Proxy :: Proxy b)}, #{getTypeScriptTypeWithNull (Proxy :: Proxy c)}, #{getTypeScriptTypeWithNull (Proxy :: Proxy d)}]|] getParentTypes _ = L.nub [ (TSType (Proxy :: Proxy a)) , (TSType (Proxy :: Proxy b)) , (TSType (Proxy :: Proxy c)) diff --git a/src/Data/Aeson/TypeScript/Types.hs b/src/Data/Aeson/TypeScript/Types.hs index a96f635..f7708a0 100644 --- a/src/Data/Aeson/TypeScript/Types.hs +++ b/src/Data/Aeson/TypeScript/Types.hs @@ -216,9 +216,9 @@ defaultExtraTypeScriptOptions = ExtraTypeScriptOptions [] Nothing stripStartEach stripStartEachLine :: String -> String stripStartEachLine s = s & T.pack - & T.splitOn "\n" + & T.splitOn (T.pack "\n") & fmap T.stripStart - & T.intercalate "\n" + & T.intercalate (T.pack "\n") & T.unpack data ExtraDeclOrGenericInfo = ExtraDecl Exp From a8fcfa59f29eb562a1c97619fb9a493b5999c2ac Mon Sep 17 00:00:00 2001 From: Kalle Virtaneva Date: Wed, 30 Jul 2025 09:18:21 -0400 Subject: [PATCH 4/5] chore: reset extra change --- src/Data/Aeson/TypeScript/Types.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Aeson/TypeScript/Types.hs b/src/Data/Aeson/TypeScript/Types.hs index f7708a0..a96f635 100644 --- a/src/Data/Aeson/TypeScript/Types.hs +++ b/src/Data/Aeson/TypeScript/Types.hs @@ -216,9 +216,9 @@ defaultExtraTypeScriptOptions = ExtraTypeScriptOptions [] Nothing stripStartEach stripStartEachLine :: String -> String stripStartEachLine s = s & T.pack - & T.splitOn (T.pack "\n") + & T.splitOn "\n" & fmap T.stripStart - & T.intercalate (T.pack "\n") + & T.intercalate "\n" & T.unpack data ExtraDeclOrGenericInfo = ExtraDecl Exp From 8cba73cf9b1c661d3b31a6fe34fcf399a563365b Mon Sep 17 00:00:00 2001 From: kallevmercury <164934876+kallevmercury@users.noreply.github.com> Date: Wed, 30 Jul 2025 09:19:08 -0400 Subject: [PATCH 5/5] Apply suggestions from code review --- src/Data/Aeson/TypeScript/Instances.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Aeson/TypeScript/Instances.hs b/src/Data/Aeson/TypeScript/Instances.hs index fe2a7e3..9f33c84 100644 --- a/src/Data/Aeson/TypeScript/Instances.hs +++ b/src/Data/Aeson/TypeScript/Instances.hs @@ -100,7 +100,7 @@ instance TypeScript Word32 where instance TypeScript Word64 where getTypeScriptType _ = "number" --- | Helper function to get TypeScript type with proper null handling for optional types +-- | Get TypeScript type with null handling for optional types getTypeScriptTypeWithNull :: forall a. TypeScript a => Proxy a -> String getTypeScriptTypeWithNull p = let baseType = getTypeScriptType p