packages feed

aeson-typescript 0.2.0.0 → 0.3.0.0

raw patch · 32 files changed

+1356/−389 lines, 32 filesdep ~th-abstractionPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: th-abstraction

API changes (from Hackage documentation)

- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T1
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T10
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T2
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T3
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T4
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T5
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T6
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T7
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T8
- Data.Aeson.TypeScript.TH: instance Data.Aeson.TypeScript.Types.TypeScript Data.Aeson.TypeScript.TH.T9
+ Data.Aeson.TypeScript.Internal: TSInterfaceDeclaration :: String -> [String] -> [TSField] -> TSDeclaration
+ Data.Aeson.TypeScript.Internal: TSRawDeclaration :: String -> TSDeclaration
+ Data.Aeson.TypeScript.Internal: TSTypeAlternatives :: String -> [String] -> [String] -> TSDeclaration
+ Data.Aeson.TypeScript.Internal: [alternativeTypes] :: TSDeclaration -> [String]
+ Data.Aeson.TypeScript.Internal: [interfaceGenericVariables] :: TSDeclaration -> [String]
+ Data.Aeson.TypeScript.Internal: [interfaceMembers] :: TSDeclaration -> [TSField]
+ Data.Aeson.TypeScript.Internal: [interfaceName] :: TSDeclaration -> String
+ Data.Aeson.TypeScript.Internal: [text] :: TSDeclaration -> String
+ Data.Aeson.TypeScript.Internal: [typeGenericVariables] :: TSDeclaration -> [String]
+ Data.Aeson.TypeScript.Internal: [typeName] :: TSDeclaration -> String
+ Data.Aeson.TypeScript.Internal: data TSDeclaration
+ Data.Aeson.TypeScript.Recursive: getTransitiveClosure :: Set TSType -> Set TSType
+ Data.Aeson.TypeScript.Recursive: getTypeScriptDeclarationsRecursively :: TypeScript a => Proxy a -> [TSDeclaration]
+ Data.Aeson.TypeScript.TH: Enum :: SumTypeFormat
+ Data.Aeson.TypeScript.TH: EnumWithType :: SumTypeFormat
+ Data.Aeson.TypeScript.TH: ExportEach :: ExportMode
+ Data.Aeson.TypeScript.TH: ExportNone :: ExportMode
+ Data.Aeson.TypeScript.TH: ExtraTypeScriptOptions :: [Name] -> ExtraTypeScriptOptions
+ Data.Aeson.TypeScript.TH: T :: T
+ Data.Aeson.TypeScript.TH: T1 :: T1
+ Data.Aeson.TypeScript.TH: T2 :: T2
+ Data.Aeson.TypeScript.TH: T3 :: T3
+ Data.Aeson.TypeScript.TH: TSRawDeclaration :: String -> TSDeclaration
+ Data.Aeson.TypeScript.TH: TSType :: Proxy a -> TSType
+ Data.Aeson.TypeScript.TH: TypeAlias :: SumTypeFormat
+ Data.Aeson.TypeScript.TH: [exportMode] :: FormattingOptions -> ExportMode
+ Data.Aeson.TypeScript.TH: [typeAlternativesFormat] :: FormattingOptions -> SumTypeFormat
+ Data.Aeson.TypeScript.TH: [typeFamiliesToMapToTypeScript] :: ExtraTypeScriptOptions -> [Name]
+ Data.Aeson.TypeScript.TH: [unTSType] :: TSType -> Proxy a
+ Data.Aeson.TypeScript.TH: data ExportMode
+ Data.Aeson.TypeScript.TH: data ExtraTypeScriptOptions
+ Data.Aeson.TypeScript.TH: data SumTypeFormat
+ Data.Aeson.TypeScript.TH: data T
+ Data.Aeson.TypeScript.TH: data T1
+ Data.Aeson.TypeScript.TH: data T2
+ Data.Aeson.TypeScript.TH: data T3
+ Data.Aeson.TypeScript.TH: data TSType
+ Data.Aeson.TypeScript.TH: deriveJSONAndTypeScript' :: Options -> Name -> ExtraTypeScriptOptions -> Q [Dec]
+ Data.Aeson.TypeScript.TH: deriveTypeScript' :: Options -> Name -> ExtraTypeScriptOptions -> Q [Dec]
+ Data.Aeson.TypeScript.TH: deriveTypeScriptLookupType :: Name -> String -> Q [Dec]
+ Data.Aeson.TypeScript.TH: getParentTypes :: TypeScript a => Proxy a -> [TSType]
+ Data.Aeson.TypeScript.TH: isGenericVariable :: TypeScript a => Proxy a -> Bool
- Data.Aeson.TypeScript.TH: FormattingOptions :: Int -> (String -> String) -> (String -> String) -> FormattingOptions
+ Data.Aeson.TypeScript.TH: FormattingOptions :: Int -> (String -> String) -> (String -> String) -> ExportMode -> SumTypeFormat -> FormattingOptions
- Data.Aeson.TypeScript.TH: class TypeScript a
+ Data.Aeson.TypeScript.TH: class (Typeable a) => TypeScript a

Files

+ CHANGELOG.md view
@@ -0,0 +1,19 @@+# Change log++## 0.3.0.0++* Update th-abstraction dependency to < 0.5 to support working with newer Stack LTS.+* Major refactors to improve TH quality.+* Tracking of parent types to allow recursive deriving+  * The `getParentTypes` function was added to the main typeclass.+  * The new `Data.Aeson.TypeScript.Recursive` module for working with recursive definitions.+* New support for mapping Haskell closed type families to TypeScript lookup types.++## 0.2.0.0++* New formatting option `interfaceNameModifier`.++## 0.1.0.0++* Initial release.+
− ChangeLog.md
@@ -1,3 +0,0 @@-# Changelog for aeson-typescript--## Unreleased changes
README.md view
@@ -1,5 +1,5 @@ -# Welcome to `aeson-typescript` [![Hackage](https://img.shields.io/hackage/v/aeson-typescript.svg)](https://hackage.haskell.org/package/aeson-typescript) [![Build Status](https://travis-ci.org/codedownio/aeson-typescript.svg)](https://travis-ci.org/codedownio/aeson-typescript)+# Welcome to `aeson-typescript` [![Hackage](https://img.shields.io/hackage/v/aeson-typescript.svg)](https://hackage.haskell.org/package/aeson-typescript) ![aeson-typescript](https://github.com/codedownio/aeson-typescript/workflows/aeson-typescript/badge.svg)  This library provides a way to generate TypeScript `.d.ts` files that match your existing Aeson `ToJSON` instances. If you already use Aeson's Template Haskell support to derive your instances, then deriving TypeScript is as simple as
aeson-typescript.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.1.+-- This file has been generated from package.yaml by hpack version 0.34.4. -- -- see: https://github.com/sol/hpack ----- hash: 74aae5bd5d3353838039530c9ce854fa79c1a282fa2d73d232a7d5677ad341e2+-- hash: 73021b1b79e95ae7e4b67a45fafb5d452d8ab05ae59d4071098e8628cf8f29e4  name:           aeson-typescript-version:        0.2.0.0+version:        0.3.0.0 synopsis:       Generate TypeScript definition files from your ADTs description:    Please see the README on Github at <https://github.com/codedownio/aeson-typescript#readme> category:       Text, Web, JSON@@ -15,14 +15,15 @@ bug-reports:    https://github.com/codedownio/aeson-typescript/issues author:         Tom McLaughlin maintainer:     tom@codedown.io-copyright:      2018 CodeDown+copyright:      2021 CodeDown license:        BSD3 license-file:   LICENSE-tested-with:    GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.2+tested-with:+    GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.2 build-type:     Simple extra-source-files:     README.md-    ChangeLog.md+    CHANGELOG.md     test/assets/package.json     test/assets/npm_install.sh     test/assets/yarn_install.sh@@ -33,6 +34,17 @@   location: https://github.com/codedownio/aeson-typescript  library+  exposed-modules:+      Data.Aeson.TypeScript.TH+      Data.Aeson.TypeScript.Internal+      Data.Aeson.TypeScript.Recursive+  other-modules:+      Data.Aeson.TypeScript.Formatting+      Data.Aeson.TypeScript.Instances+      Data.Aeson.TypeScript.Lookup+      Data.Aeson.TypeScript.Types+      Data.Aeson.TypeScript.Util+      Paths_aeson_typescript   hs-source-dirs:       src   build-depends:@@ -43,20 +55,42 @@     , mtl     , template-haskell     , text-    , th-abstraction <0.4+    , th-abstraction <0.5     , unordered-containers-  exposed-modules:-      Data.Aeson.TypeScript.TH-  other-modules:-      Data.Aeson.TypeScript.Formatting-      Data.Aeson.TypeScript.Instances-      Data.Aeson.TypeScript.Types-      Paths_aeson_typescript   default-language: Haskell2010  test-suite aeson-typescript-test   type: exitcode-stdio-1.0   main-is: Spec.hs+  other-modules:+      Basic+      Formatting+      HigherKind+      Live+      Live2+      LiveLogging+      NoOmitNothingFields+      ObjectWithSingleFieldNoTagSingleConstructors+      ObjectWithSingleFieldTagSingleConstructors+      OmitNothingFields+      TaggedObjectNoTagSingleConstructors+      TaggedObjectTagSingleConstructors+      TestBoilerplate+      TwoElemArrayNoTagSingleConstructors+      TwoElemArrayTagSingleConstructors+      TypeFamilies+      UntaggedNoTagSingleConstructors+      UntaggedTagSingleConstructors+      Util+      Data.Aeson.TypeScript.Formatting+      Data.Aeson.TypeScript.Instances+      Data.Aeson.TypeScript.Internal+      Data.Aeson.TypeScript.Lookup+      Data.Aeson.TypeScript.Recursive+      Data.Aeson.TypeScript.TH+      Data.Aeson.TypeScript.Types+      Data.Aeson.TypeScript.Util+      Paths_aeson_typescript   hs-source-dirs:       test       src@@ -76,23 +110,6 @@     , template-haskell     , temporary     , text-    , th-abstraction <0.4+    , th-abstraction <0.5     , unordered-containers-  other-modules:-      HigherKind-      ObjectWithSingleFieldNoTagSingleConstructors-      ObjectWithSingleFieldTagSingleConstructors-      TaggedObjectNoTagSingleConstructors-      TaggedObjectTagSingleConstructors-      TestBoilerplate-      TwoElemArrayNoTagSingleConstructors-      TwoElemArrayTagSingleConstructors-      UntaggedNoTagSingleConstructors-      UntaggedTagSingleConstructors-      Util-      Data.Aeson.TypeScript.Formatting-      Data.Aeson.TypeScript.Instances-      Data.Aeson.TypeScript.TH-      Data.Aeson.TypeScript.Types-      Paths_aeson_typescript   default-language: Haskell2010
src/Data/Aeson/TypeScript/Formatting.hs view
@@ -1,12 +1,16 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns #-}+{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, CPP #-}  module Data.Aeson.TypeScript.Formatting where  import Data.Aeson.TypeScript.Types-import Data.Monoid import Data.String.Interpolate.IsString import qualified Data.Text as T +#if !MIN_VERSION_base(4,11,0)+import Data.Monoid+#endif++ -- | Same as 'formatTSDeclarations'', but uses default formatting options. formatTSDeclarations :: [TSDeclaration] -> String formatTSDeclarations = formatTSDeclarations' defaultFormattingOptions@@ -14,21 +18,49 @@ -- | Format a single TypeScript declaration. This version accepts a FormattingOptions object in case you want more control over the output. formatTSDeclaration :: FormattingOptions -> TSDeclaration -> String formatTSDeclaration (FormattingOptions {..}) (TSTypeAlternatives name genericVariables names) =-  [i|type #{typeNameModifier name}#{getGenericBrackets genericVariables} = #{alternatives};|]-  where alternatives = T.intercalate " | " (fmap T.pack names)+  case typeAlternativesFormat of+    Enum -> [i|#{exportPrefix exportMode}enum #{typeNameModifier name} { #{alternativesEnum} }|]+    EnumWithType -> [i|#{exportPrefix exportMode}enum #{typeNameModifier name} { #{alternativesEnumWithType} }#{enumType}|]+    TypeAlias -> [i|#{exportPrefix exportMode}type #{typeNameModifier name}#{getGenericBrackets genericVariables} = #{alternatives};|]+  where+    alternatives = T.intercalate " | " (fmap T.pack names)+    alternativesEnum = T.intercalate ", " $ [toEnumName entry | entry <- T.pack <$> names]+    alternativesEnumWithType = T.intercalate ", " $ [toEnumName entry <> "=" <> entry | entry <- T.pack <$> names]+    enumType = [i|\n\ntype #{name} = keyof typeof #{typeNameModifier name};|]+    toEnumName = T.replace "\"" ""  formatTSDeclaration (FormattingOptions {..}) (TSInterfaceDeclaration interfaceName genericVariables members) =-  [i|interface #{modifiedInterfaceName}#{getGenericBrackets genericVariables} {-#{lines}-}|] where lines = T.intercalate "\n" $ fmap T.pack [(replicate numIndentSpaces ' ') <> formatTSField member <> ";"| member <- members]-          modifiedInterfaceName = (\(i, name) -> i <> interfaceNameModifier name) . splitAt 1 $ interfaceName+  [i|#{exportPrefix exportMode}interface #{modifiedInterfaceName}#{getGenericBrackets genericVariables} {+#{ls}+}|] where ls = T.intercalate "\n" $ fmap T.pack [(replicate numIndentSpaces ' ') <> formatTSField member <> ";"| member <- members]+          modifiedInterfaceName = (\(li, name) -> li <> interfaceNameModifier name) . splitAt 1 $ interfaceName +formatTSDeclaration (FormattingOptions {..}) (TSRawDeclaration text) = text++exportPrefix :: ExportMode -> String+exportPrefix ExportEach = "export "+exportPrefix ExportNone = ""+ -- | Format a list of TypeScript declarations into a string, suitable for putting directly into a @.d.ts@ file. formatTSDeclarations' :: FormattingOptions -> [TSDeclaration] -> String-formatTSDeclarations' options declarations = T.unpack $ T.intercalate "\n\n" (fmap (T.pack . formatTSDeclaration options) declarations)+formatTSDeclarations' options declarations = T.unpack $ T.intercalate "\n\n" (fmap (T.pack . formatTSDeclaration (validateFormattingOptions options declarations)) declarations) +validateFormattingOptions :: FormattingOptions -> [TSDeclaration] -> FormattingOptions+validateFormattingOptions options@FormattingOptions{..} decls+  | typeAlternativesFormat == Enum && isPlainSumType decls = options+  | typeAlternativesFormat == EnumWithType && isPlainSumType decls = options { typeNameModifier = flip (<>) "Enum" }+  | otherwise = options { typeAlternativesFormat = TypeAlias }+  where+    isInterface :: TSDeclaration -> Bool+    isInterface TSInterfaceDeclaration{} = True+    isInterface _ = False++    -- Plain sum types have only one declaration with multiple alternatives+    -- Units (data U = U) contain two declarations, and thus are invalid+    isPlainSumType ds = (not . any isInterface $ ds) && length ds == 1+ formatTSField :: TSField -> String-formatTSField (TSField optional name typ) = [i|#{name}#{if optional then "?" else ""}: #{typ}|]+formatTSField (TSField optional name typ) = [i|#{name}#{if optional then ("?" :: String) else ""}: #{typ}|]  getGenericBrackets :: [String] -> String getGenericBrackets [] = ""
src/Data/Aeson/TypeScript/Instances.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, ExistentialQuantification, FlexibleInstances, OverlappingInstances #-}+{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, ExistentialQuantification, FlexibleInstances, OverlappingInstances, CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}  -- Note: the OverlappingInstances pragma is only here so the overlapping instances in this file -- will work on older GHCs, like GHC 7.8.4@@ -9,15 +10,23 @@ import Data.Aeson.TypeScript.Types import Data.Data import Data.HashMap.Strict-import Data.Monoid+import qualified Data.List as L import Data.Set import Data.String.Interpolate.IsString import qualified Data.Text as T import qualified Data.Text.Lazy as TL+import Data.Void +#if !MIN_VERSION_base(4,11,0)+import Data.Monoid+#endif+ instance TypeScript () where   getTypeScriptType _ = "void" +instance TypeScript Void where+  getTypeScriptType _ = "void"+ instance TypeScript T.Text where   getTypeScriptType _ = "string" @@ -37,7 +46,6 @@   getTypeScriptType _ = "boolean"  instance TypeScript Int where-  getTypeScriptDeclarations _ = []   getTypeScriptType _ = "number"  instance TypeScript Char where@@ -45,6 +53,7 @@  instance {-# OVERLAPPABLE #-} (TypeScript a) => TypeScript [a] where   getTypeScriptType _ = (getTypeScriptType (Proxy :: Proxy a)) ++ "[]"+  getParentTypes _ = (TSType (Proxy :: Proxy a)) : (getParentTypes (Proxy :: Proxy a))  instance {-# OVERLAPPING #-} TypeScript [Char] where   getTypeScriptType _ = "string"@@ -55,25 +64,51 @@                                , TSInterfaceDeclaration "Left" ["T"] [TSField False "Left" "T"]                                , TSInterfaceDeclaration "Right" ["T"] [TSField False "Right" "T"]                                ]+  getParentTypes _ = L.nub ((TSType (Proxy :: Proxy a))+                            : (TSType (Proxy :: Proxy b))+                            : (getParentTypes (Proxy :: Proxy a))+                            <> (getParentTypes (Proxy :: Proxy b)))  instance (TypeScript a, TypeScript b) => TypeScript (a, b) where   getTypeScriptType _ = [i|[#{getTypeScriptType (Proxy :: Proxy a)}, #{getTypeScriptType (Proxy :: Proxy b)}]|]+  getParentTypes _ = L.nub ((TSType (Proxy :: Proxy a))+                            : (TSType (Proxy :: Proxy b))+                            : (getParentTypes (Proxy :: Proxy a))+                            <> (getParentTypes (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)}]|]+  getParentTypes _ = L.nub ((TSType (Proxy :: Proxy a))+                            : (TSType (Proxy :: Proxy b))+                            : (TSType (Proxy :: Proxy c))+                            : (getParentTypes (Proxy :: Proxy a))+                            <> (getParentTypes (Proxy :: Proxy b))+                            <> (getParentTypes (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)}]|]+  getParentTypes _ = L.nub ((TSType (Proxy :: Proxy a))+                            : (TSType (Proxy :: Proxy b))+                            : (TSType (Proxy :: Proxy c))+                            : (TSType (Proxy :: Proxy d))+                            : (getParentTypes (Proxy :: Proxy a))+                            <> (getParentTypes (Proxy :: Proxy b))+                            <> (getParentTypes (Proxy :: Proxy c))+                            <> (getParentTypes (Proxy :: Proxy d)))  instance (TypeScript a) => TypeScript (Maybe a) where   getTypeScriptType _ = getTypeScriptType (Proxy :: Proxy a)   getTypeScriptOptional _ = True+  getParentTypes _ = [TSType (Proxy :: Proxy a)]  instance TypeScript A.Value where   getTypeScriptType _ = "any";  instance (TypeScript a, TypeScript b) => TypeScript (HashMap a b) where   getTypeScriptType _ = [i|{[k: #{getTypeScriptType (Proxy :: Proxy a)}]: #{getTypeScriptType (Proxy :: Proxy b)}}|]+  getParentTypes _ = L.nub ((getParentTypes (Proxy :: Proxy a))+                            <> (getParentTypes (Proxy :: Proxy b)))  instance (TypeScript a) => TypeScript (Set a) where   getTypeScriptType _ = getTypeScriptType (Proxy :: Proxy a) <> "[]";+  getParentTypes _ = L.nub (getParentTypes (Proxy :: Proxy a))
+ src/Data/Aeson/TypeScript/Internal.hs view
@@ -0,0 +1,8 @@+-- | Internal details. For now, this module just exports the full TSDeclaration constructors.+-- These are subject to breaking changes but are exported here in case you want to live dangerously.++module Data.Aeson.TypeScript.Internal (+  TSDeclaration(..)+  ) where++import Data.Aeson.TypeScript.Types
+ src/Data/Aeson/TypeScript/Lookup.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE LambdaCase #-}++module Data.Aeson.TypeScript.Lookup where++import Control.Monad+import Data.Aeson.TypeScript.Instances ()+import Data.Aeson.TypeScript.Types+import Data.Proxy+import Data.String.Interpolate.IsString+import Language.Haskell.TH hiding (stringE)+import qualified Language.Haskell.TH.Lib as TH+++-- | Generates a 'TypeScript' declaration for a closed type family as a lookup type.+deriveTypeScriptLookupType :: Name+                           -- ^ Name of a type family.+                           -> String+                           -- ^ Name of the declaration to derive.+                           -> Q [Dec]+deriveTypeScriptLookupType name declNameStr = do+  info <- reify name+  case info of+    FamilyI (ClosedTypeFamilyD (TypeFamilyHead _name _vars _sig _maybeInject) eqns) _decs -> do+      interfaceDecl <- getClosedTypeFamilyInterfaceDecl name eqns+      return [FunD (mkName declNameStr) [Clause [] (NormalB (ListE [interfaceDecl])) []]]++    _ -> fail [i|Expected a close type family; got #{info}|]++getClosedTypeFamilyInterfaceDecl :: Name -> [TySynEqn] -> Q Exp+getClosedTypeFamilyInterfaceDecl name eqns = do+  fields <- forM eqns $ \case+#if MIN_VERSION_template_haskell(2,15,0)+    TySynEqn Nothing (AppT (ConT _) (ConT arg)) result -> do+#else+    TySynEqn [ConT arg] result -> do+#endif+      [| TSField False (getTypeScriptType (Proxy :: Proxy $(conT arg))) (getTypeScriptType (Proxy :: Proxy $(return result))) |]+    x -> fail [i|aeson-typescript doesn't know yet how to handle this type family equation: '#{x}'|]++  [| TSInterfaceDeclaration $(TH.stringE $ nameBase name) [] $(listE $ fmap return fields) |]++getClosedTypeFamilyImage :: [TySynEqn] -> Q [Type]+getClosedTypeFamilyImage eqns = do+  forM eqns $ \case+#if MIN_VERSION_template_haskell(2,15,0)+    TySynEqn Nothing (AppT (ConT _) (ConT _)) result -> return result+#else+    TySynEqn [ConT _] result -> return result+#endif+    x -> fail [i|aeson-typescript doesn't know yet how to handle this type family equation: '#{x}'|]
+ src/Data/Aeson/TypeScript/Recursive.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, ExistentialQuantification, FlexibleInstances, NamedFieldPuns, MultiWayIf, ViewPatterns, LambdaCase, PolyKinds #-}++module Data.Aeson.TypeScript.Recursive (+  getTransitiveClosure+  , getTypeScriptDeclarationsRecursively+  ) where++import Data.Aeson.TypeScript.Instances ()+import Data.Aeson.TypeScript.TH+import Data.Function+import Data.Proxy+import qualified Data.Set as S+++getTransitiveClosure :: S.Set TSType -> S.Set TSType+getTransitiveClosure initialTypes = fix (\loop items -> let items' = S.unions (items : [getMore x | x <- S.toList items]) in+                                            if | items' == items -> items+                                               | otherwise -> loop items'+                                        ) initialTypes+  where getMore :: TSType -> S.Set TSType+        getMore (TSType x) = S.fromList $ getParentTypes x++getTypeScriptDeclarationsRecursively :: (TypeScript a) => Proxy a -> [TSDeclaration]+getTypeScriptDeclarationsRecursively initialType = S.toList $ S.fromList declarations+  where+    closure = getTransitiveClosure (S.fromList [TSType initialType])+    declarations = mconcat [getTypeScriptDeclarations x | TSType x <- S.toList closure]
src/Data/Aeson/TypeScript/TH.hs view
@@ -1,4 +1,17 @@-{-# LANGUAGE CPP, QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, ExistentialQuantification, FlexibleInstances, NamedFieldPuns, MultiWayIf, ViewPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE LambdaCase #-}  {-| Module:      Data.Aeson.TypeScript.TH@@ -98,240 +111,278 @@ -}  module Data.Aeson.TypeScript.TH (-  deriveTypeScript,+  deriveTypeScript+  , deriveTypeScript'+  , deriveTypeScriptLookupType    -- * The main typeclass-  TypeScript(..),+  , TypeScript(..)+  , TSType(..) -  TSDeclaration,+  , TSDeclaration(TSRawDeclaration)    -- * Formatting declarations-  formatTSDeclarations,-  formatTSDeclarations',-  formatTSDeclaration,-  FormattingOptions(..),+  , formatTSDeclarations+  , formatTSDeclarations'+  , formatTSDeclaration+  , FormattingOptions(..)+  , SumTypeFormat(..)+  , ExportMode(..) +  -- * Advanced options+  , ExtraTypeScriptOptions(..)+   -- * Convenience tools-  HasJSONOptions(..),-  deriveJSONAndTypeScript,+  , HasJSONOptions(..)+  , deriveJSONAndTypeScript+  , deriveJSONAndTypeScript' -  module Data.Aeson.TypeScript.Instances+  , T(..)+  , T1(..)+  , T2(..)+  , T3(..)++  , module Data.Aeson.TypeScript.Instances   ) where  import Control.Monad+import Control.Monad.Writer import Data.Aeson as A import Data.Aeson.TH as A import Data.Aeson.TypeScript.Formatting import Data.Aeson.TypeScript.Instances ()+import Data.Aeson.TypeScript.Lookup import Data.Aeson.TypeScript.Types+import Data.Aeson.TypeScript.Util+import qualified Data.List as L import qualified Data.Map as M import Data.Maybe-import Data.Monoid import Data.Proxy import Data.String.Interpolate.IsString-import qualified Data.Text as T+import Data.Typeable import Language.Haskell.TH hiding (stringE) import Language.Haskell.TH.Datatype--data T = T-data T1 = T1-data T2 = T2-data T3 = T3-data T4 = T4-data T5 = T5-data T6 = T6-data T7 = T7-data T8 = T8-data T9 = T9-data T10 = T10--instance TypeScript T where-  getTypeScriptType _ = "T"--instance TypeScript T1 where-  getTypeScriptType _ = "T1"--instance TypeScript T2 where-  getTypeScriptType _ = "T2"--instance TypeScript T3 where-  getTypeScriptType _ = "T3"--instance TypeScript T4 where-  getTypeScriptType _ = "T4"--instance TypeScript T5 where-  getTypeScriptType _ = "T5"--instance TypeScript T6 where-  getTypeScriptType _ = "T6"--instance TypeScript T7 where-  getTypeScriptType _ = "T7"--instance TypeScript T8 where-  getTypeScriptType _ = "T8"--instance TypeScript T9 where-  getTypeScriptType _ = "T9"--instance TypeScript T10 where-  getTypeScriptType _ = "T10"+import qualified Language.Haskell.TH.Lib as TH +#if !MIN_VERSION_base(4,11,0)+import Data.Monoid+#endif  -- | Generates a 'TypeScript' instance declaration for the given data type.-deriveTypeScript :: Options-                 -- ^ Encoding options.-                 -> Name-                 -- ^ Name of the type for which to generate a 'TypeScript' instance declaration.-                 -> Q [Dec]-deriveTypeScript options name = do-  datatypeInfo@(DatatypeInfo {..}) <- reifyDatatype name--  assertExtensionsTurnedOn datatypeInfo--  let getFreeVariableName (SigT (VarT name) kind) = Just name-      getFreeVariableName typ = Nothing+deriveTypeScript' :: Options+                  -- ^ Encoding options.+                  -> Name+                  -- ^ Name of the type for which to generate a 'TypeScript' instance declaration.+                  -> ExtraTypeScriptOptions+                  -- ^ Extra options to control advanced features.+                  -> Q [Dec]+deriveTypeScript' options name extraOptions = do+  datatypeInfo' <- reifyDatatype name+  assertExtensionsTurnedOn datatypeInfo' -  let templateVarsToUse = case length datatypeVars of+  -- Plug in generic variables for all star free variables+  let starVars = [name | (isStarType -> Just name) <- getDataTypeVars datatypeInfo']+  let templateVarsToUse = case length starVars of         1 -> [ConT ''T]-        n -> take (length datatypeVars) [ConT ''T1, ConT ''T2, ConT ''T3, ConT ''T4, ConT ''T5, ConT ''T6, ConT ''T7, ConT ''T8, ConT ''T9, ConT ''T10]--#if MIN_VERSION_th_abstraction(0,3,0)-  let subMap = M.fromList $ zip (catMaybes $ fmap getFreeVariableName datatypeInstTypes) templateVarsToUse-  let fullyQualifiedDatatypeInfo = (datatypeInfo {datatypeInstTypes = templateVarsToUse-                                                 , datatypeCons = fmap (applySubstitution subMap) datatypeCons})-#else-  let subMap = M.fromList $ zip (catMaybes $ fmap getFreeVariableName datatypeVars) templateVarsToUse-  let fullyQualifiedDatatypeInfo = (datatypeInfo {datatypeVars = templateVarsToUse-                                                 , datatypeCons = fmap (applySubstitution subMap) datatypeCons})-#endif-  getTypeFn <- getTypeExpression fullyQualifiedDatatypeInfo >>= \expr -> return $ FunD 'getTypeScriptType [Clause [WildP] (NormalB expr) []]-  getDeclarationFn <- getDeclarationFunctionBody options name fullyQualifiedDatatypeInfo-  let fullyGenericInstance = mkInstance [] (AppT (ConT ''TypeScript) (ConT name)) [getTypeFn, getDeclarationFn]--  otherInstances <- case length datatypeVars > 0 of-    True -> do-      otherGetTypeFn <- getTypeExpression datatypeInfo >>= \expr -> return $ FunD 'getTypeScriptType [Clause [WildP] (NormalB expr) []]-#if MIN_VERSION_th_abstraction(0,3,0)-      return [mkInstance (fmap getDatatypePredicate datatypeInstTypes) (AppT (ConT ''TypeScript) (foldl (\x y -> AppT x y) (ConT name) datatypeInstTypes)) [otherGetTypeFn]]-#else-      return [mkInstance (fmap getDatatypePredicate datatypeVars) (AppT (ConT ''TypeScript) (foldl (\x y -> AppT x y) (ConT name) datatypeVars)) [otherGetTypeFn]]-#endif-    False -> return []--  return $ fullyGenericInstance : otherInstances+        _ -> take (length starVars) allStarConstructors+  let subMap = M.fromList $ zip starVars templateVarsToUse+  let dti = datatypeInfo' { datatypeCons = fmap (applySubstitution subMap) (datatypeCons datatypeInfo')} -getDeclarationFunctionBody :: Options -> p -> DatatypeInfo -> Q Dec-getDeclarationFunctionBody options _name datatypeInfo@(DatatypeInfo {..}) = do-  -- If name is higher-kinded, add generic variables to the type and interface declarations-  let genericVariables :: [String] = if | length datatypeVars == 1 -> ["T"]-                                        | otherwise -> ["T" <> show i | i <- [1..(length datatypeVars)]]-  let genericVariablesExp = ListE [stringE x | x <- genericVariables]+  -- Build constraints: a TypeScript constraint for every constructor type and one for every type variable.+  -- Probably overkill/not exactly right, but it's a start.+  let constructorPreds :: [Pred] = [AppT (ConT ''TypeScript) x | x <- mconcat $ fmap constructorFields (datatypeCons dti)+                                                               , hasFreeTypeVariable x]+  let constructorPreds' :: [Pred] = [AppT (ConT ''TypeScript) x | x <- mconcat $ fmap constructorFields (datatypeCons datatypeInfo')+                                                                , hasFreeTypeVariable x]+  let typeVariablePreds :: [Pred] = [AppT (ConT ''TypeScript) x | x <- getDataTypeVars dti] -  declarationFnBody <- do-    let interfaceNamesAndDeclarations = fmap (handleConstructor options datatypeInfo genericVariables) datatypeCons-    let interfaceNames = fmap fst interfaceNamesAndDeclarations-    let interfaceDeclarations = catMaybes $ fmap snd interfaceNamesAndDeclarations+  let eligibleGenericVars = catMaybes $ flip fmap (getDataTypeVars dti) $ \case+        SigT (VarT n) StarT -> Just n+        _ -> Nothing+  genericVariablesAndSuffixes <- forM eligibleGenericVars $ \var -> do+    (_, genericInfos) <- runWriterT $ forM_ (datatypeCons datatypeInfo') $ \ci ->+      forM_ (namesAndTypes options ci) $ \(_, typ) -> do+        searchForConstraints extraOptions typ var+    return (var, unifyGenericVariable genericInfos) -    let typeDeclaration = applyToArgsE (ConE 'TSTypeAlternatives) [stringE $ getTypeName datatypeName, genericVariablesExp, ListE interfaceNames]+  -- Build the declarations+  (types, extraDeclsOrGenericInfos) <- runWriterT $ mapM (handleConstructor options extraOptions dti genericVariablesAndSuffixes) (datatypeCons dti)+  typeDeclaration <- [|TSTypeAlternatives $(TH.stringE $ getTypeName (datatypeName dti))+                                          $(genericVariablesListExpr True genericVariablesAndSuffixes)+                                          $(listE $ fmap return types)|]+  let extraDecls = [x | ExtraDecl x <- extraDeclsOrGenericInfos]+  let extraTopLevelDecls = mconcat [x | ExtraTopLevelDecs x <- extraDeclsOrGenericInfos]+  let predicates = constructorPreds <> constructorPreds' <> typeVariablePreds <> [x | ExtraConstraint x <- extraDeclsOrGenericInfos] -    return $ NormalB $ ListE (typeDeclaration : interfaceDeclarations)+  declarationsFunctionBody <- [| $(return typeDeclaration) : $(listE (fmap return $ extraDecls)) |] -  return $ FunD 'getTypeScriptDeclarations [Clause [WildP] declarationFnBody []]+  let extraParentTypes = [x | ExtraParentType x <- extraDeclsOrGenericInfos] +  -- Couldn't figure out how to put the constraints for "instance TypeScript..." in the quasiquote above without+  -- introducing () when the constraints are empty, which causes "illegal tuple constraint" unless the user enables ConstraintKinds.+  -- So, just use our mkInstance function+  getTypeScriptTypeExp <- [|$(TH.stringE $ getTypeName (datatypeName dti)) <> $(getBracketsExpressionAllTypesNoSuffix genericVariablesAndSuffixes)|]+  getParentTypesExp <- listE [ [|TSType (Proxy :: Proxy $(return t))|]+                             | t <- (mconcat $ fmap constructorFields (datatypeCons datatypeInfo')) <> extraParentTypes]+  let inst = [mkInstance predicates (AppT (ConT ''TypeScript) (foldl AppT (ConT name) (getDataTypeVars dti))) [+                 FunD 'getTypeScriptType [Clause [WildP] (NormalB getTypeScriptTypeExp) []]+                 , FunD 'getTypeScriptDeclarations [Clause [WildP] (NormalB declarationsFunctionBody) []]+                 , FunD 'getParentTypes [Clause [WildP] (NormalB getParentTypesExp) []]+                 ]]+  return (extraTopLevelDecls <> inst)  -- | Return a string to go in the top-level type declaration, plus an optional expression containing a declaration-handleConstructor :: Options -> DatatypeInfo -> [String] -> ConstructorInfo -> (Exp, Maybe Exp)-handleConstructor options (DatatypeInfo {..}) genericVariables ci@(ConstructorInfo {}) =-  if | isSingleConstructorType && not (getTagSingleConstructors options) -> (stringE interfaceName, singleConstructorEncoding)-+handleConstructor :: Options -> ExtraTypeScriptOptions -> DatatypeInfo -> [(Name, String)] -> ConstructorInfo -> WriterT [ExtraDeclOrGenericInfo] Q Exp+handleConstructor options extraOptions (DatatypeInfo {..}) genericVariables ci@(ConstructorInfo {}) =+  if | (length datatypeCons == 1) && not (getTagSingleConstructors options) -> do+         writeSingleConstructorEncoding+         brackets <- lift $ getBracketsExpression False genericVariables+         lift [|$(TH.stringE interfaceName) <> $(return brackets)|]      | allConstructorsAreNullary datatypeCons && allNullaryToStringTag options -> stringEncoding+      -- With UntaggedValue, nullary constructors are encoded as strings      | (isUntaggedValue $ sumEncoding options) && isConstructorNullary ci -> stringEncoding       -- Treat as a sum-     | isObjectWithSingleField $ sumEncoding options -> (stringE [i|{#{show constructorNameToUse}: #{interfaceName}}|], singleConstructorEncoding)-     | isTwoElemArray $ sumEncoding options -> (stringE [i|[#{show constructorNameToUse}, #{interfaceName}]|], singleConstructorEncoding)-     | isUntaggedValue $ sumEncoding options -> (stringE interfaceName, singleConstructorEncoding)-     | otherwise -> (stringE interfaceName, taggedConstructorEncoding)+     | isObjectWithSingleField $ sumEncoding options -> do+         writeSingleConstructorEncoding+         brackets <- lift $ getBracketsExpression False genericVariables+         lift [|"{" <> $(TH.stringE $ show $ constructorNameToUse options ci) <> ": " <> $(TH.stringE interfaceName) <> $(return brackets) <> "}"|]+     | isTwoElemArray $ sumEncoding options -> do+         writeSingleConstructorEncoding+         brackets <- lift $ getBracketsExpression False genericVariables+         lift [|"[" <> $(TH.stringE $ show $ constructorNameToUse options ci) <> ", " <> $(TH.stringE interfaceName) <> $(return brackets) <> "]"|]+     | isUntaggedValue $ sumEncoding options -> do+         writeSingleConstructorEncoding+         brackets <- lift $ getBracketsExpression False genericVariables+         lift [|$(TH.stringE interfaceName) <> $(return brackets)|]+     | otherwise -> do+         tagField :: [Exp] <- lift $ case sumEncoding options of+           TaggedObject tagFieldName _ -> (: []) <$> [|TSField False $(TH.stringE tagFieldName) $(TH.stringE [i|"#{constructorNameToUse options ci}"|])|]+           _ -> return [] -  where-    stringEncoding = (stringE [i|"#{(constructorTagModifier options) $ getTypeName (constructorName ci)}"|], Nothing)+         tsFields <- getTSFields+         decl <- lift $ assembleInterfaceDeclaration (ListE (tagField ++ tsFields))+         tell [ExtraDecl decl]+         brackets <- lift $ getBracketsExpression False genericVariables+         lift [|$(TH.stringE interfaceName) <> $(return brackets)|] -    singleConstructorEncoding = if | constructorVariant ci == NormalConstructor -> tupleEncoding-                                   | otherwise -> Just $ assembleInterfaceDeclaration options (constructorName ci) genericVariables (ListE (getTSFields namesAndTypes))+  where+    stringEncoding = lift $ TH.stringE [i|"#{(constructorTagModifier options) $ getTypeName (constructorName ci)}"|] -    taggedConstructorEncoding = Just $ assembleInterfaceDeclaration options (constructorName ci) genericVariables (ListE (tagField ++ getTSFields namesAndTypes))+    writeSingleConstructorEncoding = if+      | constructorVariant ci == NormalConstructor -> do+          encoding <- tupleEncoding+          tell [ExtraDecl encoding]+      | otherwise -> do+          tsFields <- getTSFields+          decl <- lift $ assembleInterfaceDeclaration (ListE tsFields)+          tell [ExtraDecl decl]      -- * Type declaration to use-    interfaceName = getInterfaceName (constructorName ci) <> getGenericBrackets genericVariables+    interfaceName = "I" <> (lastNameComponent' $ constructorName ci) -    tupleEncoding = Just $ applyToArgsE (ConE 'TSTypeAlternatives) [stringE $ getInterfaceName (constructorName ci)-                                                                   , ListE [stringE x | x <- genericVariables]-                                                                   , ListE [getTypeAsStringExp contentsTupleType]]+    tupleEncoding = do+      tupleType <- transformTypeFamilies extraOptions (contentsTupleType ci)+      lift $ [|TSTypeAlternatives $(TH.stringE interfaceName)+                                  $(genericVariablesListExpr True genericVariables)+                                  [getTypeScriptType (Proxy :: Proxy $(return tupleType))]|] -    namesAndTypes :: [(String, Type)] = case constructorVariant ci of-      RecordConstructor names -> zip (fmap ((fieldLabelModifier options) . lastNameComponent') names) (constructorFields ci)-      NormalConstructor -> case sumEncoding options of-        TaggedObject _ contentsFieldName -> if | isConstructorNullary ci -> []-                                                          | otherwise -> [(contentsFieldName, contentsTupleType)]-        _ -> [(constructorNameToUse, contentsTupleType)]+    assembleInterfaceDeclaration members = [|TSInterfaceDeclaration $(TH.stringE interfaceName)+                                                                    $(genericVariablesListExpr True genericVariables)+                                                                    $(return members)|] -    tagField = case sumEncoding options of-      TaggedObject tagFieldName _ -> [(AppE (AppE (AppE (ConE 'TSField) (ConE 'False))-                                              (stringE tagFieldName))-                                        (stringE [i|"#{constructorNameToUse}"|]))]-      _ -> []+    getTSFields :: WriterT [ExtraDeclOrGenericInfo] Q [Exp]+    getTSFields = forM (namesAndTypes options ci) $ \(nameString, typ') -> do+      typ <- transformTypeFamilies extraOptions typ'+      when (typ /= typ') $ do+        let constraint = AppT (ConT ''TypeScript) typ+        tell [ExtraConstraint constraint] -    isSingleConstructorType = length datatypeCons == 1+      (fieldTyp, optAsBool) <- lift $ case typ of+        (AppT (ConT name) t) | name == ''Maybe && not (omitNothingFields options) ->+          ( , ) <$> [|$(getTypeAsStringExp t) <> " | null"|] <*> getOptionalAsBoolExp t+        _ -> ( , ) <$> getTypeAsStringExp typ <*> getOptionalAsBoolExp typ'+      lift $ [| TSField $(return optAsBool) $(TH.stringE nameString) $(return fieldTyp) |] -    constructorNameToUse = (constructorTagModifier options) $ lastNameComponent' (constructorName ci)-    contentsTupleType = getTupleType (constructorFields ci)+transformTypeFamilies :: ExtraTypeScriptOptions -> Type -> WriterT [ExtraDeclOrGenericInfo] Q Type+transformTypeFamilies eo@(ExtraTypeScriptOptions {..}) (AppT (ConT name) typ)+  | name `L.elem` typeFamiliesToMapToTypeScript = lift (reify name) >>= \case+      FamilyI (ClosedTypeFamilyD (TypeFamilyHead typeFamilyName _ _ _) eqns) _ -> do+        name' <- lift $ newName (nameBase typeFamilyName <> "'") --- | Helper for handleConstructor-getTSFields :: [(String, Type)] -> [Exp]-getTSFields namesAndTypes = [(AppE (AppE (AppE (ConE 'TSField) (getOptionalAsBoolExp typ))-                                     (stringE nameString))-                               (getTypeAsStringExp typ))-                            | (nameString, typ) <- namesAndTypes]+        f <- lift $ newName "f"+        let inst1 = DataD [] name' [PlainTV f] Nothing [] []+        tell [ExtraTopLevelDecs [inst1]] --- | Helper for handleConstructor-assembleInterfaceDeclaration options constructorName genericVariables members = AppE (AppE (AppE (ConE 'TSInterfaceDeclaration) constructorNameExp) genericVariablesExp) members where-  constructorNameExp = stringE $ getInterfaceName constructorName-  genericVariablesExp = (ListE [stringE x | x <- genericVariables])+        imageTypes <- lift $ getClosedTypeFamilyImage eqns+        inst2 <- lift $ [d|instance (Typeable g, TypeScript g) => TypeScript ($(conT name') g) where+                             getTypeScriptType _ = $(TH.stringE $ nameBase name) <> "[" <> (getTypeScriptType (Proxy :: Proxy g)) <> "]"+                             getTypeScriptDeclarations _ = [$(getClosedTypeFamilyInterfaceDecl name eqns)]+                             getParentTypes _ = $(listE [ [|TSType (Proxy :: Proxy $(return x))|] | x <- imageTypes])+                        |]+        tell [ExtraTopLevelDecs inst2] +        tell [ExtraParentType (AppT (ConT name') (ConT ''T))] --- * Getting type expression+        transformTypeFamilies eo (AppT (ConT name') typ)+      _ -> AppT (ConT name) <$> transformTypeFamilies eo typ+  | otherwise = AppT (ConT name) <$> transformTypeFamilies eo typ+transformTypeFamilies eo (AppT typ1 typ2) = AppT <$> transformTypeFamilies eo typ1 <*> transformTypeFamilies eo typ2+transformTypeFamilies eo (SigT typ kind) = flip SigT kind <$> transformTypeFamilies eo typ+transformTypeFamilies eo (InfixT typ1 n typ2) = InfixT <$> transformTypeFamilies eo typ1 <*> pure n <*> transformTypeFamilies eo typ2+transformTypeFamilies eo (UInfixT typ1 n typ2) = UInfixT <$> transformTypeFamilies eo typ1 <*> pure n <*> transformTypeFamilies eo typ2+transformTypeFamilies eo (ParensT typ) = ParensT <$> transformTypeFamilies eo typ+#if MIN_VERSION_template_haskell(2,15,0)+transformTypeFamilies eo (AppKindT typ kind) = flip AppKindT kind <$> transformTypeFamilies eo typ+transformTypeFamilies eo (ImplicitParamT s typ) = ImplicitParamT s <$> transformTypeFamilies eo typ+#endif+transformTypeFamilies _ typ = return typ --- | Get an expression to be used for getTypeScriptType.--- For datatypes of kind * this is easy, since we can just evaluate the string literal in TH.--- For higher-kinded types, we need to make an expression which evaluates the template types and fills it in.-#if MIN_VERSION_th_abstraction(0,3,0)-getTypeExpression :: DatatypeInfo -> Q Exp-getTypeExpression (DatatypeInfo {datatypeInstTypes=[], ..}) = return $ stringE $ getTypeName datatypeName-getTypeExpression (DatatypeInfo {datatypeInstTypes=vars, ..}) = do-#else-getTypeExpression :: DatatypeInfo -> Q Exp-getTypeExpression (DatatypeInfo {datatypeVars=[], ..}) = return $ stringE $ getTypeName datatypeName-getTypeExpression (DatatypeInfo {datatypeVars=vars, ..}) = do++searchForConstraints :: ExtraTypeScriptOptions -> Type -> Name -> WriterT [GenericInfo] Q ()+searchForConstraints eo@(ExtraTypeScriptOptions {..}) (AppT (ConT name) typ) var+  | typ == VarT var && (name `L.elem` typeFamiliesToMapToTypeScript) = lift (reify name) >>= \case+      FamilyI (ClosedTypeFamilyD (TypeFamilyHead typeFamilyName _ _ _) _) _ -> do+        tell [GenericInfo var (TypeFamilyKey typeFamilyName)]+        searchForConstraints eo typ var+      _ -> searchForConstraints eo typ var+  | otherwise = searchForConstraints eo typ var+searchForConstraints eo (AppT typ1 typ2) var = searchForConstraints eo typ1 var >> searchForConstraints eo typ2 var+searchForConstraints eo (SigT typ _) var = searchForConstraints eo typ var+searchForConstraints eo (InfixT typ1 _ typ2) var = searchForConstraints eo typ1 var >> searchForConstraints eo typ2 var+searchForConstraints eo (UInfixT typ1 _ typ2) var = searchForConstraints eo typ1 var >> searchForConstraints eo typ2 var+searchForConstraints eo (ParensT typ) var = searchForConstraints eo typ var+#if MIN_VERSION_template_haskell(2,15,0)+searchForConstraints eo (AppKindT typ _) var = searchForConstraints eo typ var+searchForConstraints eo (ImplicitParamT _ typ) var = searchForConstraints eo typ var #endif-  let baseName = stringE $ getTypeName datatypeName-  let typeNames = ListE [getTypeAsStringExp typ | typ <- vars]-  let headType = AppE (VarE 'head) typeNames-  let tailType = AppE (VarE 'tail) typeNames-  let comma = stringE ", "-  x <- newName "x"-  let tailsWithCommas = AppE (VarE 'mconcat) (CompE [BindS (VarP x) tailType, NoBindS (AppE (AppE (VarE 'mappend) comma) (VarE x))])-  let brackets = AppE (VarE 'mconcat) (ListE [stringE "<", headType, tailsWithCommas, stringE ">"])+searchForConstraints _ _ _ = return () -  return $ (AppE (AppE (VarE 'mappend) baseName) brackets)+hasFreeTypeVariable :: Type -> Bool+hasFreeTypeVariable (VarT _) = True+hasFreeTypeVariable (AppT typ1 typ2) = hasFreeTypeVariable typ1 || hasFreeTypeVariable typ2+hasFreeTypeVariable (SigT typ _) = hasFreeTypeVariable typ+hasFreeTypeVariable (InfixT typ1 _ typ2) = hasFreeTypeVariable typ1 || hasFreeTypeVariable typ2+hasFreeTypeVariable (UInfixT typ1 _ typ2) = hasFreeTypeVariable typ1 || hasFreeTypeVariable typ2+hasFreeTypeVariable (ParensT typ) = hasFreeTypeVariable typ+#if MIN_VERSION_template_haskell(2,15,0)+hasFreeTypeVariable (AppKindT typ _) = hasFreeTypeVariable typ+hasFreeTypeVariable (ImplicitParamT _ typ) = hasFreeTypeVariable typ+#endif+hasFreeTypeVariable _ = False +unifyGenericVariable :: [GenericInfo] -> String+unifyGenericVariable genericInfos = case [nameBase name | GenericInfo _ (TypeFamilyKey name) <- genericInfos] of+  [] -> ""+  names -> " extends keyof " <> (L.intercalate " & " names)+ -- * Convenience functions  -- | Convenience function to generate 'A.ToJSON', 'A.FromJSON', and 'TypeScript' instances simultaneously, so the instances are guaranteed to be in sync. ----- This function is given mainly as an illustration. If you want some other permutation of instances, such as 'A.ToJSON' and 'A.TypeScript' only, just take a look at the source and write your own version.+-- This function is given mainly as an illustration.+-- If you want some other permutation of instances, such as 'A.ToJSON' and 'A.TypeScript' only, just take a look at the source and write your own version. -- -- @since 0.1.0.4 deriveJSONAndTypeScript :: Options@@ -339,106 +390,21 @@                         -> Name                         -- ^ Name of the type for which to generate 'A.ToJSON', 'A.FromJSON', and 'TypeScript' instance declarations.                         -> Q [Dec]-deriveJSONAndTypeScript options name = do-  ts <- deriveTypeScript options name-  json <- A.deriveJSON options name-  return $ ts <> json---- * Util stuff--lastNameComponent :: String -> String-lastNameComponent x = T.unpack $ last $ T.splitOn "." (T.pack x)--lastNameComponent' :: Name -> String-lastNameComponent' = lastNameComponent . show--getInterfaceName :: Name -> String-getInterfaceName x = "I" <> (lastNameComponent' x)--getTypeName :: Name -> String-getTypeName x = lastNameComponent $ show x--allConstructorsAreNullary :: [ConstructorInfo] -> Bool-allConstructorsAreNullary constructors = and $ fmap isConstructorNullary constructors--isConstructorNullary :: ConstructorInfo -> Bool-isConstructorNullary (ConstructorInfo {constructorVariant, constructorFields}) = (constructorVariant == NormalConstructor) && (constructorFields == [])---- In Template Haskell 2.10.0.0 and later, Pred is just a synonm for Type--- In earlier versions, it has constructors-getDatatypePredicate :: Type -> Pred-#if MIN_VERSION_template_haskell(2,10,0)-getDatatypePredicate typ = AppT (ConT ''TypeScript) typ-#else-getDatatypePredicate typ = ClassP ''TypeScript [typ]-#endif--getTypeAsStringExp :: Type -> Exp-getTypeAsStringExp typ = AppE (VarE 'getTypeScriptType) (SigE (ConE 'Proxy) (AppT (ConT ''Proxy) typ))--getOptionalAsBoolExp :: Type -> Exp-getOptionalAsBoolExp typ = AppE (VarE 'getTypeScriptOptional) (SigE (ConE 'Proxy) (AppT (ConT ''Proxy) typ))--isTaggedObject (sumEncoding -> TaggedObject _ _) = True-isTaggedObject _ = False---- | Get the type of a tuple of constructor fields, as when we're packing a record-less constructor into a list-getTupleType constructorFields = case length constructorFields of-  0 -> AppT ListT (ConT ''())-  1 -> head constructorFields-  x -> applyToArgsT (ConT $ tupleTypeName x) constructorFields---- | Helper to apply a type constructor to a list of type args-applyToArgsT :: Type -> [Type] -> Type-applyToArgsT constructor [] = constructor-applyToArgsT constructor (x:xs) = applyToArgsT (AppT constructor x) xs---- | Helper to apply a function a list of args-applyToArgsE :: Exp -> [Exp] -> Exp-applyToArgsE f [] = f-applyToArgsE f (x:xs) = applyToArgsE (AppE f x) xs--stringE = LitE . StringL---- Between Template Haskell 2.10 and 2.11, InstanceD got an additional argument-#if MIN_VERSION_template_haskell(2,11,0)-mkInstance context typ decs = InstanceD Nothing context typ decs-#else-mkInstance context typ decs = InstanceD context typ decs-#endif---- Between Aeson 1.1.2.0 and 1.2.0.0, tagSingleConstructors was added-getTagSingleConstructors :: Options -> Bool-#if MIN_VERSION_aeson(1,2,0)-getTagSingleConstructors options = tagSingleConstructors options-#else-getTagSingleConstructors _ = False-#endif---- Between Template Haskell 2.10 and 2.11, the ability to look up which extensions are turned on was added-assertExtensionsTurnedOn :: DatatypeInfo -> Q ()-#if MIN_VERSION_template_haskell(2,11,0)-assertExtensionsTurnedOn (DatatypeInfo {..}) = do-  -- Check that necessary language extensions are turned on-  scopedTypeVariablesEnabled <- isExtEnabled ScopedTypeVariables-  kindSignaturesEnabled <- isExtEnabled KindSignatures-  when (not scopedTypeVariablesEnabled) $ error [i|The ScopedTypeVariables extension is required; please enable it before calling deriveTypeScript. (For example: put {-# LANGUAGE ScopedTypeVariables #-} at the top of the file.)|]-  when ((not kindSignaturesEnabled) && (length datatypeVars > 0)) $ error [i|The KindSignatures extension is required since type #{datatypeName} is a higher order type; please enable it before calling deriveTypeScript. (For example: put {-# LANGUAGE KindSignatures #-} at the top of the file.)|]-#else-assertExtensionsTurnedOn _ = return ()-#endif---- Older versions of Aeson don't have an Eq instance for SumEncoding so we do this-isObjectWithSingleField ObjectWithSingleField = True-isObjectWithSingleField _ = False+deriveJSONAndTypeScript options name = (<>) <$> (deriveTypeScript options name) <*> (A.deriveJSON options name) --- Older versions of Aeson don't have an Eq instance for SumEncoding so we do this-isTwoElemArray TwoElemArray = True-isTwoElemArray _ = False+deriveJSONAndTypeScript' :: Options+                         -- ^ Encoding options.+                         -> Name+                         -- ^ Name of the type for which to generate 'A.ToJSON', 'A.FromJSON', and 'TypeScript' instance declarations.+                         -> ExtraTypeScriptOptions+                         -- ^ Extra options to control advanced features.+                         -> Q [Dec]+deriveJSONAndTypeScript' options name extraOptions = (<>) <$> (deriveTypeScript' options name extraOptions) <*> (A.deriveJSON options name) --- Older versions of Aeson don't have an Eq instance for SumEncoding so we do this--- UntaggedValue was added between Aeson 0.11.3.0 and 1.0.0.0-#if MIN_VERSION_aeson(1,0,0)-isUntaggedValue UntaggedValue = True-#endif-isUntaggedValue _ = False+-- | Generates a 'TypeScript' instance declaration for the given data type.+deriveTypeScript :: Options+                 -- ^ Encoding options.+                 -> Name+                 -- ^ Name of the type for which to generate a 'TypeScript' instance declaration.+                 -> Q [Dec]+deriveTypeScript options name = deriveTypeScript' options name defaultExtraTypeScriptOptions
src/Data/Aeson/TypeScript/Types.hs view
@@ -1,11 +1,12 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, ExistentialQuantification, PolyKinds #-}+{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, ExistentialQuantification, PolyKinds, StandaloneDeriving #-}  module Data.Aeson.TypeScript.Types where  import qualified Data.Aeson as A-import qualified Data.Aeson.TH as A-import Data.Data+import Data.Proxy import Data.String+import Data.Typeable+import Language.Haskell.TH  -- | The typeclass that defines how a type is turned into TypeScript. --@@ -38,7 +39,7 @@ -- instance (TypeScript a) => TypeScript (Set a) where --   getTypeScriptType _ = getTypeScriptType (Proxy :: Proxy a) <> "[]"; -- @-class TypeScript a where+class (Typeable a) => TypeScript a where   getTypeScriptDeclarations :: Proxy a -> [TSDeclaration]   -- ^ Get the declaration(s) needed for this type.   getTypeScriptDeclarations _ = []@@ -50,17 +51,38 @@   -- ^ Get a flag representing whether this type is optional.   getTypeScriptOptional _ = False +  getParentTypes :: Proxy a -> [TSType]+  -- ^ Get the types that this type depends on. This is useful for generating transitive closures of necessary types.+  getParentTypes _ = []++  isGenericVariable :: Proxy a -> Bool+  -- ^ Special flag to indicate whether this type corresponds to a template variable.+  isGenericVariable _ = False++-- | An existential wrapper for any TypeScript instance.+data TSType = forall a. (Typeable a, TypeScript a) => TSType { unTSType :: Proxy a }++instance Eq TSType where+  (TSType p1) == (TSType p2) = typeRep p1 == typeRep p2++instance Ord TSType where+  (TSType p1) `compare` (TSType p2) = typeRep p1 `compare` typeRep p2++instance Show TSType where+  show (TSType proxy) = show $ typeRep proxy+ data TSDeclaration = TSInterfaceDeclaration { interfaceName :: String                                             , interfaceGenericVariables :: [String]                                             , interfaceMembers :: [TSField] }                    | TSTypeAlternatives { typeName :: String                                         , typeGenericVariables :: [String]                                         , alternativeTypes :: [String]}-  deriving (Show, Eq)+                   | TSRawDeclaration { text :: String }+  deriving (Show, Eq, Ord)  data TSField = TSField { fieldOptional :: Bool                        , fieldName :: String-                       , fieldType :: String } deriving (Show, Eq)+                       , fieldType :: String } deriving (Show, Eq, Ord)  newtype TSString a = TSString { unpackTSString :: String } deriving Show @@ -76,14 +98,87 @@   -- ^ Function applied to generated interface names   , typeNameModifier :: String -> String   -- ^ Function applied to generated type names+  , exportMode :: ExportMode+  -- ^ Whether to include the export keyword in declarations+  , typeAlternativesFormat :: SumTypeFormat+  -- ^ How to format the declaration of the alternatives when multiple constructors exist   } +data ExportMode =+  ExportEach+  -- ^ Prefix every declaration with the "export" keyword (suitable for putting in a TypeScripe module)+  | ExportNone+  -- ^ No exporting (suitable for putting in a .d.ts file)++-- | TODO: docstrings here+data SumTypeFormat =+  TypeAlias+  | Enum+  | EnumWithType+  deriving (Eq, Show)++defaultFormattingOptions :: FormattingOptions defaultFormattingOptions = FormattingOptions   { numIndentSpaces = 2   , interfaceNameModifier = id   , typeNameModifier = id+  , exportMode = ExportNone+  , typeAlternativesFormat = TypeAlias   }  -- | Convenience typeclass class you can use to "attach" a set of Aeson encoding options to a type. class HasJSONOptions a where   getJSONOptions :: (Proxy a) -> A.Options++data T = T+data T1 = T1+data T2 = T2+data T3 = T3+data T4 = T4+data T5 = T5+data T6 = T6+data T7 = T7+data T8 = T8+data T9 = T9+data T10 = T10++instance TypeScript T where getTypeScriptType _ = "T"; isGenericVariable _ = True+instance TypeScript T1 where getTypeScriptType _ = "T1"; isGenericVariable _ = True+instance TypeScript T2 where getTypeScriptType _ = "T2"; isGenericVariable _ = True+instance TypeScript T3 where getTypeScriptType _ = "T3"; isGenericVariable _ = True+instance TypeScript T4 where getTypeScriptType _ = "T4"; isGenericVariable _ = True+instance TypeScript T5 where getTypeScriptType _ = "T5"; isGenericVariable _ = True+instance TypeScript T6 where getTypeScriptType _ = "T6"; isGenericVariable _ = True+instance TypeScript T7 where getTypeScriptType _ = "T7"; isGenericVariable _ = True+instance TypeScript T8 where getTypeScriptType _ = "T8"; isGenericVariable _ = True+instance TypeScript T9 where getTypeScriptType _ = "T9"; isGenericVariable _ = True+instance TypeScript T10 where getTypeScriptType _ = "T10"; isGenericVariable _ = True++allStarConstructors :: [Type]+allStarConstructors = [ConT ''T1, ConT ''T2, ConT ''T3, ConT ''T4, ConT ''T5, ConT ''T6, ConT ''T7, ConT ''T8, ConT ''T9, ConT ''T10]++allStarConstructors' :: [Name]+allStarConstructors' = [''T1, ''T2, ''T3, ''T4, ''T5, ''T6, ''T7, ''T8, ''T9, ''T10]++-- | Type variable gathering++data ExtraTypeScriptOptions = ExtraTypeScriptOptions {+  typeFamiliesToMapToTypeScript :: [Name]+  }++defaultExtraTypeScriptOptions :: ExtraTypeScriptOptions+defaultExtraTypeScriptOptions = ExtraTypeScriptOptions []++data ExtraDeclOrGenericInfo = ExtraDecl Exp+                            | ExtraGeneric GenericInfo+                            | ExtraTopLevelDecs [Dec]+                            | ExtraConstraint Type+                            | ExtraParentType Type+  deriving Show++data GenericInfo = GenericInfo Name GenericInfoExtra+  deriving Show++data GenericInfoExtra = NormalStar+                      | TypeFamilyKey Name+  deriving Show
+ src/Data/Aeson/TypeScript/Util.hs view
@@ -0,0 +1,166 @@+{-# LANGUAGE CPP, QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, ExistentialQuantification, FlexibleInstances, NamedFieldPuns, MultiWayIf, ViewPatterns, PolyKinds #-}++module Data.Aeson.TypeScript.Util where++import Control.Monad+import Data.Aeson as A+import Data.Aeson.TypeScript.Instances ()+import Data.Aeson.TypeScript.Types+import qualified Data.List as L+import Data.Proxy+import Data.String.Interpolate.IsString+import qualified Data.Text as T+import Language.Haskell.TH hiding (stringE)+import Language.Haskell.TH.Datatype+import qualified Language.Haskell.TH.Lib as TH++#if !MIN_VERSION_base(4,11,0)+import Data.Monoid+#endif++getDataTypeVars :: DatatypeInfo -> [Type]+#if MIN_VERSION_th_abstraction(0,3,0)+getDataTypeVars (DatatypeInfo {datatypeInstTypes}) = datatypeInstTypes+#else+getDataTypeVars (DatatypeInfo {datatypeVars}) = datatypeVars+#endif++setDataTypeVars :: DatatypeInfo -> [Type] -> DatatypeInfo+#if MIN_VERSION_th_abstraction(0,3,0)+setDataTypeVars dti@(DatatypeInfo {}) vars = dti { datatypeInstTypes = vars }+#else+setDataTypeVars dti@(DatatypeInfo {}) vars = dti { datatypeVars = vars }+#endif++dropLeadingIFromInterfaceName :: TSDeclaration -> TSDeclaration+dropLeadingIFromInterfaceName decl@(TSInterfaceDeclaration {interfaceName=('I':xs)}) = decl { interfaceName = xs }+dropLeadingIFromInterfaceName decl@(TSTypeAlternatives {typeName=('I':xs)}) = decl { typeName = xs }+dropLeadingIFromInterfaceName x = x       ++lastNameComponent :: String -> String+lastNameComponent x = T.unpack $ last $ T.splitOn "." (T.pack x)++lastNameComponent' :: Name -> String+lastNameComponent' = lastNameComponent . show++getTypeName :: Name -> String+getTypeName x = lastNameComponent $ show x++allConstructorsAreNullary :: [ConstructorInfo] -> Bool+allConstructorsAreNullary constructors = and $ fmap isConstructorNullary constructors++isConstructorNullary :: ConstructorInfo -> Bool+isConstructorNullary (ConstructorInfo {constructorVariant, constructorFields}) = (constructorVariant == NormalConstructor) && (constructorFields == [])++-- In Template Haskell 2.10.0.0 and later, Pred is just a synonm for Type+-- In earlier versions, it has constructors+getDatatypePredicate :: Type -> Pred+#if MIN_VERSION_template_haskell(2,10,0)+getDatatypePredicate typ = AppT (ConT ''TypeScript) typ+#else+getDatatypePredicate typ = ClassP ''TypeScript [typ]+#endif++getTypeAsStringExp :: Type -> Q Exp+getTypeAsStringExp typ = [|getTypeScriptType (Proxy :: Proxy $(return typ))|]++getOptionalAsBoolExp :: Type -> Q Exp+getOptionalAsBoolExp typ = [|getTypeScriptOptional (Proxy :: Proxy $(return typ))|]++-- | Helper to apply a type constructor to a list of type args+applyToArgsT :: Type -> [Type] -> Type+applyToArgsT constructor [] = constructor+applyToArgsT constructor (x:xs) = applyToArgsT (AppT constructor x) xs++-- | Helper to apply a function a list of args+applyToArgsE :: Exp -> [Exp] -> Exp+applyToArgsE f [] = f+applyToArgsE f (x:xs) = applyToArgsE (AppE f x) xs++-- Between Aeson 1.1.2.0 and 1.2.0.0, tagSingleConstructors was added+getTagSingleConstructors :: Options -> Bool+#if MIN_VERSION_aeson(1,2,0)+getTagSingleConstructors options = tagSingleConstructors options+#else+getTagSingleConstructors _ = False+#endif++-- Between Template Haskell 2.10 and 2.11, the ability to look up which extensions are turned on was added+assertExtensionsTurnedOn :: DatatypeInfo -> Q ()+#if MIN_VERSION_template_haskell(2,11,0)+assertExtensionsTurnedOn (DatatypeInfo {..}) = do+  -- Check that necessary language extensions are turned on+  scopedTypeVariablesEnabled <- isExtEnabled ScopedTypeVariables+  kindSignaturesEnabled <- isExtEnabled KindSignatures+  when (not scopedTypeVariablesEnabled) $ error [i|The ScopedTypeVariables extension is required; please enable it before calling deriveTypeScript. (For example: put {-# LANGUAGE ScopedTypeVariables #-} at the top of the file.)|]+  when ((not kindSignaturesEnabled) && (length datatypeVars > 0)) $ error [i|The KindSignatures extension is required since type #{datatypeName} is a higher order type; please enable it before calling deriveTypeScript. (For example: put {-# LANGUAGE KindSignatures #-} at the top of the file.)|]+#else+assertExtensionsTurnedOn _ = return ()+#endif++-- Older versions of Aeson don't have an Eq instance for SumEncoding so we do this+isObjectWithSingleField :: SumEncoding -> Bool+isObjectWithSingleField ObjectWithSingleField = True+isObjectWithSingleField _ = False++-- Older versions of Aeson don't have an Eq instance for SumEncoding so we do this+isTwoElemArray :: SumEncoding -> Bool+isTwoElemArray TwoElemArray = True+isTwoElemArray _ = False++-- Older versions of Aeson don't have an Eq instance for SumEncoding so we do this+-- UntaggedValue was added between Aeson 0.11.3.0 and 1.0.0.0+isUntaggedValue :: SumEncoding -> Bool+#if MIN_VERSION_aeson(1,0,0)+isUntaggedValue UntaggedValue = True+#endif+isUntaggedValue _ = False++-- Between Template Haskell 2.10 and 2.11, InstanceD got an additional argument+mkInstance :: Cxt -> Type -> [Dec] -> Dec+#if MIN_VERSION_template_haskell(2,11,0)+mkInstance context typ decs = InstanceD Nothing context typ decs+#else+mkInstance context typ decs = InstanceD context typ decs+#endif++namesAndTypes :: Options -> ConstructorInfo -> [(String, Type)]+namesAndTypes options ci = case constructorVariant ci of+  RecordConstructor names -> zip (fmap ((fieldLabelModifier options) . lastNameComponent') names) (constructorFields ci)+  _ -> case sumEncoding options of+    TaggedObject _ contentsFieldName+      | isConstructorNullary ci -> []+      | otherwise -> [(contentsFieldName, contentsTupleType ci)]+    _ -> [(constructorNameToUse options ci, contentsTupleType ci)]++constructorNameToUse :: Options -> ConstructorInfo -> String+constructorNameToUse options ci = (constructorTagModifier options) $ lastNameComponent' (constructorName ci)++-- | Get the type of a tuple of constructor fields, as when we're packing a record-less constructor into a list+contentsTupleType :: ConstructorInfo -> Type+contentsTupleType ci = let fields = constructorFields ci in+  case length fields of+    0 -> AppT ListT (ConT ''())+    1 -> head fields+    x -> applyToArgsT (ConT $ tupleTypeName x) fields++getBracketsExpression :: Bool -> [(Name, String)] -> Q Exp+getBracketsExpression _ [] = [|""|]+getBracketsExpression includeSuffix names =+  [|let vars = $(genericVariablesListExpr includeSuffix names) in "<" <> L.intercalate ", " vars <> ">"|]++getBracketsExpressionAllTypesNoSuffix :: [(Name, String)] -> Q Exp+getBracketsExpressionAllTypesNoSuffix [] = [|""|]+getBracketsExpressionAllTypesNoSuffix names = [|"<" <> L.intercalate ", " $(listE [ [|(getTypeScriptType (Proxy :: Proxy $(varT x)))|] | (x, _suffix) <- names]) <> ">"|]++genericVariablesListExpr :: Bool -> [(Name, String)] -> Q Exp+genericVariablesListExpr includeSuffix genericVariables = listE (fmap (\((_, suffix), correspondingGeneric) ->+  [|(getTypeScriptType (Proxy :: Proxy $(return correspondingGeneric))) <> $(TH.stringE (if includeSuffix then suffix else ""))|])+  (case genericVariables of+      [x] -> [(x, ConT ''T)]+      xs -> zip xs allStarConstructors)+  )++isStarType :: Type -> Maybe Name+isStarType (SigT (VarT n) StarT) = Just n+isStarType _ = Nothing
+ test/Basic.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}++module Basic (tests) where++import Data.Aeson as A+import Data.Aeson.TypeScript.TH+import Data.Aeson.TypeScript.Types+import Data.Proxy+import Data.String.Interpolate.IsString+import Prelude hiding (Double)+import Test.Hspec+++data Unit1 = Unit1+$(deriveTypeScript A.defaultOptions ''Unit1)++data Unit2 = Unit2+$(deriveTypeScript (A.defaultOptions { A.tagSingleConstructors = True+                                     , A.constructorTagModifier = const "foo" }) ''Unit2)++tests :: SpecWith ()+tests = describe "Basic tests" $ do+  describe "tagSingleConstructors and constructorTagModifier" $ do+    it [i|Works with a normal unit|] $ do+      (getTypeScriptDeclarations (Proxy :: Proxy Unit1)) `shouldBe` ([+        TSTypeAlternatives "Unit1" [] ["IUnit1"]+        , TSTypeAlternatives "IUnit1" [] ["void[]"]+        ])++    it [i|Works with a unit with constructorTagModifier|] $ do+      (getTypeScriptDeclarations (Proxy :: Proxy Unit2)) `shouldBe` ([])+++main :: IO ()+main = hspec tests
+ test/Formatting.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE QuasiQuotes #-}++module Formatting (tests) where++import Data.Aeson (defaultOptions)+import Data.Aeson.TypeScript.TH+import Data.Aeson.TypeScript.Types+import Data.Proxy+import Data.String.Interpolate.IsString+import Test.Hspec++data D = S | F deriving (Eq, Show)++$(deriveTypeScript defaultOptions ''D)++tests :: Spec+tests = do+  describe "Formatting" $+    describe "when given a Sum Type" $ do+      describe "and the TypeAlias format option is set" $+        it "should generate a TS string literal type" $+          formatTSDeclarations' defaultFormattingOptions (getTypeScriptDeclarations @D Proxy) `shouldBe`+            [i|type D = "S" | "F";|]+      describe "and the Enum format option is set" $+        it "should generate a TS Enum" $+          formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = Enum }) (getTypeScriptDeclarations @D Proxy) `shouldBe`+            [i|enum D { S, F }|]+      describe "and the EnumWithType format option is set" $+        it "should generate a TS Enum with a type declaration" $+          formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = EnumWithType }) (getTypeScriptDeclarations @D Proxy) `shouldBe`+            [i|enum DEnum { S="S", F="F" }\n\ntype D = keyof typeof DEnum;|]
test/HigherKind.hs view
@@ -1,4 +1,17 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}  module HigherKind (tests) where @@ -32,10 +45,11 @@ $(deriveJSON A.defaultOptions ''HigherKindWithUnary)  +tests :: SpecWith () tests = describe "Higher kinds" $ do   describe "Kind * -> *" $ do     it [i|makes the declaration and types correctly|] $ do-      (getTypeScriptDeclarations (Proxy :: Proxy HigherKind)) `shouldBe` ([+      (getTypeScriptDeclarations (Proxy :: Proxy (HigherKind T))) `shouldBe` ([         TSTypeAlternatives "HigherKind" ["T"] ["IHigherKind<T>"],         TSInterfaceDeclaration "IHigherKind" ["T"] [TSField False "higherKindList" "T[]"]         ])@@ -51,14 +65,14 @@         ])      it [i|works with an interface inside|] $ do-      (getTypeScriptDeclarations (Proxy :: Proxy HigherKindWithUnary)) `shouldBe` ([+      (getTypeScriptDeclarations (Proxy :: Proxy (HigherKindWithUnary T))) `shouldBe` ([         TSTypeAlternatives "HigherKindWithUnary" ["T"] ["IUnary<T>"],         TSTypeAlternatives "IUnary" ["T"] ["number"]         ])    describe "Kind * -> * -> *" $ do     it [i|makes the declaration and type correctly|] $ do-      (getTypeScriptDeclarations (Proxy :: Proxy DoubleHigherKind)) `shouldBe` ([+      (getTypeScriptDeclarations (Proxy :: Proxy (DoubleHigherKind T1 T2))) `shouldBe` ([         TSTypeAlternatives "DoubleHigherKind" ["T1","T2"] ["IDoubleHigherKind<T1, T2>"],         TSInterfaceDeclaration "IDoubleHigherKind" ["T1","T2"] [TSField False "someList" "T2[]"                                                                , TSField False "higherKindThing" "HigherKind<T1>"]@@ -69,9 +83,9 @@    describe "TSC compiler checks" $ do     it "type checks everything with tsc" $ do-      let declarations = ((getTypeScriptDeclarations (Proxy :: Proxy HigherKind)) <>-                          (getTypeScriptDeclarations (Proxy :: Proxy DoubleHigherKind)) <>-                          (getTypeScriptDeclarations (Proxy :: Proxy HigherKindWithUnary))+      let declarations = ((getTypeScriptDeclarations (Proxy :: Proxy (HigherKind T))) <>+                          (getTypeScriptDeclarations (Proxy :: Proxy (DoubleHigherKind T1 T2))) <>+                          (getTypeScriptDeclarations (Proxy :: Proxy (HigherKindWithUnary T)))                          )        let typesAndValues = [(getTypeScriptType (Proxy :: Proxy (HigherKind Int)) , A.encode (HigherKind [42 :: Int]))@@ -86,11 +100,12 @@       testTypeCheckDeclarations declarations typesAndValues  +main :: IO () main = hspec tests -+main' :: IO () main' = putStrLn $ formatTSDeclarations (-   (getTypeScriptDeclarations (Proxy :: Proxy HigherKind)) <>-   (getTypeScriptDeclarations (Proxy :: Proxy DoubleHigherKind)) <>-   (getTypeScriptDeclarations (Proxy :: Proxy HigherKindWithUnary))+   (getTypeScriptDeclarations (Proxy :: Proxy (HigherKind T))) <>+   (getTypeScriptDeclarations (Proxy :: Proxy (DoubleHigherKind T1 T2))) <>+   (getTypeScriptDeclarations (Proxy :: Proxy (HigherKindWithUnary T)))   )
+ test/Live.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Live where++import Data.Aeson as A+import Data.Aeson.TypeScript.Recursive+import Data.Aeson.TypeScript.TH+import Data.Function+import Data.Functor.Identity+import Data.Kind+import Data.Proxy+import Data.String.Interpolate.IsString+import qualified Data.Text as T+import Prelude hiding (Double)+++instance TypeScript Identity where getTypeScriptType _ = "any"++data SingleDE = SingleDE+instance TypeScript SingleDE where getTypeScriptType _ = [i|"single"|]++data K8SDE = K8SDE+instance TypeScript K8SDE where getTypeScriptType _ = [i|"k8s"|]++data SingleNodeEnvironment = SingleNodeEnvironment+  deriving (Eq, Show)+instance TypeScript SingleNodeEnvironment where getTypeScriptType _ = [i|"single_node_env"|]+                                  +data K8SEnvironment = K8SEnvironment+  deriving (Eq, Show)+instance TypeScript K8SEnvironment where getTypeScriptType _ = [i|"k8s_env"|]++data Nullable (c :: Type -> Type) x+data Exposed x+type family Columnar (f :: Type -> Type) x where+    Columnar Exposed x = Exposed x+    Columnar Identity x = x+    Columnar (Nullable c) x = Columnar c (Maybe x)+    Columnar f x = f x++type family DeployEnvironment env = result | result -> env where+  DeployEnvironment SingleNodeEnvironment = SingleDE+  DeployEnvironment K8SEnvironment = K8SDE+  DeployEnvironment T = ()++-- * The main type++data UserT env f = User {+  _userUsername :: Columnar f T.Text+  , _userCreatedAt  :: Columnar f Int+  , _userDeployEnvironment  :: Columnar f (DeployEnvironment env)+  }++$(deriveTypeScript' A.defaultOptions ''UserT (ExtraTypeScriptOptions [''DeployEnvironment]))++main :: IO ()+main = getTypeScriptDeclarationsRecursively (Proxy :: Proxy (UserT T Identity))+     & formatTSDeclarations+     & putStrLn
+ test/Live2.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilyDependencies #-}++module Live2 where++import Data.Aeson as A+import Data.Aeson.TypeScript.TH+import Data.Function+import Data.Proxy++       +data TestT a = TestT {+  listOfA :: [a]+  , maybeA :: Maybe a+  }+$(deriveTypeScript A.defaultOptions ''TestT)++main :: IO ()+main = getTypeScriptDeclarations (Proxy :: Proxy (TestT Int))+     & formatTSDeclarations+     & putStrLn
+ test/LiveLogging.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilies #-}++module LiveLogging where++import Data.Kind+import Prelude hiding (Double)+++data LoggingSource = SGeneral++data LoggingSourceTagged s where+  General :: LoggingSourceTagged 'SGeneral++type family ParamsFamily (q :: LoggingSource) :: Type where+  ParamsFamily 'SGeneral = String++data HigherKindWithTypeFamily s = TapMessageParams { params :: ParamsFamily s }+-- $(deriveTypeScript A.defaultOptions ''HigherKindWithTypeFamily)++-- main = do+--   putStrLn $(stringE . pprint =<< (deriveTypeScript A.defaultOptions ''TestT))
+ test/NoOmitNothingFields.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module NoOmitNothingFields (main, tests) where++import Data.Aeson as A+import Data.Aeson.TypeScript.TH+import Data.Aeson.TypeScript.Types+import Data.Proxy+import Test.Hspec+import TestBoilerplate++$(testDeclarations "NoOmitNothingFields" (A.defaultOptions {omitNothingFields=False}))++main :: IO ()+main = hspec $ describe "NoOmitNothingFields" $ do+  it "encodes as expected" $ do+    let decls = getTypeScriptDeclarations (Proxy :: Proxy Optional)++    decls `shouldBe` [TSInterfaceDeclaration {+                         interfaceName = "Optional"+                         , interfaceGenericVariables = []+                         , interfaceMembers = [+                             TSField {fieldOptional = False+                                     , fieldName = "optionalInt"+                                     , fieldType = "number | null"}+                             ]+                         }]++  tests
test/ObjectWithSingleFieldNoTagSingleConstructors.hs view
@@ -1,13 +1,23 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module ObjectWithSingleFieldNoTagSingleConstructors (tests) where+module ObjectWithSingleFieldNoTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate-import Util  $(testDeclarations "ObjectWithSingleField with tagSingleConstructors=False" (A.defaultOptions {sumEncoding=ObjectWithSingleField})) +main :: IO () main = hspec tests
test/ObjectWithSingleFieldTagSingleConstructors.hs view
@@ -1,13 +1,24 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module ObjectWithSingleFieldTagSingleConstructors (tests) where+module ObjectWithSingleFieldTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate import Util  $(testDeclarations "ObjectWithSingleField with tagSingleConstructors=True" (setTagSingleConstructors $ A.defaultOptions {sumEncoding=ObjectWithSingleField})) +main :: IO () main = hspec tests
+ test/OmitNothingFields.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module OmitNothingFields (main, tests) where++import Data.Aeson as A+import Data.Aeson.TypeScript.TH+import Data.Aeson.TypeScript.Types+import Data.Proxy+import Test.Hspec+import TestBoilerplate++$(testDeclarations "OmitNothingFields" (A.defaultOptions {omitNothingFields=True}))++main :: IO ()+main = hspec $ describe "OmitNothingFields" $ do+  it "encodes as expected" $ do+    let decls = getTypeScriptDeclarations (Proxy :: Proxy Optional)++    decls `shouldBe` [TSInterfaceDeclaration {+                         interfaceName = "Optional"+                         , interfaceGenericVariables = []+                         , interfaceMembers = [+                             TSField {fieldOptional = True+                                     , fieldName = "optionalInt"+                                     , fieldType = "number"}+                             ]+                         }]++  tests
test/Spec.hs view
@@ -3,7 +3,10 @@  import Test.Hspec +import qualified Formatting import qualified HigherKind+import qualified TypeFamilies+ import qualified ObjectWithSingleFieldNoTagSingleConstructors import qualified ObjectWithSingleFieldTagSingleConstructors import qualified TaggedObjectNoTagSingleConstructors@@ -12,8 +15,16 @@ import qualified TwoElemArrayTagSingleConstructors import qualified UntaggedNoTagSingleConstructors import qualified UntaggedTagSingleConstructors+import qualified OmitNothingFields+import qualified NoOmitNothingFields ++main :: IO () main = hspec $ do+  Formatting.tests+  HigherKind.tests+  TypeFamilies.tests+   ObjectWithSingleFieldTagSingleConstructors.tests   ObjectWithSingleFieldNoTagSingleConstructors.tests   TaggedObjectTagSingleConstructors.tests@@ -22,4 +33,5 @@   TwoElemArrayNoTagSingleConstructors.tests   UntaggedTagSingleConstructors.tests   UntaggedNoTagSingleConstructors.tests-  HigherKind.tests+  OmitNothingFields.tests+  NoOmitNothingFields.tests
test/TaggedObjectNoTagSingleConstructors.hs view
@@ -1,13 +1,23 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module TaggedObjectNoTagSingleConstructors (tests) where+module TaggedObjectNoTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate-import Util  $(testDeclarations "TaggedObject with tagSingleConstructors=False" A.defaultOptions) +main :: IO () main = hspec tests
test/TaggedObjectTagSingleConstructors.hs view
@@ -1,13 +1,24 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module TaggedObjectTagSingleConstructors (tests) where+module TaggedObjectTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate import Util  $(testDeclarations "TaggedObject with tagSingleConstructors=True" (setTagSingleConstructors A.defaultOptions)) +main :: IO () main = hspec tests
test/TestBoilerplate.hs view
@@ -1,4 +1,16 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}  module TestBoilerplate where @@ -6,8 +18,11 @@ import qualified Data.Aeson as A import Data.Aeson.TH as A import Data.Aeson.TypeScript.TH+import Data.Functor.Identity+import Data.Kind import Data.Proxy-import Language.Haskell.TH+import Data.String.Interpolate.IsString+import Language.Haskell.TH hiding (Type) import Test.Hspec import Util @@ -19,8 +34,34 @@ data Hybrid = HybridSimple Int | HybridRecord { hybridString :: String } data TwoConstructor = Con1 { con1String :: String } | Con2 { con2String :: String, con2Int :: Int } data Complex a = Nullary | Unary Int | Product String Char a | Record { testOne :: Int, testTwo :: Bool, testThree :: Complex a} deriving Eq+data Optional = Optional {optionalInt :: Maybe Int} +-- * For testing type families +instance TypeScript Identity where getTypeScriptType _ = "any"++data SingleDE = SingleDE+instance TypeScript SingleDE where getTypeScriptType _ = [i|"single"|]++data K8SDE = K8SDE+instance TypeScript K8SDE where getTypeScriptType _ = [i|"k8s"|]++data SingleNodeEnvironment = SingleNodeEnvironment deriving (Eq, Show)+instance TypeScript SingleNodeEnvironment where getTypeScriptType _ = [i|"single_node_env"|]++data K8SEnvironment = K8SEnvironment deriving (Eq, Show)+instance TypeScript K8SEnvironment where getTypeScriptType _ = [i|"k8s_env"|]++data Nullable (c :: Type -> Type) x+data Exposed x+type family Columnar (f :: Type -> Type) x where+    Columnar Exposed x = Exposed x+    Columnar Identity x = x+    Columnar (Nullable c) x = Columnar c (Maybe x)+    Columnar f x = f x++-- * Declarations+ testDeclarations :: String -> A.Options -> Q [Dec] testDeclarations testName aesonOptions = do   decls :: [Dec] <- execWriterT $ do@@ -32,6 +73,7 @@     deriveInstances ''Hybrid     deriveInstances ''TwoConstructor     deriveInstances ''Complex+    deriveInstances ''Optional    typesAndValues :: Exp <- [e|[(getTypeScriptType (Proxy :: Proxy Unit), A.encode Unit) @@ -52,7 +94,9 @@                               , (getTypeScriptType (Proxy :: Proxy (Complex Int)), A.encode (Nullary :: Complex Int))                               , (getTypeScriptType (Proxy :: Proxy (Complex Int)), A.encode (Unary 42 :: Complex Int))                               , (getTypeScriptType (Proxy :: Proxy (Complex Int)), A.encode (Product "asdf" 'g' 42 :: Complex Int))-                              , (getTypeScriptType (Proxy :: Proxy (Complex Int)), A.encode ((Record { testOne = 3, testTwo = True, testThree = Product "test" 'A' 123}) :: Complex Int))]+                              , (getTypeScriptType (Proxy :: Proxy (Complex Int)), A.encode ((Record { testOne = 3, testTwo = True, testThree = Product "test" 'A' 123}) :: Complex Int))+                              , (getTypeScriptType (Proxy :: Proxy Optional), A.encode (Optional { optionalInt = Nothing }))+                              , (getTypeScriptType (Proxy :: Proxy Optional), A.encode (Optional { optionalInt = Just 1 }))]                            |]    declarations :: Exp <- [e|getTypeScriptDeclarations (Proxy :: Proxy Unit)@@ -62,10 +106,12 @@                          <> getTypeScriptDeclarations (Proxy :: Proxy TwoField)                          <> getTypeScriptDeclarations (Proxy :: Proxy Hybrid)                          <> getTypeScriptDeclarations (Proxy :: Proxy TwoConstructor)-                         <> getTypeScriptDeclarations (Proxy :: Proxy Complex)+                         <> getTypeScriptDeclarations (Proxy :: Proxy (Complex T))+                         <> getTypeScriptDeclarations (Proxy :: Proxy Optional)                          |] -  tests <- [d|tests = describe $(return $ LitE $ StringL testName) $ it "type checks everything with tsc" $ testTypeCheckDeclarations $(return declarations) $(return typesAndValues)|]+  tests <- [d|tests :: SpecWith ()+              tests = describe $(return $ LitE $ StringL testName) $ it "type checks everything with tsc" $ testTypeCheckDeclarations $(return declarations) $(return typesAndValues)|]    return $ decls ++ tests 
test/TwoElemArrayNoTagSingleConstructors.hs view
@@ -1,13 +1,23 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module TwoElemArrayNoTagSingleConstructors (tests) where+module TwoElemArrayNoTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate-import Util  $(testDeclarations "TwoElemArray with tagSingleConstructors=False" (A.defaultOptions {sumEncoding=TwoElemArray})) +main :: IO () main = hspec tests
test/TwoElemArrayTagSingleConstructors.hs view
@@ -1,13 +1,24 @@-{-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module TwoElemArrayTagSingleConstructors (tests) where+module TwoElemArrayTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate import Util  $(testDeclarations "TwoElemArray with tagSingleConstructors=True" (setTagSingleConstructors $ A.defaultOptions {sumEncoding=TwoElemArray})) +main :: IO () main = hspec tests
+ test/TypeFamilies.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}++module TypeFamilies (tests) where++import Data.Aeson as A+import Data.Aeson.TypeScript.Recursive+import Data.Aeson.TypeScript.TH+import Data.Aeson.TypeScript.Types+import Data.Functor.Identity+import Data.Proxy+import Data.String.Interpolate.IsString+import qualified Data.Text as T+import Prelude hiding (Double)+import Test.Hspec+import TestBoilerplate+++type family DeployEnvironment env = result | result -> env where+  DeployEnvironment SingleNodeEnvironment = SingleDE+  DeployEnvironment K8SEnvironment = K8SDE+  DeployEnvironment T = ()+data UserT env f = User {+  _userUsername :: Columnar f T.Text+  , _userCreatedAt  :: Columnar f Int+  , _userDeployEnvironment  :: Columnar f (DeployEnvironment env)+  }+$(deriveTypeScript' A.defaultOptions ''UserT (ExtraTypeScriptOptions [''DeployEnvironment]))++type family DeployEnvironment2 env = result | result -> env where+  DeployEnvironment2 SingleNodeEnvironment = SingleDE+  DeployEnvironment2 K8SEnvironment = K8SDE+  DeployEnvironment2 T = ()+newtype Simple env = Simple (DeployEnvironment2 env)+$(deriveTypeScript' A.defaultOptions ''Simple (ExtraTypeScriptOptions [''DeployEnvironment2]))++tests :: SpecWith ()+tests = describe "Type families" $ do+  describe "simple newtype" $ do+    it [i|makes the declaration and types correctly|] $ do+      (getTypeScriptDeclarationsRecursively (Proxy :: Proxy (Simple T))) `shouldBe` ([+        TSInterfaceDeclaration "DeployEnvironment2" [] [+          TSField False "\"single_node_env\"" "\"single\""+          , TSField False "\"k8s_env\"" "\"k8s\""+          , TSField False "T" "void"+          ]+        , TSTypeAlternatives "ISimple" ["T extends keyof DeployEnvironment2"] ["DeployEnvironment2[T]"]+        , TSTypeAlternatives "Simple" ["T extends keyof DeployEnvironment2"] ["ISimple<T>"]+        ])++  describe "Complicated Beam-like user type" $ do+    it [i|makes the declaration and types correctly|] $ do+      (getTypeScriptDeclarations (Proxy :: Proxy (UserT T Identity))) `shouldBe` ([+        TSTypeAlternatives "UserT" ["T extends keyof DeployEnvironment"] ["IUser<T>"]+        , TSInterfaceDeclaration "IUser" ["T extends keyof DeployEnvironment"] [+            TSField False "_userUsername" "string"+            , TSField False "_userCreatedAt" "number"+            , TSField False "_userDeployEnvironment" "DeployEnvironment[T]"+            ]+        ])++    it [i|get the declarations recursively|] $ do+      (getTypeScriptDeclarationsRecursively (Proxy :: Proxy (UserT T Identity))) `shouldBe` ([+        TSInterfaceDeclaration "DeployEnvironment" [] [+          TSField False "\"single_node_env\"" "\"single\""+          , TSField False "\"k8s_env\"" "\"k8s\""+          , TSField False "T" "void"+          ]+        , TSInterfaceDeclaration "IUser" ["T extends keyof DeployEnvironment"] [+            TSField False "_userUsername" "string"+            , TSField False "_userCreatedAt" "number"+            , TSField False "_userDeployEnvironment" "DeployEnvironment[T]"+            ]+        , TSTypeAlternatives "UserT" ["T extends keyof DeployEnvironment"] ["IUser<T>"]+        ])++main :: IO ()+main = hspec tests
test/UntaggedNoTagSingleConstructors.hs view
@@ -1,12 +1,21 @@-{-# LANGUAGE CPP, QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module UntaggedNoTagSingleConstructors (tests) where+module UntaggedNoTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate-import Util  -- Between Aeson 0.11.3.0 and 1.0.0.0, UntaggedValue was added -- Disable these tests if it's not present@@ -16,4 +25,5 @@ tests = describe "UntaggedNoTagSingleConstructors" $ it "tests are disabled for this Aeson version" $ 2 `shouldBe` 2 #endif +main :: IO () main = hspec tests
test/UntaggedTagSingleConstructors.hs view
@@ -1,9 +1,19 @@-{-# LANGUAGE CPP, QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, KindSignatures #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -module UntaggedTagSingleConstructors (tests) where+module UntaggedTagSingleConstructors (main, tests) where  import Data.Aeson as A-import Data.Aeson.TH as A import Test.Hspec import TestBoilerplate import Util@@ -13,7 +23,9 @@ #if MIN_VERSION_aeson(1,0,0) $(testDeclarations "UntaggedTagSingleConstructors" (setTagSingleConstructors $ A.defaultOptions {sumEncoding=UntaggedValue})) #else+tests :: SpecWith () tests = describe "UntaggedTagSingleConstructors" $ it "tests are disabled for this Aeson version" $ 2 `shouldBe` 2 #endif +main :: IO () main = hspec tests
test/Util.hs view
@@ -1,10 +1,9 @@-{-# LANGUAGE CPP, QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns #-}+{-# LANGUAGE CPP, QuasiQuotes, OverloadedStrings, TemplateHaskell, RecordWildCards, ScopedTypeVariables, NamedFieldPuns, LambdaCase #-}  module Util where  import Control.Monad import Data.Aeson as A-import Data.Aeson.TH as A import Data.Aeson.TypeScript.TH import qualified Data.ByteString.Lazy as B import Data.Proxy@@ -17,6 +16,7 @@ import System.IO.Temp import System.Process +npmInstallScript, yarnInstallScript, localTSC :: String npmInstallScript = "test/assets/npm_install.sh" yarnInstallScript = "test/assets/yarn_install.sh" localTSC = "test/assets/node_modules/.bin/tsc"@@ -25,14 +25,12 @@ isCI = lookupEnv "CI" >>= (return . (== (Just "true")))  getTSC :: IO FilePath-getTSC = do-  isCI <- isCI-  case isCI of-    True -> do-      return "tsc" -- Assume it's set up on the path-    False -> do-      ensureTSCExists-      return localTSC+getTSC = isCI >>= \case+  True -> do+    return "tsc" -- Assume it's set up on the path+  False -> do+    ensureTSCExists+    return localTSC  testTypeCheck :: forall a. (TypeScript a, ToJSON a) => a -> IO () testTypeCheck obj = withSystemTempDirectory "typescript_test" $ \folder -> do@@ -46,19 +44,20 @@    -- "--diagnostics", "--listFiles"   tsc <- getTSC-  readProcess tsc ["--noEmit", "--skipLibCheck", "--traceResolution", "--noResolve", tsFile] ""+  void $ readProcess tsc ["--noEmit", "--skipLibCheck", "--traceResolution", "--noResolve", tsFile] ""    return ()   where tsDeclarations :: [TSDeclaration] = getTypeScriptDeclarations (Proxy :: Proxy a)         tsType :: String = getTypeScriptType (Proxy :: Proxy a)  +getTSFile :: [TSDeclaration] -> [(String, B.ByteString)] -> String getTSFile tsDeclarations typesAndVals = [i| #{formatTSDeclarations tsDeclarations}  #{T.unlines typeLines} |]-  where typeLines = [[i|let x#{index}: #{typ} = #{val};|] | (index, (typ, val)) <- zip [1..] typesAndVals]+  where typeLines = [[i|let x#{index}: #{typ} = #{val};|] | (index, (typ, val)) <- zip [(1 :: Int)..] typesAndVals]   testTypeCheckDeclarations :: [TSDeclaration] -> [(String, B.ByteString)] -> IO ()@@ -70,11 +69,10 @@   writeFile tsFile contents    tsc <- getTSC-  (code, output, err) <- readProcessWithExitCode tsc ["--noEmit", "--skipLibCheck", "--traceResolution", "--noResolve", tsFile] ""+  (code, output, _err) <- readProcessWithExitCode tsc ["--strict", "--noEmit", "--skipLibCheck", "--traceResolution", "--noResolve", tsFile] ""    when (code /= ExitSuccess) $ do-    putStrLn [i|TSC check failed. File contents were\n\n#{contents}|]-    error [i|TSC check failed: #{output}|]+    error [i|TSC check failed: #{output}. File contents were\n\n#{contents}|]    return ()