Skip to content

Commit dc62c76

Browse files
authored
Generalise break_only_where to allow single-line module headers (#349)
Previously, the `break_only_where` option of `module_header` allowed choosing between the following two styles: 1. `break_only_where: false`: ```haskell module Foo where module Bar ( main ) where ``` 2. `break_only_where: true`: ```haskell module Foo where module Bar ( main ) where ``` In case a module only has a single export (e.g., `main` or `tests`) or no exports at all (e.g., a module defining orphans), we still "break" the `where`: ```haskell module Bar ( main ) where module Orphans ( ) where ``` It should be possible to print these module headers as: ```haskell module Bar (main) where module Orphans () where ``` We generalise the `break_only_where` option to `break_where` with the following three possible values: - `exports`: only break when there is an explicit export list. - `single`: only break when the export list counts more than one export. - `inline`: only break when the export list is too long. This is determined by the `columns` setting. Not applicable when the export list contains comments as newlines will be required. - `always`: always break before the `where`.
1 parent 95e2261 commit dc62c76

File tree

5 files changed

+258
-63
lines changed

5 files changed

+258
-63
lines changed

data/stylish-haskell.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,15 @@ steps:
3131
# # See `separate_lists` for the `imports` step.
3232
# separate_lists: true
3333
#
34-
# # Whether to break the "where" if there are no exports.
35-
# break_only_where: false
34+
# # When to break the "where".
35+
# # Possible values:
36+
# # - exports: only break when there is an explicit export list.
37+
# # - single: only break when the export list counts more than one export.
38+
# # - inline: only break when the export list is too long. This is
39+
# # determined by the `columns` setting. Not applicable when the export
40+
# # list contains comments as newlines will be required.
41+
# # - always: always break before the "where".
42+
# break_where: exports
3643

3744
# Format record definitions. This is disabled by default.
3845
#

lib/Language/Haskell/Stylish/Config.hs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,23 @@ parseEnum strs _ (Just k) = case lookup k strs of
196196

197197
--------------------------------------------------------------------------------
198198
parseModuleHeader :: Config -> A.Object -> A.Parser Step
199-
parseModuleHeader _ o = fmap ModuleHeader.step $ ModuleHeader.Config
200-
<$> o A..:? "indent" A..!= ModuleHeader.indent def
201-
<*> o A..:? "sort" A..!= ModuleHeader.sort def
202-
<*> o A..:? "separate_lists" A..!= ModuleHeader.separateLists def
203-
<*> o A..:? "break_only_where" A..!= ModuleHeader.breakOnlyWhere def
199+
parseModuleHeader config o = fmap (ModuleHeader.step columns) $ ModuleHeader.Config
200+
<$> (o A..:? "indent" A..!= ModuleHeader.indent def)
201+
<*> (o A..:? "sort" A..!= ModuleHeader.sort def)
202+
<*> (o A..:? "separate_lists" A..!= ModuleHeader.separateLists def)
203+
<*> (o A..:? "break_where" >>= parseEnum breakWhere (ModuleHeader.breakWhere def))
204204
where
205205
def = ModuleHeader.defaultConfig
206206

207+
columns = configColumns config
208+
209+
breakWhere =
210+
[ ("exports", ModuleHeader.Exports)
211+
, ("single", ModuleHeader.Single)
212+
, ("inline", ModuleHeader.Inline)
213+
, ("always", ModuleHeader.Always)
214+
]
215+
207216
--------------------------------------------------------------------------------
208217
parseSimpleAlign :: Config -> A.Object -> A.Parser Step
209218
parseSimpleAlign c o = SimpleAlign.step

lib/Language/Haskell/Stylish/Printer.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module Language.Haskell.Stylish.Printer
4141
, removeLineComment
4242
, sep
4343
, groupAttachedComments
44+
, groupWithoutComments
4445
, space
4546
, spaces
4647
, suffix
@@ -73,7 +74,7 @@ import Control.Monad.Reader (MonadReader, ReaderT(..), asks
7374
import Control.Monad.State (MonadState, State)
7475
import Control.Monad.State (runState)
7576
import Control.Monad.State (get, gets, modify, put)
76-
import Data.Foldable (find)
77+
import Data.Foldable (find, toList)
7778
import Data.Functor ((<&>))
7879
import Data.List (delete, isPrefixOf)
7980
import Data.List.NonEmpty (NonEmpty(..))
@@ -427,6 +428,17 @@ groupAttachedComments = go
427428

428429
go _ = pure []
429430

431+
-- | A view on 'groupAttachedComments': return 'Just' when there is just a
432+
-- one big group without any comments.
433+
groupWithoutComments
434+
:: [([AnnotationComment], NonEmpty (Located a))]
435+
-> Maybe [Located a]
436+
groupWithoutComments grouped
437+
| all (null . fst) grouped
438+
= Just $ concatMap (toList . snd) grouped
439+
| otherwise
440+
= Nothing
441+
430442
modifyCurrentLine :: (String -> String) -> P ()
431443
modifyCurrentLine f = do
432444
s0 <- get

lib/Language/Haskell/Stylish/Step/ModuleHeader.hs

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE LambdaCase #-}
33
module Language.Haskell.Stylish.Step.ModuleHeader
44
( Config (..)
5+
, BreakWhere (..)
56
, defaultConfig
67
, step
78
) where
@@ -40,25 +41,31 @@ import qualified Language.Haskell.Stylish.Step.Imports as Imports
4041

4142

4243
data Config = Config
43-
{ indent :: Int
44-
, sort :: Bool
45-
, separateLists :: Bool
46-
, breakOnlyWhere :: Bool
44+
{ indent :: Int
45+
, sort :: Bool
46+
, separateLists :: Bool
47+
, breakWhere :: BreakWhere
4748
}
4849

50+
data BreakWhere
51+
= Exports
52+
| Single
53+
| Inline
54+
| Always
55+
4956
defaultConfig :: Config
5057
defaultConfig = Config
51-
{ indent = 4
52-
, sort = True
53-
, separateLists = True
54-
, breakOnlyWhere = False
58+
{ indent = 4
59+
, sort = True
60+
, separateLists = True
61+
, breakWhere = Exports
5562
}
5663

57-
step :: Config -> Step
58-
step = makeStep "Module header" . printModuleHeader
64+
step :: Maybe Int -> Config -> Step
65+
step maxCols = makeStep "Module header" . printModuleHeader maxCols
5966

60-
printModuleHeader :: Config -> Lines -> Module -> Lines
61-
printModuleHeader conf ls m =
67+
printModuleHeader :: Maybe Int -> Config -> Lines -> Module -> Lines
68+
printModuleHeader maxCols conf ls m =
6269
let
6370
header = moduleHeader m
6471
name = rawModuleName header
@@ -73,8 +80,7 @@ printModuleHeader conf ls m =
7380
& dropAfterLocated exports
7481
& dropBeforeLocated name
7582

76-
-- TODO: pass max columns?
77-
printedModuleHeader = runPrinter_ (PrinterConfig Nothing) relevantComments
83+
printedModuleHeader = runPrinter_ (PrinterConfig maxCols) relevantComments
7884
m (printHeader conf name exports haddocks)
7985

8086
getBlock loc =
@@ -144,13 +150,27 @@ printHeader conf mname mexps _ = do
144150

145151
case mexps of
146152
Nothing -> when (isJust mname) do
147-
if breakOnlyWhere conf
148-
then do
153+
case breakWhere conf of
154+
Always -> do
149155
newline
150156
spaces (indent conf)
151-
else space
157+
_ -> space
152158
putText "where"
153-
Just exps -> printExportList conf exps
159+
Just (L loc exps) -> do
160+
exportsWithComments <- fmap (second doSort) <$> groupAttachedComments exps
161+
case breakWhere conf of
162+
Single
163+
| Just exportsWithoutComments <- groupWithoutComments exportsWithComments
164+
, length exportsWithoutComments <= 1
165+
-> printSingleLineExportList conf (L loc exportsWithoutComments)
166+
Inline
167+
| Just exportsWithoutComments <- groupWithoutComments exportsWithComments
168+
-> wrapping
169+
(printSingleLineExportList conf (L loc exportsWithoutComments))
170+
(printMultiLineExportList conf (L loc exportsWithComments))
171+
_ -> printMultiLineExportList conf (L loc exportsWithComments)
172+
where
173+
doSort = if sort conf then NonEmpty.sortBy compareLIE else id
154174

155175
attachEolComment :: SrcSpan -> P ()
156176
attachEolComment = \case
@@ -164,12 +184,25 @@ attachEolCommentEnd = \case
164184
RealSrcSpan rspan ->
165185
removeLineComment (srcSpanEndLine rspan) >>= mapM_ \c -> space >> putComment c
166186

167-
printExportList :: Config -> Located [GHC.LIE GhcPs] -> P ()
168-
printExportList conf (L srcLoc exports) = do
187+
printSingleLineExportList :: Config -> Located [GHC.LIE GhcPs] -> P ()
188+
printSingleLineExportList conf (L srcLoc exports) = do
189+
space >> putText "("
190+
printInlineExports exports
191+
putText ")" >> space >> putText "where" >> attachEolCommentEnd srcLoc
192+
where
193+
printInlineExports :: [GHC.LIE GhcPs] -> P ()
194+
printInlineExports = \case
195+
[] -> pure ()
196+
[e] -> printExport conf e
197+
(e:es) -> printExport conf e >> comma >> space >> printInlineExports es
198+
199+
printMultiLineExportList
200+
:: Config
201+
-> Located [([AnnotationComment], NonEmpty (GHC.LIE GhcPs))]
202+
-> P ()
203+
printMultiLineExportList conf (L srcLoc exportsWithComments) = do
169204
newline
170-
doIndent >> putText "(" >> when (notNull exports) space
171-
172-
exportsWithComments <- fmap (second doSort) <$> groupAttachedComments exports
205+
doIndent >> putText "(" >> when (notNull exportsWithComments) space
173206

174207
printExports exportsWithComments
175208

@@ -191,11 +224,9 @@ printExportList conf (L srcLoc exports) = do
191224
doIndent = spaces (indent conf)
192225
doHang = pad (indent conf + 2)
193226

194-
doSort = if sort conf then NonEmpty.sortBy compareLIE else id
195-
196227
printExports :: [([AnnotationComment], NonEmpty (GHC.LIE GhcPs))] -> P ()
197228
printExports (([], firstInGroup :| groupRest) : rest) = do
198-
printExport firstInGroup
229+
printExport conf firstInGroup
199230
newline
200231
doIndent
201232
printExportsGroupTail groupRest
@@ -204,7 +235,7 @@ printExportList conf (L srcLoc exports) = do
204235
putComment firstComment >> newline >> doIndent
205236
forM_ comments \c -> doHang >> putComment c >> newline >> doIndent
206237
doHang
207-
printExport firstExport
238+
printExport conf firstExport
208239
newline
209240
doIndent
210241
printExportsGroupTail groupRest
@@ -216,14 +247,14 @@ printExportList conf (L srcLoc exports) = do
216247
printExportsTail = mapM_ \(comments, exported) -> do
217248
forM_ comments \c -> doHang >> putComment c >> newline >> doIndent
218249
forM_ exported \export -> do
219-
comma >> space >> printExport export
250+
comma >> space >> printExport conf export
220251
newline >> doIndent
221252

222253
printExportsGroupTail :: [GHC.LIE GhcPs] -> P ()
223254
printExportsGroupTail (x : xs) = printExportsTail [([], x :| xs)]
224255
printExportsGroupTail [] = pure ()
225256

226-
-- NOTE(jaspervdj): This code is almost the same as the import printing
227-
-- in 'Imports' and should be merged.
228-
printExport :: GHC.LIE GhcPs -> P ()
229-
printExport = Imports.printImport (separateLists conf) . unLoc
257+
-- NOTE(jaspervdj): This code is almost the same as the import printing in
258+
-- 'Imports' and should be merged.
259+
printExport :: Config -> GHC.LIE GhcPs -> P ()
260+
printExport conf = Imports.printImport (separateLists conf) . unLoc

0 commit comments

Comments
 (0)