summaryrefslogtreecommitdiffhomepage
path: root/lib/Language/Haskell/Stylish/Step/Data.hs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Language/Haskell/Stylish/Step/Data.hs')
-rw-r--r--lib/Language/Haskell/Stylish/Step/Data.hs126
1 files changed, 126 insertions, 0 deletions
diff --git a/lib/Language/Haskell/Stylish/Step/Data.hs b/lib/Language/Haskell/Stylish/Step/Data.hs
new file mode 100644
index 0000000..1f7732b
--- /dev/null
+++ b/lib/Language/Haskell/Stylish/Step/Data.hs
@@ -0,0 +1,126 @@
+{-# LANGUAGE RecordWildCards #-}
+
+module Language.Haskell.Stylish.Step.Data where
+
+import Data.List (find, intercalate)
+import Data.Maybe (fromMaybe, maybeToList)
+import qualified Language.Haskell.Exts as H
+import Language.Haskell.Exts.Comments
+import Language.Haskell.Stylish.Block
+import Language.Haskell.Stylish.Editor
+import Language.Haskell.Stylish.Step
+import Language.Haskell.Stylish.Util
+import Prelude hiding (init)
+
+data Indent
+ = SameLine
+ | Indent !Int
+ deriving (Show)
+
+data Config = Config
+ { cEquals :: !Indent
+ -- ^ Indent between type constructor and @=@ sign (measured from column 0)
+ , cFirstField :: !Indent
+ -- ^ Indent between data constructor and @{@ line (measured from column with data constructor name)
+ , cFieldComment :: !Int
+ -- ^ Indent between column with @{@ and start of field line comment (this line has @cFieldComment = 2@)
+ , cDeriving :: !Int
+ -- ^ Indent before @deriving@ lines (measured from column 0)
+ } deriving (Show)
+
+datas :: H.Module l -> [H.Decl l]
+datas (H.Module _ _ _ _ decls) = decls
+datas _ = []
+
+type ChangeLine = Change String
+
+step :: Config -> Step
+step cfg = makeStep "Data" (step' cfg)
+
+step' :: Config -> Lines -> Module -> Lines
+step' cfg ls (module', allComments) = applyChanges changes ls
+ where
+ datas' = datas $ fmap linesFromSrcSpan module'
+ changes = datas' >>= maybeToList . changeDecl allComments cfg
+
+findCommentOnLine :: LineBlock -> [Comment] -> Maybe Comment
+findCommentOnLine lb = find commentOnLine
+ where
+ commentOnLine (Comment _ (H.SrcSpan _ start _ end _) _) =
+ blockStart lb == start && blockEnd lb == end
+
+findCommentBelowLine :: LineBlock -> [Comment] -> Maybe Comment
+findCommentBelowLine lb = find commentOnLine
+ where
+ commentOnLine (Comment _ (H.SrcSpan _ start _ end _) _) =
+ blockStart lb == start - 1 && blockEnd lb == end - 1
+
+commentsWithin :: LineBlock -> [Comment] -> [Comment]
+commentsWithin lb = filter within
+ where
+ within (Comment _ (H.SrcSpan _ start _ end _) _) =
+ start >= blockStart lb && end <= blockEnd lb
+
+changeDecl :: [Comment] -> Config -> H.Decl LineBlock -> Maybe ChangeLine
+changeDecl _ _ (H.DataDecl _ (H.DataType _) Nothing _ [] _) = Nothing
+changeDecl allComments cfg@Config{..} (H.DataDecl block (H.DataType _) Nothing dhead decls derivings)
+ | hasRecordFields = Just $ change block (const $ concat newLines)
+ | otherwise = Nothing
+ where
+ hasRecordFields = any
+ (\qual -> case qual of
+ (H.QualConDecl _ _ _ (H.RecDecl {})) -> True
+ _ -> False)
+ decls
+
+ typeConstructor = "data " <> H.prettyPrint dhead
+
+ -- In any case set @pipeIndent@ such that @|@ is aligned with @=@.
+ (firstLine, firstLineInit, pipeIndent) =
+ case cEquals of
+ SameLine -> (Nothing, typeConstructor <> " = ", length typeConstructor + 1)
+ Indent n -> (Just [[typeConstructor]], indent n "= ", n)
+
+ newLines = fromMaybe [] firstLine ++ fmap constructors zipped <> [fmap (indent cDeriving . H.prettyPrint) derivings]
+ zipped = zip decls ([1..] ::[Int])
+
+ constructors (decl, 1) = processConstructor allComments firstLineInit cfg decl
+ constructors (decl, _) = processConstructor allComments (indent pipeIndent "| ") cfg decl
+changeDecl _ _ _ = Nothing
+
+processConstructor :: [Comment] -> String -> Config -> H.QualConDecl LineBlock -> [String]
+processConstructor allComments init Config{..} (H.QualConDecl _ _ _ (H.RecDecl _ dname (f:fs))) = do
+ fromMaybe [] firstLine <> n1 <> ns <> [indent fieldIndent "}"]
+ where
+ n1 = processName firstLinePrefix (extractField f)
+ ns = fs >>= processName (indent fieldIndent ", ") . extractField
+
+ -- Set @fieldIndent@ such that @,@ is aligned with @{@.
+ (firstLine, firstLinePrefix, fieldIndent) =
+ case cFirstField of
+ SameLine ->
+ ( Nothing
+ , init <> H.prettyPrint dname <> " { "
+ , length init + length (H.prettyPrint dname) + 1
+ )
+ Indent n ->
+ ( Just [init <> H.prettyPrint dname]
+ , indent (length init + n) "{ "
+ , length init + n
+ )
+
+ processName prefix (fnames, _type, lineComment, commentBelowLine) =
+ [prefix <> intercalate ", " (fmap H.prettyPrint fnames) <> " :: " <> H.prettyPrint _type <> addLineComment lineComment
+ ] ++ addCommentBelow commentBelowLine
+
+ addLineComment (Just (Comment _ _ c)) = " --" <> c
+ addLineComment Nothing = ""
+
+ -- Field comment indent is measured from the column with @{@, hence adding of @fieldIndent@ here.
+ addCommentBelow Nothing = []
+ addCommentBelow (Just (Comment _ _ c)) = [indent (fieldIndent + cFieldComment) "--" <> c]
+
+ extractField (H.FieldDecl lb names _type) =
+ (names, _type, findCommentOnLine lb allComments, findCommentBelowLine lb allComments)
+
+processConstructor _ init _ decl = [init <> trimLeft (H.prettyPrint decl)]