Skip to content

Commit

Permalink
Changes approach to optional fields
Browse files Browse the repository at this point in the history
  • Loading branch information
GambolingPangolin authored and bradsherman committed Jan 22, 2024
1 parent b834e60 commit 955664d
Show file tree
Hide file tree
Showing 5 changed files with 732 additions and 51 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

## 0.7.0.0

Represents optional fields (according to the Haskell model) as required fields
with type `A | null` rather than optional fields.

## 0.6.2.0

* Expose generic type constructors `T4` through `T10`. (We only exposed `T`, `T1`, `T2`, and `T3` before.)
Expand Down
95 changes: 49 additions & 46 deletions src/Data/Aeson/TypeScript/Formatting.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,58 +15,58 @@ import qualified Data.Text as T
import Data.Monoid
#endif


-- | Same as 'formatTSDeclarations'', but uses default formatting options.
formatTSDeclarations :: [TSDeclaration] -> String
formatTSDeclarations = formatTSDeclarations' defaultFormattingOptions

-- | 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 maybeDoc) =
makeDocPrefix maybeDoc <> mainDeclaration
formatTSDeclaration (FormattingOptions{..}) (TSTypeAlternatives name genericVariables names maybeDoc) =
makeDocPrefix maybeDoc <> mainDeclaration
where
mainDeclaration = case chooseTypeAlternativesFormat typeAlternativesFormat of
Enum -> [i|#{exportPrefix exportMode}enum #{typeNameModifier name} { #{alternativesEnum} }|]
where
alternativesEnum = T.intercalate ", " $ [toEnumName entry <> "=" <> entry | entry <- T.pack <$> names]
EnumWithType -> [i|#{exportPrefix exportMode}enum #{typeNameModifier name}Enum { #{alternativesEnumWithType} }#{enumType}|]
where
alternativesEnumWithType = T.intercalate ", " $ [toEnumName entry <> "=" <> entry | entry <- T.pack <$> names]
enumType = [i|\n\ntype #{name} = keyof typeof #{typeNameModifier name}Enum;|] :: T.Text
TypeAlias -> [i|#{exportPrefix exportMode}type #{typeNameModifier name}#{getGenericBrackets genericVariables} = #{alternatives};|]
where
alternatives = T.intercalate " | " (fmap T.pack names)
Enum -> [i|#{exportPrefix exportMode}enum #{typeNameModifier name} { #{alternativesEnum} }|]
where
alternativesEnum = T.intercalate ", " $ [toEnumName entry <> "=" <> entry | entry <- T.pack <$> names]
EnumWithType -> [i|#{exportPrefix exportMode}enum #{typeNameModifier name}Enum { #{alternativesEnumWithType} }#{enumType}|]
where
alternativesEnumWithType = T.intercalate ", " $ [toEnumName entry <> "=" <> entry | entry <- T.pack <$> names]
enumType = [i|\n\ntype #{name} = keyof typeof #{typeNameModifier name}Enum;|] :: T.Text
TypeAlias -> [i|#{exportPrefix exportMode}type #{typeNameModifier name}#{getGenericBrackets genericVariables} = #{alternatives};|]
where
alternatives = T.intercalate " | " (fmap T.pack names)

-- Only allow certain formats if some checks pass
chooseTypeAlternativesFormat Enum
| all isDoubleQuotedString names = Enum
| otherwise = TypeAlias
| all isDoubleQuotedString names = Enum
| otherwise = TypeAlias
chooseTypeAlternativesFormat EnumWithType
| all isDoubleQuotedString names = EnumWithType
| otherwise = TypeAlias
| all isDoubleQuotedString names = EnumWithType
| otherwise = TypeAlias
chooseTypeAlternativesFormat x = x

isDoubleQuotedString s = case A.eitherDecode (BL8.pack s) of
Right (A.String _) -> True
_ -> False
Right (A.String _) -> True
_ -> False

toEnumName = T.replace "\"" ""

formatTSDeclaration (FormattingOptions {..}) (TSInterfaceDeclaration interfaceName genericVariables (filter (not . isNoEmitTypeScriptField) -> members) maybeDoc) =
makeDocPrefix maybeDoc <> [i|#{exportPrefix exportMode}interface #{modifiedInterfaceName}#{getGenericBrackets genericVariables} {
formatTSDeclaration (FormattingOptions{..}) (TSInterfaceDeclaration interfaceName genericVariables (filter (not . isNoEmitTypeScriptField) -> members) maybeDoc) =
makeDocPrefix maybeDoc
<> [i|#{exportPrefix exportMode}interface #{modifiedInterfaceName}#{getGenericBrackets genericVariables} {
#{ls}
}|] where
ls = T.intercalate "\n" $ [indentTo numIndentSpaces (T.pack (formatTSField member <> ";")) | member <- members]
modifiedInterfaceName = (\(li, name) -> li <> interfaceNameModifier name) . splitAt 1 $ interfaceName

formatTSField :: TSField -> String
formatTSField (TSField optional name typ maybeDoc') = makeDocPrefix maybeDoc' <> [i|#{name}#{if optional then ("?" :: String) else ""}: #{typ}|]
}|]
where
ls = T.intercalate "\n" $ [indentTo numIndentSpaces (T.pack (formatTSField member <> ";")) | member <- members]
modifiedInterfaceName = (\(li, name) -> li <> interfaceNameModifier name) . splitAt 1 $ interfaceName

formatTSField :: TSField -> String
formatTSField (TSField optional name typ maybeDoc') = makeDocPrefix maybeDoc' <> [i|#{name}: #{typ}#{if optional then ("| null" :: String) else ""}|]
formatTSDeclaration _ (TSRawDeclaration text) = text

indentTo :: Int -> T.Text -> T.Text
indentTo numIndentSpaces input = T.intercalate "\n" [padding <> line | line <- T.splitOn "\n" input]
where padding = T.replicate numIndentSpaces " "
where
padding = T.replicate numIndentSpaces " "

exportPrefix :: ExportMode -> String
exportPrefix ExportEach = "export "
Expand All @@ -75,32 +75,35 @@ 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 allDeclarations =
declarations & fmap (T.pack . formatTSDeclaration options)
& T.intercalate "\n\n"
& T.unpack
declarations
& fmap (T.pack . formatTSDeclaration options)
& T.intercalate "\n\n"
& T.unpack
where
removedDeclarationNames = mapMaybe getDeclarationName (filter isNoEmitTypeScriptDeclaration allDeclarations)
where
getDeclarationName :: TSDeclaration -> Maybe String
getDeclarationName (TSInterfaceDeclaration {..}) = Just interfaceName
getDeclarationName (TSTypeAlternatives {..}) = Just typeName
getDeclarationName (TSInterfaceDeclaration{..}) = Just interfaceName
getDeclarationName (TSTypeAlternatives{..}) = Just typeName
getDeclarationName _ = Nothing

removeReferencesToRemovedNames :: [String] -> TSDeclaration -> TSDeclaration
removeReferencesToRemovedNames removedNames decl@(TSTypeAlternatives {..}) = decl { alternativeTypes = [x | x <- alternativeTypes, not (x `L.elem` removedNames)] }
removeReferencesToRemovedNames removedNames decl@(TSTypeAlternatives{..}) = decl{alternativeTypes = [x | x <- alternativeTypes, not (x `L.elem` removedNames)]}
removeReferencesToRemovedNames _ x = x

declarations = allDeclarations
& filter (not . isNoEmitTypeScriptDeclaration)
& fmap (removeReferencesToRemovedNames removedDeclarationNames)
declarations =
allDeclarations
& filter (not . isNoEmitTypeScriptDeclaration)
& fmap (removeReferencesToRemovedNames removedDeclarationNames)

makeDocPrefix :: Maybe String -> String
makeDocPrefix maybeDoc = case maybeDoc of
Nothing -> ""
Just (T.pack -> text) -> ["// " <> line | line <- T.splitOn "\n" text]
& T.intercalate "\n"
& (<> "\n")
& T.unpack
Nothing -> ""
Just (T.pack -> text) ->
["// " <> line | line <- T.splitOn "\n" text]
& T.intercalate "\n"
& (<> "\n")
& T.unpack

getGenericBrackets :: [String] -> String
getGenericBrackets [] = ""
Expand All @@ -112,10 +115,10 @@ noEmitTypeScriptAnnotation :: String
noEmitTypeScriptAnnotation = "@no-emit-typescript"

isNoEmitTypeScriptField :: TSField -> Bool
isNoEmitTypeScriptField (TSField {fieldDoc=(Just doc)}) = noEmitTypeScriptAnnotation `L.isInfixOf` doc
isNoEmitTypeScriptField (TSField{fieldDoc = (Just doc)}) = noEmitTypeScriptAnnotation `L.isInfixOf` doc
isNoEmitTypeScriptField _ = False

isNoEmitTypeScriptDeclaration :: TSDeclaration -> Bool
isNoEmitTypeScriptDeclaration (TSInterfaceDeclaration {interfaceDoc=(Just doc)}) = noEmitTypeScriptAnnotation `L.isInfixOf` doc
isNoEmitTypeScriptDeclaration (TSTypeAlternatives {typeDoc=(Just doc)}) = noEmitTypeScriptAnnotation `L.isInfixOf` doc
isNoEmitTypeScriptDeclaration (TSInterfaceDeclaration{interfaceDoc = (Just doc)}) = noEmitTypeScriptAnnotation `L.isInfixOf` doc
isNoEmitTypeScriptDeclaration (TSTypeAlternatives{typeDoc = (Just doc)}) = noEmitTypeScriptAnnotation `L.isInfixOf` doc
isNoEmitTypeScriptDeclaration _ = False
2 changes: 1 addition & 1 deletion stack.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

resolver: nightly-2023-11-14
resolver: lts-22.7

packages:
- .
8 changes: 4 additions & 4 deletions stack.yaml.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
packages: []
snapshots:
- completed:
sha256: 0eaacfc9de6b0ab46ab6026166d2ba7718ab06a8612086b3ee21d7667e682df0
size: 698974
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2023/11/14.yaml
original: nightly-2023-11-14
sha256: 7b975b104cb3dbf0c297dfd01f936a4d2ee523241dd0b1ae960522b833fe3027
size: 714096
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/7.yaml
original: lts-22.7
Loading

0 comments on commit 955664d

Please sign in to comment.