packages feed

ormolu 0.0.4.0 → 0.0.5.0

raw patch · 51 files changed

+920/−389 lines, 51 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Ormolu: OrmoluCppEnabled :: FilePath -> OrmoluException
- Ormolu: [cfgTolerateCpp] :: Config -> !Bool
- Ormolu.Config: [cfgTolerateCpp] :: Config -> !Bool
- Ormolu.Config: instance GHC.Classes.Eq Ormolu.Config.Config
- Ormolu.Config: instance GHC.Show.Show Ormolu.Config.Config
- Ormolu.Exception: OrmoluCppEnabled :: FilePath -> OrmoluException
- Ormolu.Parser.CommentStream: isShebang :: String -> Bool
- Ormolu.Parser.Result: [prExtensions] :: ParseResult -> [Pragma]
- Ormolu.Printer.Combinators: getLastCommentSpan :: R (Maybe (Maybe HaddockStyle, RealSrcSpan))
- Ormolu.Printer.Combinators: setLastCommentSpan :: Maybe HaddockStyle -> RealSrcSpan -> R ()
- Ormolu.Printer.Comments: spitStackHeader :: R ()
- Ormolu.Printer.Internal: getLastCommentSpan :: R (Maybe (Maybe HaddockStyle, RealSrcSpan))
- Ormolu.Printer.Internal: setLastCommentSpan :: Maybe HaddockStyle -> RealSrcSpan -> R ()
+ Ormolu: RegionIndices :: !Maybe Int -> !Maybe Int -> RegionIndices
+ Ormolu: [cfgRegion] :: Config region -> !region
+ Ormolu: [regionEndLine] :: RegionIndices -> !Maybe Int
+ Ormolu: [regionStartLine] :: RegionIndices -> !Maybe Int
+ Ormolu: data RegionIndices
+ Ormolu.Config: RegionDeltas :: !Int -> !Int -> RegionDeltas
+ Ormolu.Config: RegionIndices :: !Maybe Int -> !Maybe Int -> RegionIndices
+ Ormolu.Config: [cfgRegion] :: Config region -> !region
+ Ormolu.Config: [regionEndLine] :: RegionIndices -> !Maybe Int
+ Ormolu.Config: [regionPrefixLength] :: RegionDeltas -> !Int
+ Ormolu.Config: [regionStartLine] :: RegionIndices -> !Maybe Int
+ Ormolu.Config: [regionSuffixLength] :: RegionDeltas -> !Int
+ Ormolu.Config: data RegionDeltas
+ Ormolu.Config: data RegionIndices
+ Ormolu.Config: instance GHC.Base.Functor Ormolu.Config.Config
+ Ormolu.Config: instance GHC.Classes.Eq Ormolu.Config.RegionDeltas
+ Ormolu.Config: instance GHC.Classes.Eq Ormolu.Config.RegionIndices
+ Ormolu.Config: instance GHC.Classes.Eq region => GHC.Classes.Eq (Ormolu.Config.Config region)
+ Ormolu.Config: instance GHC.Show.Show Ormolu.Config.RegionDeltas
+ Ormolu.Config: instance GHC.Show.Show Ormolu.Config.RegionIndices
+ Ormolu.Config: instance GHC.Show.Show region => GHC.Show.Show (Ormolu.Config.Config region)
+ Ormolu.Config: regionIndicesToDeltas :: Int -> RegionIndices -> RegionDeltas
+ Ormolu.Parser.Result: [prLiteralPrefix] :: ParseResult -> Text
+ Ormolu.Parser.Result: [prLiteralSuffix] :: ParseResult -> Text
+ Ormolu.Parser.Result: [prPragmas] :: ParseResult -> [([RealLocated Comment], Pragma)]
+ Ormolu.Parser.Result: [prStackHeader] :: ParseResult -> Maybe (RealLocated Comment)
+ Ormolu.Parser.Shebang: Shebang :: Located String -> Shebang
+ Ormolu.Parser.Shebang: extractShebangs :: [Located String] -> ([Shebang], [Located String])
+ Ormolu.Parser.Shebang: instance Data.Data.Data Ormolu.Parser.Shebang.Shebang
+ Ormolu.Parser.Shebang: instance GHC.Classes.Eq Ormolu.Parser.Shebang.Shebang
+ Ormolu.Parser.Shebang: isShebang :: String -> Bool
+ Ormolu.Parser.Shebang: newtype Shebang
+ Ormolu.Printer.Combinators: CommentSpan :: RealSrcSpan -> SpanMark
+ Ormolu.Printer.Combinators: HaddockSpan :: HaddockStyle -> RealSrcSpan -> SpanMark
+ Ormolu.Printer.Combinators: OtherSpan :: RealSrcSpan -> SpanMark
+ Ormolu.Printer.Combinators: data SpanMark
+ Ormolu.Printer.Combinators: getSpanMark :: R (Maybe SpanMark)
+ Ormolu.Printer.Combinators: setSpanMark :: SpanMark -> R ()
+ Ormolu.Printer.Combinators: spanMarkSpan :: SpanMark -> RealSrcSpan
+ Ormolu.Printer.Comments: spitCommentNow :: RealSrcSpan -> Comment -> R ()
+ Ormolu.Printer.Comments: spitCommentPending :: CommentPosition -> RealSrcSpan -> Comment -> R ()
+ Ormolu.Printer.Internal: CommentSpan :: RealSrcSpan -> SpanMark
+ Ormolu.Printer.Internal: HaddockSpan :: HaddockStyle -> RealSrcSpan -> SpanMark
+ Ormolu.Printer.Internal: OtherSpan :: RealSrcSpan -> SpanMark
+ Ormolu.Printer.Internal: data SpanMark
+ Ormolu.Printer.Internal: getSpanMark :: R (Maybe SpanMark)
+ Ormolu.Printer.Internal: setSpanMark :: SpanMark -> R ()
+ Ormolu.Printer.Internal: spanMarkSpan :: SpanMark -> RealSrcSpan
+ Ormolu.Processing.Common: OrmoluDisabled :: OrmoluState
+ Ormolu.Processing.Common: OrmoluEnabled :: OrmoluState
+ Ormolu.Processing.Common: data OrmoluState
+ Ormolu.Processing.Common: endDisabling :: IsString s => s
+ Ormolu.Processing.Common: instance GHC.Classes.Eq Ormolu.Processing.Common.OrmoluState
+ Ormolu.Processing.Common: instance GHC.Show.Show Ormolu.Processing.Common.OrmoluState
+ Ormolu.Processing.Common: startDisabling :: IsString s => s
+ Ormolu.Processing.Cpp: InConditional :: State
+ Ormolu.Processing.Cpp: InContinuation :: State
+ Ormolu.Processing.Cpp: Outside :: State
+ Ormolu.Processing.Cpp: data State
+ Ormolu.Processing.Cpp: instance GHC.Classes.Eq Ormolu.Processing.Cpp.State
+ Ormolu.Processing.Cpp: instance GHC.Show.Show Ormolu.Processing.Cpp.State
+ Ormolu.Processing.Cpp: processLine :: String -> State -> (String, State)
+ Ormolu.Processing.Cpp: unmaskLine :: Text -> Text
+ Ormolu.Processing.Postprocess: postprocess :: Text -> Text
+ Ormolu.Processing.Preprocess: preprocess :: FilePath -> String -> RegionDeltas -> (String, String, String, [Located String])
+ Ormolu.Utils: FirstPos :: RelativePos
+ Ormolu.Utils: LastPos :: RelativePos
+ Ormolu.Utils: MiddlePos :: RelativePos
+ Ormolu.Utils: SinglePos :: RelativePos
+ Ormolu.Utils: attachRelativePos :: [a] -> [(RelativePos, a)]
+ Ormolu.Utils: data RelativePos
+ Ormolu.Utils: incSpanLine :: Int -> SrcSpan -> SrcSpan
+ Ormolu.Utils: instance GHC.Classes.Eq Ormolu.Utils.RelativePos
+ Ormolu.Utils: instance GHC.Show.Show Ormolu.Utils.RelativePos
+ Ormolu.Utils: separatedByBlankNE :: (a -> SrcSpan) -> NonEmpty a -> NonEmpty a -> Bool
- Ormolu: Config :: ![DynOption] -> !Bool -> !Bool -> !Bool -> !Bool -> Config
+ Ormolu: Config :: ![DynOption] -> !Bool -> !Bool -> !Bool -> !region -> Config region
- Ormolu: [cfgCheckIdempotency] :: Config -> !Bool
+ Ormolu: [cfgCheckIdempotency] :: Config region -> !Bool
- Ormolu: [cfgDebug] :: Config -> !Bool
+ Ormolu: [cfgDebug] :: Config region -> !Bool
- Ormolu: [cfgDynOptions] :: Config -> ![DynOption]
+ Ormolu: [cfgDynOptions] :: Config region -> ![DynOption]
- Ormolu: [cfgUnsafe] :: Config -> !Bool
+ Ormolu: [cfgUnsafe] :: Config region -> !Bool
- Ormolu: data Config
+ Ormolu: data Config region
- Ormolu: defaultConfig :: Config
+ Ormolu: defaultConfig :: Config RegionIndices
- Ormolu: ormolu :: MonadIO m => Config -> FilePath -> String -> m Text
+ Ormolu: ormolu :: MonadIO m => Config RegionIndices -> FilePath -> String -> m Text
- Ormolu: ormoluFile :: MonadIO m => Config -> FilePath -> m Text
+ Ormolu: ormoluFile :: MonadIO m => Config RegionIndices -> FilePath -> m Text
- Ormolu: ormoluStdin :: MonadIO m => Config -> m Text
+ Ormolu: ormoluStdin :: MonadIO m => Config RegionIndices -> m Text
- Ormolu.Config: Config :: ![DynOption] -> !Bool -> !Bool -> !Bool -> !Bool -> Config
+ Ormolu.Config: Config :: ![DynOption] -> !Bool -> !Bool -> !Bool -> !region -> Config region
- Ormolu.Config: [cfgCheckIdempotency] :: Config -> !Bool
+ Ormolu.Config: [cfgCheckIdempotency] :: Config region -> !Bool
- Ormolu.Config: [cfgDebug] :: Config -> !Bool
+ Ormolu.Config: [cfgDebug] :: Config region -> !Bool
- Ormolu.Config: [cfgDynOptions] :: Config -> ![DynOption]
+ Ormolu.Config: [cfgDynOptions] :: Config region -> ![DynOption]
- Ormolu.Config: [cfgUnsafe] :: Config -> !Bool
+ Ormolu.Config: [cfgUnsafe] :: Config region -> !Bool
- Ormolu.Config: data Config
+ Ormolu.Config: data Config region
- Ormolu.Config: defaultConfig :: Config
+ Ormolu.Config: defaultConfig :: Config RegionIndices
- Ormolu.Parser: parseModule :: MonadIO m => Config -> FilePath -> String -> m ([Warn], Either (SrcSpan, String) ParseResult)
+ Ormolu.Parser: parseModule :: MonadIO m => Config RegionDeltas -> FilePath -> String -> m ([Warn], Either (SrcSpan, String) ParseResult)
- Ormolu.Parser.CommentStream: mkCommentStream :: [Located String] -> PState -> (CommentStream, [Pragma], [Located String])
+ Ormolu.Parser.CommentStream: mkCommentStream :: [Located String] -> PState -> (Maybe (RealLocated Comment), [Shebang], [([RealLocated Comment], Pragma)], CommentStream)
- Ormolu.Parser.Result: ParseResult :: ParsedSource -> Anns -> CommentStream -> [Pragma] -> [Located String] -> Bool -> Bool -> ParseResult
+ Ormolu.Parser.Result: ParseResult :: ParsedSource -> Anns -> Maybe (RealLocated Comment) -> [Shebang] -> [([RealLocated Comment], Pragma)] -> CommentStream -> Bool -> Bool -> Text -> Text -> ParseResult
- Ormolu.Parser.Result: [prShebangs] :: ParseResult -> [Located String]
+ Ormolu.Parser.Result: [prShebangs] :: ParseResult -> [Shebang]
- Ormolu.Printer.Meat.Module: p_hsModule :: [Located String] -> [Pragma] -> Bool -> ParsedSource -> R ()
+ Ormolu.Printer.Meat.Module: p_hsModule :: Maybe (RealLocated Comment) -> [Shebang] -> [([RealLocated Comment], Pragma)] -> Bool -> ParsedSource -> R ()
- Ormolu.Printer.Meat.Pragma: p_pragmas :: [Pragma] -> R ()
+ Ormolu.Printer.Meat.Pragma: p_pragmas :: [([RealLocated Comment], Pragma)] -> R ()
- Ormolu.Utils: separatedByBlank :: (a -> SrcSpan) -> NonEmpty a -> NonEmpty a -> Bool
+ Ormolu.Utils: separatedByBlank :: (a -> SrcSpan) -> a -> a -> Bool

Files

CHANGELOG.md view
@@ -1,3 +1,27 @@+## Ormolu 0.0.5.0++* Grouping of statements in `do`-blocks is now preserved. [Issue+  74](https://github.com/tweag/ormolu/issues/74).++* Grouping of TH splices is now preserved. [Issue+  507](https://github.com/tweag/ormolu/issues/507).++* Comments on pragmas are now preserved. [Issue+  216](https://github.com/tweag/ormolu/issues/216).++* Ormolu can now be enabled and disabled via special comments. [Issue+  435](https://github.com/tweag/ormolu/issues/435).++* Added experimental support for simple CPP. [Issue+  415](https://github.com/tweag/ormolu/issues/415).++* Added two new options `--start-line` and `--end-line` that allow us to+  select a region to format. [Issue+  516](https://github.com/tweag/ormolu/issues/516).++* Fixed rendering of module headers in the presence of preceding comments or+  Haddocks. [Issue 561](https://github.com/tweag/ormolu/issues/561).+ ## Ormolu 0.0.4.0  * When given several files to format, Ormolu does not stop on the first
README.md view
@@ -91,9 +91,33 @@ $ ormolu --mode inplace Module.hs ``` +## Magic comments++Ormolu understands two magic comments:++```haskell+{- ORMOLU_DISABLE -}+```++and++```haskell+{- ORMOLU_ENABLE -}+```++This allows us to disable formatting selectively for code between these+markers or disable it for the entire file. To achieve the latter, just put+`{- ORMOLU_DISABLE -}` at the very top. Note that the source code should+still be parseable even without the “excluded” part. Because of that the+magic comments cannot be placed arbitrary, but should rather enclose+independent top-level definitions.+ ## Current limitations -* Does not handle CPP (wontfix, see [the design document][design]).+* CPP support is experimental. CPP is virtually impossible to handle+  correctly, so we process them as a sort of unchangeable snippets. This+  works only in simple cases when CPP conditionals are self-contained. Use+  Ormolu with CPP at your own risk. * Input modules should be parsable by Haddock, which is a bit stricter   criterion than just being valid Haskell modules. * Various minor idempotence issues, most of them are related to comments.
app/Main.hs view
@@ -4,10 +4,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -module Main-  ( main,-  )-where+module Main (main) where  import Control.Exception (SomeException, displayException, try) import Control.Monad@@ -47,7 +44,7 @@   -- | Mode of operation   Mode ->   -- | Configuration-  Config ->+  Config RegionIndices ->   -- | File to format or stdin as 'Nothing'   Maybe FilePath ->   IO ()@@ -84,7 +81,7 @@   { -- | Mode of operation     optMode :: !Mode,     -- | Ormolu 'Config'-    optConfig :: !Config,+    optConfig :: !(Config RegionIndices),     -- | Haskell source files to format or stdin (when the list is empty)     optInputFiles :: ![FilePath]   }@@ -150,7 +147,7 @@         help "Haskell source files to format or stdin (default)"       ] -configParser :: Parser Config+configParser :: Parser (Config RegionIndices) configParser =   Config     <$> (fmap (fmap DynOption) . many . strOption . mconcat)@@ -170,15 +167,20 @@         help "Output information useful for debugging"       ]     <*> (switch . mconcat)-      [ long "tolerate-cpp",-        short 'p',-        help "Do not fail if CPP pragma is present"-      ]-    <*> (switch . mconcat)       [ long "check-idempotency",         short 'c',         help "Fail if formatting is not idempotent"       ]+    <*> ( RegionIndices+            <$> (optional . option auto . mconcat)+              [ long "start-line",+                help "Start line of the region to format (lines start from 1)"+              ]+            <*> (optional . option auto . mconcat)+              [ long "end-line",+                help "End line of the region to format (inclusive)"+              ]+        )  ---------------------------------------------------------------------------- -- Helpers
data/examples/declaration/annotation/annotation-out.hs view
@@ -18,4 +18,5 @@  data Foo = Foo Int {-# ANN type Foo ("HLint: ignore") #-}+ {- Comment -}
+ data/examples/declaration/splice/grouped-splices-out.hs view
@@ -0,0 +1,3 @@+$(deriveJSON fieldLabelMod ''A)+$(deriveJSON fieldLabelMod ''B)+$(deriveJSON fieldLabelMod ''C)
+ data/examples/declaration/splice/grouped-splices.hs view
@@ -0,0 +1,3 @@+$(deriveJSON fieldLabelMod ''A)+$(deriveJSON fieldLabelMod ''B)+$(deriveJSON fieldLabelMod ''C)
data/examples/declaration/splice/splice-decl.hs view
@@ -15,4 +15,3 @@ -- TemplateHaskell allows Q () at the top level do   pure []-
data/examples/declaration/value/function/do/blocks-out.hs view
@@ -15,4 +15,5 @@     <|> do       optional g_Semi       void allspacing+   return things
+ data/examples/declaration/value/function/do/comment-alignment-out.hs view
@@ -0,0 +1,9 @@+main = do+  case blah of+    Nothing -> return ()+    Just xs -> do+      forM_ xs foo+      bar+  -- and here it goes+  unless bobla $+    quux
+ data/examples/declaration/value/function/do/comment-alignment.hs view
@@ -0,0 +1,9 @@+main = do+  case blah of+    Nothing -> return ()+    Just xs -> do+      forM_ xs foo+      bar+  -- and here it goes+  unless bobla $+    quux
+ data/examples/declaration/value/function/do/comment-spacing-out.hs view
@@ -0,0 +1,7 @@+foo = do+  bar+  -- foo+  baz++  -- foo+  quux
+ data/examples/declaration/value/function/do/comment-spacing.hs view
@@ -0,0 +1,7 @@+foo = do+  bar+  -- foo+  baz++  -- foo+  quux
data/examples/declaration/value/function/do/recursive-do-rec-out.hs view
@@ -2,13 +2,18 @@  foo = do   rec a <- b + 5+       let d = c+       b <- a * 5+       something+       c <- a + b   print c   rec something $ do         x <- a         print x+         y <- c         print y
data/examples/declaration/value/function/let-single-line-out.hs view
@@ -5,7 +5,6 @@ foo x = let g :: Int -> Int; g = id in ()  let a = b; c = do { foo; bar }; d = baz in b- let a = case True of { True -> foo; False -> bar }; b = foo a in b  foo x = let ?g = id; ?f = g in x
data/examples/module-header/double-dot-with-names-out.hs view
@@ -1,11 +1,6 @@ {-# LANGUAGE PatternSynonyms #-} -module ExportSyntax-  ( A (.., NoA),-    Q (F, ..),-    G (T, .., U),-  )-where+module ExportSyntax (A (.., NoA), Q (F, ..), G (T, .., U)) where  data A = A | B 
data/examples/module-header/multiline2.hs view
@@ -1,2 +1,2 @@-module Foo-  (foo, bar, baz) where+module Foo (+  foo, bar, baz) where
data/examples/module-header/preceding-comment-with-haddock-out.hs view
@@ -3,9 +3,6 @@ -}  -- | This is the module's Haddock.-module Main-  ( main,-  )-where+module Main (main) where  main = return ()
data/examples/module-header/singleline-empty-out.hs view
@@ -1,5 +1,2 @@ -- | This demonstrates a BUG.-module Foo-  (-  )-where+module Foo () where
data/examples/module-header/stack-header-0-out.hs view
@@ -3,4 +3,5 @@ {-# LANGUAGE OverloadedStrings #-}  main = return ()+ -- stack runhaskell
data/examples/module-header/stack-header-1-out.hs view
@@ -4,4 +4,5 @@ {-# LANGUAGE OverloadedStrings #-}  main = return ()+ -- stack runhaskell
data/examples/module-header/warning-pragma-multiline-out.hs view
@@ -1,9 +1,6 @@ module Test   {-# DEPRECATED "This module is unstable" #-}-  ( foo,-    bar,-    baz,-  )+  (foo, bar, baz) where  import Blah
data/examples/module-header/warning-pragma-out.hs view
@@ -1,3 +1,5 @@ module Test-  {-# WARNING "This module is very internal" #-}+  {-# WARNING+    "This module is very internal"+    #-} where
+ data/examples/other/pragma-comments-out.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE OverloadedStrings #-}+-- TODO This extension is probably too dangerous, remove it.+{-# LANGUAGE RecordWildCards #-}+-- Avoid warning produced by TH.+{-# OPTIONS_GHC -fno-warn-overlapping-patterns #-}++-- | Header comment.+module Foo () where
+ data/examples/other/pragma-comments.hs view
@@ -0,0 +1,11 @@+-- | Header comment.++{-# LANGUAGE OverloadedStrings #-}++-- Avoid warning produced by TH.+{-# OPTIONS_GHC -fno-warn-overlapping-patterns #-}++-- TODO This extension is probably too dangerous, remove it.+{-# LANGUAGE RecordWildCards #-}++module Foo () where
data/examples/other/pragma-out.hs view
@@ -9,7 +9,4 @@ {-# OPTIONS_HADDOCK prune, show-extensions #-}  -- | Header comment.-module Foo-  (-  )-where+module Foo () where
data/examples/other/pragma-sorting-out.hs view
@@ -1,10 +1,9 @@ {-# LANGUAGE NondecreasingIndentation #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE NoMonoLocalBinds #-}- -- This gap is necessary for stylish Haskell not to re-arrange -- NoMonoLocalBinds before TypeFamilies+{-# LANGUAGE NoMonoLocalBinds #-}  module Foo   ( bar,
ormolu.cabal view
@@ -1,5 +1,5 @@ name:                 ormolu-version:              0.0.4.0+version:              0.0.5.0 cabal-version:        1.18 tested-with:          GHC==8.6.5, GHC==8.8.3, GHC==8.10.1 license:              BSD3@@ -82,6 +82,7 @@                     , Ormolu.Parser.CommentStream                     , Ormolu.Parser.Pragma                     , Ormolu.Parser.Result+                    , Ormolu.Parser.Shebang                     , Ormolu.Printer                     , Ormolu.Printer.Combinators                     , Ormolu.Printer.Comments@@ -108,6 +109,10 @@                     , Ormolu.Printer.Meat.Type                     , Ormolu.Printer.Operators                     , Ormolu.Printer.SpanStream+                    , Ormolu.Processing.Common+                    , Ormolu.Processing.Cpp+                    , Ormolu.Processing.Postprocess+                    , Ormolu.Processing.Preprocess                     , Ormolu.Utils   other-modules:      GHC                     , GHC.DynFlags
src/Ormolu.hs view
@@ -6,6 +6,7 @@     ormoluFile,     ormoluStdin,     Config (..),+    RegionIndices (..),     defaultConfig,     DynOption (..),     OrmoluException (..),@@ -42,13 +43,15 @@ ormolu ::   MonadIO m =>   -- | Ormolu configuration-  Config ->+  Config RegionIndices ->   -- | Location of source file   FilePath ->   -- | Input to format   String ->   m Text-ormolu cfg path str = do+ormolu cfgWithIndices path str = do+  let totalLines = length (lines str)+      cfg = regionIndicesToDeltas totalLines <$> cfgWithIndices   (warnings, result0) <-     parseModule' cfg OrmoluParsingFailed path str   when (cfgDebug cfg) $ do@@ -93,7 +96,7 @@ ormoluFile ::   MonadIO m =>   -- | Ormolu configuration-  Config ->+  Config RegionIndices ->   -- | Location of source file   FilePath ->   -- | Resulting rendition@@ -108,7 +111,7 @@ ormoluStdin ::   MonadIO m =>   -- | Ormolu configuration-  Config ->+  Config RegionIndices ->   -- | Resulting rendition   m Text ormoluStdin cfg =@@ -121,7 +124,7 @@ parseModule' ::   MonadIO m =>   -- | Ormolu configuration-  Config ->+  Config RegionDeltas ->   -- | How to obtain 'OrmoluException' to throw when parsing fails   (GHC.SrcSpan -> String -> OrmoluException) ->   -- | File name to use in errors
src/Ormolu/Config.hs view
@@ -1,7 +1,13 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE RecordWildCards #-}+ -- | Configuration options used by the tool. module Ormolu.Config   ( Config (..),+    RegionIndices (..),+    RegionDeltas (..),     defaultConfig,+    regionIndicesToDeltas,     DynOption (..),     dynOptionToLocatedStr,   )@@ -10,31 +16,66 @@ import qualified SrcLoc as GHC  -- | Ormolu configuration.-data Config = Config+data Config region = Config   { -- | Dynamic options to pass to GHC parser     cfgDynOptions :: ![DynOption],     -- | Do formatting faster but without automatic detection of defects     cfgUnsafe :: !Bool,     -- | Output information useful for debugging     cfgDebug :: !Bool,-    -- | Do not fail if CPP pragma is present (still doesn't handle CPP but-    -- useful for formatting of files that enable the extension without-    -- actually containing CPP macros)-    cfgTolerateCpp :: !Bool,-    -- | Checks if re-formatting the result is idempotent.-    cfgCheckIdempotency :: !Bool+    -- | Checks if re-formatting the result is idempotent+    cfgCheckIdempotency :: !Bool,+    -- | Region selection+    cfgRegion :: !region   }+  deriving (Eq, Show, Functor)++-- | Region selection as the combination of start and end line numbers.+data RegionIndices = RegionIndices+  { -- | Start line of the region to format+    regionStartLine :: !(Maybe Int),+    -- | End line of the region to format+    regionEndLine :: !(Maybe Int)+  }   deriving (Eq, Show) --- | Default 'Config'.-defaultConfig :: Config+-- | Region selection as the length of the literal prefix and the literal+-- suffix.+data RegionDeltas = RegionDeltas+  { -- | Prefix length in number of lines+    regionPrefixLength :: !Int,+    -- | Suffix length in number of lines+    regionSuffixLength :: !Int+  }+  deriving (Eq, Show)++-- | Default @'Config' 'RegionIndices'@.+defaultConfig :: Config RegionIndices defaultConfig =   Config     { cfgDynOptions = [],       cfgUnsafe = False,       cfgDebug = False,-      cfgTolerateCpp = False,-      cfgCheckIdempotency = False+      cfgCheckIdempotency = False,+      cfgRegion =+        RegionIndices+          { regionStartLine = Nothing,+            regionEndLine = Nothing+          }+    }++-- | Convert 'RegionIndices' into 'RegionDeltas'.+regionIndicesToDeltas ::+  -- | Total number of lines in the input+  Int ->+  -- | Region indices+  RegionIndices ->+  -- | Region deltas+  RegionDeltas+regionIndicesToDeltas total RegionIndices {..} =+  RegionDeltas+    { regionPrefixLength = maybe 0 (subtract 1) regionStartLine,+      regionSuffixLength = maybe 0 (total -) regionEndLine     }  -- | A wrapper for dynamic options.
src/Ormolu/Exception.hs view
@@ -19,9 +19,7 @@  -- | Ormolu exception representing all cases when Ormolu can fail. data OrmoluException-  = -- | Ormolu does not work with source files that use CPP-    OrmoluCppEnabled FilePath-  | -- | Parsing of original source code failed+  = -- | Parsing of original source code failed     OrmoluParsingFailed GHC.SrcSpan String   | -- | Parsing of formatted source code failed     OrmoluOutputParsingFailed GHC.SrcSpan String@@ -35,13 +33,8 @@  instance Exception OrmoluException where   displayException = \case-    OrmoluCppEnabled path ->-      unlines-        [ "CPP is not supported:",-          withIndent path-        ]     OrmoluParsingFailed s e ->-      showParsingErr "Parsing of source code failed:" s [e]+      showParsingErr "The GHC parser (in Haddock mode) failed:" s [e]     OrmoluOutputParsingFailed s e ->       showParsingErr "Parsing of formatted code failed:" s [e]         ++ "Please, consider reporting the bug.\n"@@ -81,8 +74,8 @@       hPutStrLn stderr (displayException e)       exitWith . ExitFailure $         case e of-          -- Error code 1 is for `error` or `notImplemented`-          OrmoluCppEnabled {} -> 2+          -- Error code 1 is for 'error' or 'notImplemented'+          -- 2 used to be for erroring out on CPP           OrmoluParsingFailed {} -> 3           OrmoluOutputParsingFailed {} -> 4           OrmoluASTDiffers {} -> 5
src/Ormolu/Parser.hs view
@@ -12,12 +12,11 @@ import Bag (bagToList) import qualified CmdLineParser as GHC import Control.Exception-import Control.Monad import Control.Monad.IO.Class-import Data.List ((\\), foldl', isPrefixOf, sortOn)+import qualified Data.List as L import qualified Data.List.NonEmpty as NE-import Data.Maybe (catMaybes) import Data.Ord (Down (Down))+import qualified Data.Text as T import DynFlags as GHC import ErrUtils (Severity (..), errMsgSeverity, errMsgSpan) import qualified FastString as GHC@@ -32,6 +31,8 @@ import Ormolu.Parser.Anns import Ormolu.Parser.CommentStream import Ormolu.Parser.Result+import Ormolu.Processing.Preprocess (preprocess)+import Ormolu.Utils (incSpanLine) import qualified Panic as GHC import qualified Parser as GHC import qualified StringBuffer as GHC@@ -40,7 +41,7 @@ parseModule ::   MonadIO m =>   -- | Ormolu configuration-  Config ->+  Config RegionDeltas ->   -- | File name (only for source location annotations)   FilePath ->   -- | Input for parser@@ -49,8 +50,9 @@     ( [GHC.Warn],       Either (SrcSpan, String) ParseResult     )-parseModule Config {..} path input' = liftIO $ do-  let (input, extraComments) = extractCommentsFromLines path input'+parseModule Config {..} path rawInput = liftIO $ do+  let (literalPrefix, input, literalSuffix, extraComments) =+        preprocess path rawInput cfgRegion   -- It's important that 'setDefaultExts' is done before   -- 'parsePragmasIntoDynFlags', because otherwise we might enable an   -- extension that was explicitly disabled in the file.@@ -60,7 +62,7 @@           (setDefaultExts baseDynFlags)       extraOpts = dynOptionToLocatedStr <$> cfgDynOptions   (warnings, dynFlags) <--    parsePragmasIntoDynFlags baseFlags extraOpts path input' >>= \case+    parsePragmasIntoDynFlags baseFlags extraOpts path rawInput >>= \case       Right res -> pure res       Left err ->         let loc =@@ -68,8 +70,6 @@                 (mkSrcLoc (GHC.mkFastString path) 1 1)                 (mkSrcLoc (GHC.mkFastString path) 1 1)          in throwIO (OrmoluParsingFailed loc err)-  when (GHC.xopt Cpp dynFlags && not cfgTolerateCpp) $-    throwIO (OrmoluCppEnabled path)   let useRecordDot =         "record-dot-preprocessor" == pgm_F dynFlags           || any@@ -77,14 +77,17 @@             (pluginModNames dynFlags)       pStateErrors = \pstate ->         let errs = bagToList $ GHC.getErrorMessages pstate dynFlags-         in case sortOn (Down . SeverityOrd . errMsgSeverity) errs of+            fixupErrSpan = incSpanLine (regionPrefixLength cfgRegion)+         in case L.sortOn (Down . SeverityOrd . errMsgSeverity) errs of               [] -> Nothing-              err : _ -> Just (errMsgSpan err, show err) -- Show instance returns a short error message+              err : _ ->+                -- Show instance returns a short error message+                Just (fixupErrSpan (errMsgSpan err), show err)       r = case runParser GHC.parseModule dynFlags path input of         GHC.PFailed pstate ->           case pStateErrors pstate of             Just err -> Left err-            Nothing -> error "invariant violation: PFailed does not have an error"+            Nothing -> error "PFailed does not have an error"         GHC.POk pstate pmod ->           case pStateErrors pstate of             -- Some parse errors (pattern/arrow syntax in expr context)@@ -93,20 +96,32 @@             -- later stages; but we fail in those cases.             Just err -> Left err             Nothing ->-              let (comments, exts, shebangs) = mkCommentStream extraComments pstate+              let (stackHeader, shebangs, pragmas, comments) =+                    mkCommentStream extraComments pstate                in Right                     ParseResult                       { prParsedSource = pmod,                         prAnns = mkAnns pstate,-                        prCommentStream = comments,-                        prExtensions = exts,+                        prStackHeader = stackHeader,                         prShebangs = shebangs,+                        prPragmas = pragmas,+                        prCommentStream = comments,                         prUseRecordDot = useRecordDot,                         prImportQualifiedPost =-                          GHC.xopt ImportQualifiedPost dynFlags+                          GHC.xopt ImportQualifiedPost dynFlags,+                        prLiteralPrefix = T.pack literalPrefix,+                        prLiteralSuffix = T.pack literalSuffix                       }   return (warnings, r) +-- | Enable all language extensions that we think should be enabled by+-- default for ease of use.+setDefaultExts :: DynFlags -> DynFlags+setDefaultExts flags = L.foldl' xopt_set flags autoExts+  where+    autoExts = allExts L.\\ manualExts+    allExts = [minBound .. maxBound]+ -- | Extensions that are not enabled automatically and should be activated -- by user. manualExts :: [Extension]@@ -133,9 +148,6 @@     -- decision of enabling this style is left to the user   ] -------------------------------------------------------------------------------- Helpers (taken from ghc-exactprint)- -- | Run a 'GHC.P' computation. runParser ::   -- | Computation to run@@ -154,67 +166,27 @@     buffer = GHC.stringToStringBuffer input     parseState = GHC.mkPState flags buffer location --- | Transform given lines possibly returning comments extracted from them.--- This handles LINE pragmas and shebangs.-extractCommentsFromLines ::-  -- | File name, just to use in the spans-  FilePath ->-  -- | List of lines from that file-  String ->-  -- | Adjusted lines together with comments extracted from them-  (String, [Located String])-extractCommentsFromLines path =-  unlines' . unzip . zipWith (extractCommentFromLine path) [1 ..] . lines-  where-    unlines' (a, b) = (unlines a, catMaybes b)---- | Transform a given line possibly returning a comment extracted from it.-extractCommentFromLine ::-  -- | File name, just to use in the spans-  FilePath ->-  -- | Line number of this line-  Int ->-  -- | The actual line-  String ->-  -- | Adjusted line and possibly a comment extracted from it-  (String, Maybe (Located String))-extractCommentFromLine path line s-  | "{-# LINE" `isPrefixOf` s =-    let (pragma, res) = getPragma s-        size = length pragma-        ss = mkSrcSpan (mkSrcLoc' 1) (mkSrcLoc' (size + 1))-     in (res, Just $ L ss pragma)-  | isShebang s =-    let ss = mkSrcSpan (mkSrcLoc' 1) (mkSrcLoc' (length s))-     in ("", Just $ L ss s)-  | otherwise = (s, Nothing)-  where-    mkSrcLoc' = mkSrcLoc (GHC.mkFastString path) line+-- | Wrap GHC's 'Severity' to add 'Ord' instance.+newtype SeverityOrd = SeverityOrd Severity --- | Take a line pragma and output its replacement (where line pragma is--- replaced with spaces) and the contents of the pragma itself.-getPragma ::-  -- | Pragma line to analyze-  String ->-  -- | Contents of the pragma and its replacement line-  (String, String)-getPragma [] = error "Ormolu.Parser.getPragma: input must not be empty"-getPragma s@(x : xs)-  | "#-}" `isPrefixOf` s = ("#-}", "   " ++ drop 3 s)-  | otherwise =-    let (prag, remline) = getPragma xs-     in (x : prag, ' ' : remline)+instance Eq SeverityOrd where+  s1 == s2 = compare s1 s2 == EQ --- | Enable all language extensions that we think should be enabled by--- default for ease of use.-setDefaultExts :: DynFlags -> DynFlags-setDefaultExts flags = foldl' GHC.xopt_set flags autoExts-  where-    autoExts = allExts \\ manualExts-    allExts = [minBound .. maxBound]+instance Ord SeverityOrd where+  compare (SeverityOrd s1) (SeverityOrd s2) =+    compare (f s1) (f s2)+    where+      f :: Severity -> Int+      f SevOutput = 1+      f SevFatal = 2+      f SevInteractive = 3+      f SevDump = 4+      f SevInfo = 5+      f SevWarning = 6+      f SevError = 7  ------------------------------------------------------------------------------- More helpers (taken from HLint)+-- Helpers taken from HLint  parsePragmasIntoDynFlags ::   -- | Pre-set 'DynFlags'@@ -243,25 +215,3 @@         reportErr         (GHC.handleSourceError reportErr act)     reportErr e = return $ Left (show e)--------------------------------------------------------------------------------- Even more helpers---- Wrap GHC's ErrUtils.Severity to add Ord instance-newtype SeverityOrd = SeverityOrd Severity--instance Eq SeverityOrd where-  s1 == s2 = compare s1 s2 == EQ--instance Ord SeverityOrd where-  compare (SeverityOrd s1) (SeverityOrd s2) =-    compare (f s1) (f s2)-    where-      f :: Severity -> Int-      f SevOutput = 1-      f SevFatal = 2-      f SevInteractive = 3-      f SevDump = 4-      f SevInfo = 5-      f SevWarning = 6-      f SevError = 7
src/Ormolu/Parser/CommentStream.hs view
@@ -7,7 +7,6 @@   ( CommentStream (..),     Comment (..),     mkCommentStream,-    isShebang,     isPrevHaddock,     isMultilineComment,     showCommentStream,@@ -16,14 +15,14 @@  import Data.Char (isSpace) import Data.Data (Data)-import Data.Either (partitionEithers)-import Data.List (dropWhileEnd, isPrefixOf, sortOn)+import qualified Data.List as L import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE import Data.Maybe (mapMaybe) import qualified GHC import qualified Lexer as GHC import Ormolu.Parser.Pragma+import Ormolu.Parser.Shebang import Ormolu.Utils (showOutputable) import SrcLoc @@ -45,37 +44,38 @@   [Located String] ->   -- | Parser state to use for comment extraction   GHC.PState ->-  -- | Comment stream, a set of extracted pragmas, and extracted shebangs-  (CommentStream, [Pragma], [Located String])+  -- | Stack header, shebangs, pragmas, and comment stream+  ( Maybe (RealLocated Comment),+    [Shebang],+    [([RealLocated Comment], Pragma)],+    CommentStream+  ) mkCommentStream extraComments pstate =-  ( CommentStream $-      mkComment <$> sortOn (realSrcSpanStart . getRealSrcSpan) comments,+  ( mstackHeader,+    shebangs,     pragmas,-    shebangs+    CommentStream comments   )   where-    (comments, pragmas) = partitionEithers (partitionComments <$> rawComments)-    rawComments =-      mapMaybe toRealSpan $+    (comments, pragmas) = extractPragmas rawComments1+    (rawComments1, mstackHeader) = extractStackHeader rawComments0+    rawComments0 =+      L.sortOn (realSrcSpanStart . getRealSrcSpan) . mapMaybe toRealSpan $         otherExtraComments           ++ mapMaybe (liftMaybe . fmap unAnnotationComment) (GHC.comment_q pstate)           ++ concatMap             (mapMaybe (liftMaybe . fmap unAnnotationComment) . snd)             (GHC.annotations_comments pstate)-    (shebangs, otherExtraComments) = span (isShebang . unLoc) extraComments---- | Return 'True' if given 'String' is a shebang.-isShebang :: String -> Bool-isShebang str = "#!" `isPrefixOf` str+    (shebangs, otherExtraComments) = extractShebangs extraComments  -- | Test whether a 'Comment' looks like a Haddock following a definition, -- i.e. something starting with @-- ^@. isPrevHaddock :: Comment -> Bool-isPrevHaddock (Comment (x :| _)) = "-- ^" `isPrefixOf` x+isPrevHaddock (Comment (x :| _)) = "-- ^" `L.isPrefixOf` x  -- | Is this comment multiline-style? isMultilineComment :: Comment -> Bool-isMultilineComment (Comment (x :| _)) = "{-" `isPrefixOf` x+isMultilineComment (Comment (x :| _)) = "{-" `L.isPrefixOf` x  -- | Pretty-print a 'CommentStream'. showCommentStream :: CommentStream -> String@@ -94,7 +94,7 @@ mkComment :: RealLocated String -> RealLocated Comment mkComment (L l s) =   L l . Comment . fmap dropTrailing $-    if "{-" `isPrefixOf` s+    if "{-" `L.isPrefixOf` s       then case NE.nonEmpty (lines s) of         Nothing -> s :| []         Just (x :| xs) ->@@ -106,7 +106,7 @@            in x :| (drop n <$> xs)       else s :| []   where-    dropTrailing = dropWhileEnd isSpace+    dropTrailing = L.dropWhileEnd isSpace     startIndent = srcSpanStartCol l - 1  -- | Get a 'String' from 'GHC.AnnotationComment'.@@ -129,12 +129,32 @@ toRealSpan (L (RealSrcSpan l) a) = Just (L l a) toRealSpan _ = Nothing --- | If a given comment is a pragma, return it in parsed form in 'Right'.--- Otherwise return the original comment unchanged.-partitionComments ::-  RealLocated String ->-  Either (RealLocated String) Pragma-partitionComments input =-  case parsePragma (unRealSrcSpan input) of-    Nothing -> Left input-    Just pragma -> Right pragma+-- | Detect and extract stack header if it is present.+extractStackHeader ::+  [RealLocated String] ->+  ([RealLocated String], Maybe (RealLocated Comment))+extractStackHeader = \case+  [] -> ([], Nothing)+  (x : xs) ->+    let comment = mkComment x+     in if isStackHeader (unRealSrcSpan comment)+          then (xs, Just comment)+          else (x : xs, Nothing)+  where+    isStackHeader (Comment (x :| _)) =+      "stack" `L.isPrefixOf` dropWhile isSpace (drop 2 x)++-- | Extract pragmas and their associated comments.+extractPragmas ::+  [RealLocated String] ->+  ([RealLocated Comment], [([RealLocated Comment], Pragma)])+extractPragmas = go id id+  where+    go csSoFar pragmasSoFar = \case+      [] -> (csSoFar [], pragmasSoFar [])+      (x : xs) ->+        case parsePragma (unRealSrcSpan x) of+          Nothing -> go (csSoFar . (mkComment x :)) pragmasSoFar xs+          Just pragma ->+            let combined = (csSoFar [], pragma)+             in go id (pragmasSoFar . (combined :)) xs
src/Ormolu/Parser/Result.hs view
@@ -7,10 +7,12 @@   ) where +import Data.Text (Text) import GHC import Ormolu.Parser.Anns import Ormolu.Parser.CommentStream import Ormolu.Parser.Pragma (Pragma)+import Ormolu.Parser.Shebang (Shebang)  -- | A collection of data that represents a parsed module in Ormolu. data ParseResult = ParseResult@@ -18,16 +20,22 @@     prParsedSource :: ParsedSource,     -- | Ormolu-specfic representation of annotations     prAnns :: Anns,+    -- | Stack header+    prStackHeader :: Maybe (RealLocated Comment),+    -- | Shebangs found in the input+    prShebangs :: [Shebang],+    -- | Pragmas and the associated comments+    prPragmas :: [([RealLocated Comment], Pragma)],     -- | Comment stream     prCommentStream :: CommentStream,-    -- | Extensions enabled in that module-    prExtensions :: [Pragma],-    -- | Shebangs found in the input-    prShebangs :: [Located String],     -- | Whether or not record dot syntax is enabled     prUseRecordDot :: Bool,     -- | Whether or not ImportQualifiedPost is enabled-    prImportQualifiedPost :: Bool+    prImportQualifiedPost :: Bool,+    -- | Literal prefix+    prLiteralPrefix :: Text,+    -- | Literal suffix+    prLiteralSuffix :: Text   }  -- | Pretty-print a 'ParseResult'.
+ src/Ormolu/Parser/Shebang.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE DeriveDataTypeable #-}++-- | A module for dealing with shebangs.+module Ormolu.Parser.Shebang+  ( Shebang (..),+    extractShebangs,+    isShebang,+  )+where++import Data.Data (Data)+import qualified Data.List as L+import SrcLoc++-- | A wrapper for a shebang.+newtype Shebang = Shebang (Located String)+  deriving (Eq, Data)++-- | Extract shebangs from the beginning of a comment stream.+extractShebangs :: [Located String] -> ([Shebang], [Located String])+extractShebangs comments = (Shebang <$> shebangs, rest)+  where+    (shebangs, rest) = span (isShebang . unLoc) comments++-- | Return 'True' if given 'String' is a shebang.+isShebang :: String -> Bool+isShebang str = "#!" `L.isPrefixOf` str
src/Ormolu/Printer.hs view
@@ -11,6 +11,7 @@ import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Module import Ormolu.Printer.SpanStream+import Ormolu.Processing.Postprocess (postprocess)  -- | Render a module. printModule ::@@ -19,14 +20,19 @@   -- | Resulting rendition   Text printModule ParseResult {..} =-  runR-    ( p_hsModule-        prShebangs-        prExtensions-        prImportQualifiedPost-        prParsedSource-    )-    (mkSpanStream prParsedSource)-    prCommentStream-    prAnns-    prUseRecordDot+  prLiteralPrefix <> region <> prLiteralSuffix+  where+    region =+      postprocess $+        runR+          ( p_hsModule+              prStackHeader+              prShebangs+              prPragmas+              prImportQualifiedPost+              prParsedSource+          )+          (mkSpanStream prParsedSource)+          prCommentStream+          prAnns+          prUseRecordDot
src/Ormolu/Printer/Combinators.hs view
@@ -52,10 +52,12 @@     -- ** Literals     comma, -    -- ** Comments+    -- ** Stateful markers+    SpanMark (..),+    spanMarkSpan,     HaddockStyle (..),-    setLastCommentSpan,-    getLastCommentSpan,+    setSpanMark,+    getSpanMark,   ) where 
src/Ormolu/Printer/Comments.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}  -- | Helpers for formatting of comments. This is low-level code, use@@ -6,17 +7,15 @@   ( spitPrecedingComments,     spitFollowingComments,     spitRemainingComments,-    spitStackHeader,+    spitCommentNow,+    spitCommentPending,   ) where  import Control.Monad-import Data.Char (isSpace) import Data.Coerce (coerce) import Data.Data (Data)-import Data.List (isPrefixOf) import qualified Data.List.NonEmpty as NE-import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Text as T import Ormolu.Parser.CommentStream import Ormolu.Printer.Internal@@ -35,10 +34,10 @@ spitPrecedingComments ref = do   gotSome <- handleCommentSeries (spitPrecedingComment ref)   when gotSome $ do+    lastMark <- getSpanMark     -- Insert a blank line between the preceding comments and the thing     -- after them if there was a blank line in the input.-    lastSpn <- fmap snd <$> getLastCommentSpan-    when (needsNewlineBefore (getRealSrcSpan ref) lastSpn) newline+    when (needsNewlineBefore (getRealSrcSpan ref) lastMark) newline  -- | Output all comments following an element at given location. spitFollowingComments ::@@ -52,17 +51,11 @@  -- | Output all remaining comments in the comment stream. spitRemainingComments :: R ()-spitRemainingComments = void $ handleCommentSeries spitRemainingComment---- | If there is a stack header in the comment stream, print it.-spitStackHeader :: R ()-spitStackHeader = do-  let isStackHeader (Comment (x :| _)) =-        "stack" `isPrefixOf` dropWhile isSpace (drop 2 x)-  mstackHeader <- popComment (isStackHeader . unRealSrcSpan)-  forM_ mstackHeader $ \(L spn x) -> do-    spitCommentNow spn x-    newline+spitRemainingComments = do+  -- Make sure we have a blank a line between the last definition and the+  -- trailing comments.+  newline+  void $ handleCommentSeries spitRemainingComment  ---------------------------------------------------------------------------- -- Single-comment functions@@ -73,14 +66,14 @@   -- | AST element to attach comments to   RealLocated a ->   -- | Location of last comment in the series-  Maybe RealSrcSpan ->+  Maybe SpanMark ->   -- | Are we done?   R Bool-spitPrecedingComment (L ref a) mlastSpn = do+spitPrecedingComment (L ref a) mlastMark = do   let p (L l _) = realSrcSpanEnd l <= realSrcSpanStart ref   withPoppedComment p $ \l comment -> do     dirtyLine <--      case mlastSpn of+      case mlastMark of         -- When the current line is dirty it means that something that can         -- have comments attached to it is already on the line. To avoid         -- problems with idempotence we cannot output the first comment@@ -89,7 +82,7 @@         -- an extra 'newline' in this case.         Nothing -> isLineDirty -- only for very first preceding comment         Just _ -> return False-    when (dirtyLine || needsNewlineBefore l mlastSpn) newline+    when (dirtyLine || needsNewlineBefore l mlastMark) newline     spitCommentNow l comment     if theSameLinePre l ref && not (isModule a)       then space@@ -102,34 +95,34 @@   -- | AST element to attach comments to   RealLocated a ->   -- | Location of last comment in the series-  Maybe RealSrcSpan ->+  Maybe SpanMark ->   -- | Are we done?   R Bool-spitFollowingComment (L ref a) mlastSpn = do+spitFollowingComment (L ref a) mlastMark = do   mnSpn <- nextEltSpan   -- Get first enclosing span that is not equal to reference span, i.e. it's   -- truly something enclosing the AST element.   meSpn <- getEnclosingSpan (/= ref)-  withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastSpn) $ \l comment ->+  withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastMark) $ \l comment ->     if theSameLinePost l ref && not (isModule a)       then         if isMultilineComment comment           then space >> spitCommentNow l comment           else spitCommentPending OnTheSameLine l comment       else do-        when (needsNewlineBefore l mlastSpn) $+        when (needsNewlineBefore l mlastMark) $           registerPendingCommentLine OnNextLine ""         spitCommentPending OnNextLine l comment  -- | Output a single remaining comment from the comment stream. spitRemainingComment ::   -- | Location of last comment in the series-  Maybe RealSrcSpan ->+  Maybe SpanMark ->   -- | Are we done?   R Bool-spitRemainingComment mlastSpn =+spitRemainingComment mlastMark =   withPoppedComment (const True) $ \l comment -> do-    when (needsNewlineBefore l mlastSpn) newline+    when (needsNewlineBefore l mlastMark) newline     spitCommentNow l comment     newline @@ -140,13 +133,13 @@ handleCommentSeries ::   -- | Given location of previous comment, output the next comment   -- returning 'True' if we're done-  (Maybe RealSrcSpan -> R Bool) ->+  (Maybe SpanMark -> R Bool) ->   -- | Whether we printed any comments   R Bool handleCommentSeries f = go False   where     go gotSome = do-      done <- getLastCommentSpan >>= f . fmap snd+      done <- getSpanMark >>= f       if done         then return gotSome         else go True@@ -172,13 +165,13 @@   -- | Current comment span   RealSrcSpan ->   -- | Last printed comment span-  Maybe RealSrcSpan ->+  Maybe SpanMark ->   Bool-needsNewlineBefore l mlastSpn =-  case mlastSpn of+needsNewlineBefore l mlastMark =+  case spanMarkSpan <$> mlastMark of     Nothing -> False-    Just lastSpn ->-      srcSpanStartLine l > srcSpanEndLine lastSpn + 1+    Just lastMark ->+      srcSpanStartLine l > srcSpanEndLine lastMark + 1  -- | Is the preceding comment and AST element are on the same line? theSameLinePre ::@@ -209,11 +202,11 @@   -- | Location of enclosing AST element   Maybe RealSrcSpan ->   -- | Location of last comment in the series-  Maybe RealSrcSpan ->+  Maybe SpanMark ->   -- | Comment to test   RealLocated Comment ->   Bool-commentFollowsElt ref mnSpn meSpn mlastSpn (L l comment) =+commentFollowsElt ref mnSpn meSpn mlastMark (L l comment) =   -- A comment follows a AST element if all 4 conditions are satisfied:   goesAfter     && logicallyFollows@@ -255,9 +248,12 @@                        >= abs (startColumn ref - startColumn l)                    )     continuation =-      case mlastSpn of-        Nothing -> False-        Just spn -> srcSpanEndLine spn + 1 == srcSpanStartLine l+      case mlastMark of+        Just (HaddockSpan _ spn) ->+          srcSpanEndLine spn + 1 == srcSpanStartLine l+        Just (CommentSpan spn) ->+          srcSpanEndLine spn + 1 == srcSpanStartLine l+        _ -> False     lastInEnclosing =       case meSpn of         -- When there is no enclosing element, return false@@ -281,7 +277,7 @@     . fmap (txt . T.pack)     . coerce     $ comment-  setLastCommentSpan Nothing spn+  setSpanMark (CommentSpan spn)  -- | Output a 'Comment' at the end of correct line or after it depending on -- 'CommentPosition'. Used for comments that may potentially follow on the@@ -297,4 +293,4 @@     . fmap (registerPendingCommentLine position . T.pack)     . coerce     $ comment-  setLastCommentSpan Nothing spn+  setSpanMark (CommentSpan spn)
src/Ormolu/Printer/Internal.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} @@ -38,9 +39,13 @@     popComment,     getEnclosingSpan,     withEnclosingSpan,++    -- * Stateful markers+    SpanMark (..),+    spanMarkSpan,     HaddockStyle (..),-    setLastCommentSpan,-    getLastCommentSpan,+    setSpanMark,+    getSpanMark,      -- * Annotations     getAnns,@@ -107,8 +112,8 @@     scDirtyLine :: !Bool,     -- | Whether to output a space before the next output     scRequestedDelimiter :: !RequestedDelimiter,-    -- | Span of last output comment-    scLastCommentSpan :: !(Maybe (Maybe HaddockStyle, RealSrcSpan))+    -- | An auxiliary marker for keeping track of last output element+    scSpanMark :: !(Maybe SpanMark)   }  -- | Make sure next output is delimited by one of the following.@@ -176,7 +181,7 @@           scPendingComments = [],           scDirtyLine = False,           scRequestedDelimiter = VeryBeginning,-          scLastCommentSpan = Nothing+          scSpanMark = Nothing         }  ----------------------------------------------------------------------------@@ -239,11 +244,11 @@           scColumn = scColumn sc + T.length indentedTxt,           scDirtyLine = scDirtyLine sc || dirty,           scRequestedDelimiter = RequestedNothing,-          scLastCommentSpan =+          scSpanMark =             -- If there are pending comments, do not reset last comment             -- location.             if printingComments || (not . null . scPendingComments) sc-              then scLastCommentSpan sc+              then scSpanMark sc               else Nothing         } @@ -463,6 +468,25 @@         { rcEnclosingSpans = spn : rcEnclosingSpans rc         } +----------------------------------------------------------------------------+-- Stateful markers++-- | An auxiliary marker for keeping track of last output element.+data SpanMark+  = -- | Haddock comment+    HaddockSpan HaddockStyle RealSrcSpan+  | -- | Non-haddock comment+    CommentSpan RealSrcSpan+  | -- | Non-comment span+    OtherSpan RealSrcSpan++-- | Project 'RealSrcSpan' from 'SpanMark'.+spanMarkSpan :: SpanMark -> RealSrcSpan+spanMarkSpan = \case+  HaddockSpan _ s -> s+  CommentSpan s -> s+  OtherSpan s -> s+ -- | Haddock string style. data HaddockStyle   = -- | @-- |@@@ -475,20 +499,18 @@     Named String  -- | Set span of last output comment.-setLastCommentSpan ::-  -- | 'HaddockStyle' or 'Nothing' if it's a non-Haddock comment-  Maybe HaddockStyle ->-  -- | Location of last printed comment-  RealSrcSpan ->+setSpanMark ::+  -- | Span mark to set+  SpanMark ->   R ()-setLastCommentSpan mhStyle spn = R . modify $ \sc ->+setSpanMark spnMark = R . modify $ \sc ->   sc-    { scLastCommentSpan = Just (mhStyle, spn)+    { scSpanMark = Just spnMark     }  -- | Get span of last output comment.-getLastCommentSpan :: R (Maybe (Maybe HaddockStyle, RealSrcSpan))-getLastCommentSpan = R (gets scLastCommentSpan)+getSpanMark :: R (Maybe SpanMark)+getSpanMark = R (gets scSpanMark)  ---------------------------------------------------------------------------- -- Annotations
src/Ormolu/Printer/Meat/Common.hs view
@@ -17,7 +17,6 @@  import Control.Monad import Data.List (isPrefixOf)-import Data.Maybe (isJust) import qualified Data.Text as T import GHC hiding (GhcPs, IE) import Name (nameStableString)@@ -155,7 +154,10 @@   LHsDocString ->   R () p_hsDocString hstyle needsNewline (L l str) = do-  goesAfterComment <- isJust <$> getLastCommentSpan+  goesAfterComment <- getSpanMark >>= \case+    Just (HaddockSpan _ _) -> return True+    Just (CommentSpan _) -> return True+    _ -> return False   -- Make sure the Haddock is separated by a newline from other comments.   when goesAfterComment newline   forM_ (zip (splitDocString str) (True : repeat False)) $ \(x, isFirst) -> do@@ -174,8 +176,8 @@       -- It's often the case that the comment itself doesn't have a span       -- attached to it and instead its location can be obtained from       -- nearest enclosing span.-      getEnclosingSpan (const True) >>= mapM_ (setLastCommentSpan (Just hstyle))-    RealSrcSpan spn -> setLastCommentSpan (Just hstyle) spn+      getEnclosingSpan (const True) >>= mapM_ (setSpanMark . HaddockSpan hstyle)+    RealSrcSpan spn -> setSpanMark (HaddockSpan hstyle spn)  -- | Print anchor of named doc section. p_hsDocName :: String -> R ()
src/Ormolu/Printer/Meat/Declaration.hs view
@@ -65,14 +65,15 @@     renderGroupWithPrev prev curr =       -- We can omit a blank line when the user didn't add one, but we must       -- ensure we always add blank lines around documented declarations-      if or-        [ grouping == Disregard,-          separatedByBlank getLoc prev curr,-          isDocumented prev,-          isDocumented curr-        ]-        then breakpoint : renderGroup curr-        else renderGroup curr+      case grouping of+        Disregard ->+          breakpoint : renderGroup curr+        Respect ->+          if separatedByBlankNE getLoc prev curr+            || isDocumented prev+            || isDocumented curr+            then breakpoint : renderGroup curr+            else renderGroup curr  -- | Is a declaration group documented? isDocumented :: NonEmpty (LHsDecl GhcPs) -> Bool@@ -85,7 +86,7 @@ -- | Group relevant declarations together. -- -- Add a declaration to a group iff it is relevant to either the first or--- the last declaration of the group.+-- the preceding declaration in the group. groupDecls :: [LHsDecl GhcPs] -> [NonEmpty (LHsDecl GhcPs)] groupDecls [] = [] groupDecls (l@(L _ DocNext) : xs) =@@ -94,18 +95,12 @@   case groupDecls xs of     [] -> [l :| []]     (x : xs') -> (l <| x) : xs'-groupDecls (lhdr : xs) =-  let -- Pick the first decl as the group header-      hdr = unLoc lhdr-      -- Zip rest of the decls with their previous decl-      zipped = zip (lhdr : xs) xs-      -- Pick decls from the tail if they are relevant to the group header-      -- or the previous decl.-      (grp, rest) = flip span zipped $ \(L _ prev, L _ cur) ->-        let relevantToHdr = groupedDecls hdr cur-            relevantToPrev = groupedDecls prev cur+groupDecls (header : xs) =+  let (grp, rest) = flip span (zip (header : xs) xs) $ \(previous, current) ->+        let relevantToHdr = groupedDecls header current+            relevantToPrev = groupedDecls previous current          in relevantToHdr || relevantToPrev-   in (lhdr :| map snd grp) : groupDecls (map snd rest)+   in (header :| map snd grp) : groupDecls (map snd rest)  p_hsDecl :: FamilyStyle -> HsDecl GhcPs -> R () p_hsDecl style = \case@@ -169,30 +164,37 @@  -- | Determine if these declarations should be grouped together. groupedDecls ::-  HsDecl GhcPs ->-  HsDecl GhcPs ->+  LHsDecl GhcPs ->+  LHsDecl GhcPs ->   Bool-groupedDecls (TypeSignature ns) (FunctionBody ns') = ns `intersects` ns'-groupedDecls (TypeSignature ns) (DefaultSignature ns') = ns `intersects` ns'-groupedDecls (DefaultSignature ns) (TypeSignature ns') = ns `intersects` ns'-groupedDecls (DefaultSignature ns) (FunctionBody ns') = ns `intersects` ns'-groupedDecls x (FunctionBody ns) | Just ns' <- isPragma x = ns `intersects` ns'-groupedDecls (FunctionBody ns) x | Just ns' <- isPragma x = ns `intersects` ns'-groupedDecls x (DataDeclaration n) | Just ns <- isPragma x = n `elem` ns-groupedDecls (DataDeclaration n) x-  | Just ns <- isPragma x =-    let f = occNameFS . rdrNameOcc in f n `elem` map f ns-groupedDecls x y | Just ns <- isPragma x, Just ns' <- isPragma y = ns `intersects` ns'-groupedDecls x (TypeSignature ns) | Just ns' <- isPragma x = ns `intersects` ns'-groupedDecls (TypeSignature ns) x | Just ns' <- isPragma x = ns `intersects` ns'-groupedDecls (PatternSignature ns) (Pattern n) = n `elem` ns-groupedDecls (KindSignature n) (DataDeclaration n') = n == n'-groupedDecls (KindSignature n) (ClassDeclaration n') = n == n'-groupedDecls (KindSignature n) (FamilyDeclaration n') = n == n'--- This looks only at Haddocks, normal comments are handled elsewhere-groupedDecls DocNext _ = True-groupedDecls _ DocPrev = True-groupedDecls _ _ = False+groupedDecls (L l_x x') (L l_y y') =+  case (x', y') of+    (TypeSignature ns, FunctionBody ns') -> ns `intersects` ns'+    (TypeSignature ns, DefaultSignature ns') -> ns `intersects` ns'+    (DefaultSignature ns, TypeSignature ns') -> ns `intersects` ns'+    (DefaultSignature ns, FunctionBody ns') -> ns `intersects` ns'+    (x, FunctionBody ns) | Just ns' <- isPragma x -> ns `intersects` ns'+    (FunctionBody ns, x) | Just ns' <- isPragma x -> ns `intersects` ns'+    (x, DataDeclaration n) | Just ns <- isPragma x -> n `elem` ns+    (DataDeclaration n, x)+      | Just ns <- isPragma x ->+        let f = occNameFS . rdrNameOcc in f n `elem` map f ns+    (x, y)+      | Just ns <- isPragma x,+        Just ns' <- isPragma y ->+        ns `intersects` ns'+    (x, TypeSignature ns) | Just ns' <- isPragma x -> ns `intersects` ns'+    (TypeSignature ns, x) | Just ns' <- isPragma x -> ns `intersects` ns'+    (PatternSignature ns, Pattern n) -> n `elem` ns+    (KindSignature n, DataDeclaration n') -> n == n'+    (KindSignature n, ClassDeclaration n') -> n == n'+    (KindSignature n, FamilyDeclaration n') -> n == n'+    -- Special case for TH splices, we look at locations+    (Splice, Splice) -> not (separatedByBlank id l_x l_y)+    -- This looks only at Haddocks, normal comments are handled elsewhere+    (DocNext, _) -> True+    (_, DocPrev) -> True+    _ -> False  intersects :: Ord a => [a] -> [a] -> Bool intersects a b = go (sort a) (sort b)@@ -216,6 +218,11 @@   AnnValuePragma n -> Just [n]   WarningPragma n -> Just n   _ -> Nothing++-- Declarations that do not refer to names++pattern Splice :: HsDecl GhcPs+pattern Splice <- SpliceD NoExtField (SpliceDecl NoExtField _ _)  -- Declarations referring to a single name 
src/Ormolu/Printer/Meat/Declaration/Value.hs view
@@ -345,7 +345,7 @@     txt "do"     newline     inci . located es $-      sitcc . sep newline (located' (sitcc . p_stmt' cmdPlacement p_hsCmd))+      sitcc . sep newline (sitcc . withSpacing (p_stmt' cmdPlacement p_hsCmd))   HsCmdWrap {} -> notImplemented "HsCmdWrap"   XCmd x -> noExtCon x @@ -354,6 +354,28 @@   HsCmdTop NoExtField cmd -> located cmd p_hsCmd   XCmdTop x -> noExtCon x +withSpacing :: Data a => (a -> R ()) -> Located a -> R ()+withSpacing f l = located l $ \x -> do+  case getLoc l of+    UnhelpfulSpan _ -> f x+    RealSrcSpan currentSpn -> do+      getSpanMark >>= \case+        -- Spacing before comments will be handled by the code+        -- that prints comments, so we just have to deal with+        -- blank lines between statements here.+        Just (OtherSpan lastSpn) ->+          if srcSpanStartLine currentSpn > srcSpanEndLine lastSpn + 1+            then newline+            else return ()+        _ -> return ()+      f x+      -- In some cases the (f x) expression may insert a new mark. We want+      -- to be careful not to override comment marks.+      getSpanMark >>= \case+        Just (HaddockSpan _ _) -> return ()+        Just (CommentSpan _) -> return ()+        _ -> setSpanMark (OtherSpan currentSpn)+ p_stmt :: Stmt GhcPs (LHsExpr GhcPs) -> R () p_stmt = p_stmt' exprPlacement p_hsExpr @@ -425,7 +447,7 @@   RecStmt {..} -> do     txt "rec"     space-    sitcc $ sepSemi (located' (p_stmt' placer render)) recS_stmts+    sitcc $ sepSemi (withSpacing (p_stmt' placer render)) recS_stmts   XStmtLR c -> noExtCon c  gatherStmt :: ExprLStmt GhcPs -> [[ExprLStmt GhcPs]]@@ -451,19 +473,22 @@           (Left <$> bagToList bag) ++ (Right <$> lsigs)         p_item (Left x) = located x p_valDecl         p_item (Right x) = located x p_sigDecl-        -- Assigns 'False' to the last element, 'True' to the rest.-        markInit :: [a] -> [(Bool, a)]-        markInit [] = []-        markInit [x] = [(False, x)]-        markInit (x : xs) = (True, x) : markInit xs     -- When in a single-line layout, there is a chance that the inner     -- elements will also contain semicolons and they will confuse the     -- parser. so we request braces around every element except the last.     br <- layoutToBraces <$> getLayout     sitcc $       sepSemi-        (\(m, i) -> (if m then br else id) $ p_item i)-        (markInit $ sortOn ssStart items)+        ( \(p, i) ->+            ( case p of+                SinglePos -> id+                FirstPos -> br+                MiddlePos -> br+                LastPos -> id+            )+              (p_item i)+        )+        (attachRelativePos $ sortOn ssStart items)   HsValBinds NoExtField _ -> notImplemented "HsValBinds"   HsIPBinds NoExtField (IPBinds NoExtField xs) ->     -- Second argument of IPBind is always Left before type-checking.@@ -647,7 +672,7 @@           ub <- layoutToBraces <$> getLayout           inci $             sepSemi-              (located' (ub . p_stmt' exprPlacement (p_hsExpr' S)))+              (ub . withSpacing (p_stmt' exprPlacement (p_hsExpr' S)))               (unLoc es)         compBody = brackets N $ located es $ \xs -> do           let p_parBody =
src/Ormolu/Printer/Meat/Declaration/Warning.hs view
@@ -27,9 +27,7 @@ p_moduleWarning :: WarningTxt -> R () p_moduleWarning wtxt = do   let (pragmaText, lits) = warningText wtxt-  switchLayout (getLoc <$> lits)-    $ inci-    $ pragma pragmaText (inci $ p_lits lits)+  inci $ pragma pragmaText $ inci $ p_lits lits  p_topLevelWarning :: [Located RdrName] -> WarningTxt -> R () p_topLevelWarning fnames wtxt = do
src/Ormolu/Printer/Meat/ImportExport.hs view
@@ -13,6 +13,7 @@ import GHC import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common+import Ormolu.Utils (RelativePos (..), attachRelativePos)  p_hsmodExports :: [LIE GhcPs] -> R () p_hsmodExports [] = do@@ -22,7 +23,10 @@ p_hsmodExports xs =   parens N . sitcc $ do     layout <- getLayout-    sep breakpoint (sitcc . located' (uncurry (p_lie layout))) (attachPositions xs)+    sep+      breakpoint+      (\(p, l) -> sitcc (located l (p_lie layout p)))+      (attachRelativePos xs)  p_hsmodImport :: Bool -> ImportDecl GhcPs -> R () p_hsmodImport useQualifiedPost ImportDecl {..} = do@@ -63,12 +67,15 @@         breakpoint         parens N . sitcc $ do           layout <- getLayout-          sep breakpoint (sitcc . located' (uncurry (p_lie layout))) (attachPositions xs)+          sep+            breakpoint+            (\(p, l) -> sitcc (located l (p_lie layout p)))+            (attachRelativePos xs)     newline p_hsmodImport _ (XImportDecl x) = noExtCon x -p_lie :: Layout -> (Int, Int) -> IE GhcPs -> R ()-p_lie encLayout (i, totalItems) = \case+p_lie :: Layout -> RelativePos -> IE GhcPs -> R ()+p_lie encLayout relativePos = \case   IEVar NoExtField l1 -> do     located l1 p_ieWrappedName     p_comma@@ -98,7 +105,11 @@     located l1 p_hsmodName     p_comma   IEGroup NoExtField n str -> do-    unless (i == 0) newline+    case relativePos of+      SinglePos -> return ()+      FirstPos -> return ()+      MiddlePos -> newline+      LastPos -> newline     p_hsDocString (Asterisk n) False (noLoc str)   IEDoc NoExtField str ->     p_hsDocString Pipe False (noLoc str)@@ -107,14 +118,10 @@   where     p_comma =       case encLayout of-        SingleLine -> unless (i + 1 == totalItems) comma+        SingleLine ->+          case relativePos of+            SinglePos -> return ()+            FirstPos -> comma+            MiddlePos -> comma+            LastPos -> return ()         MultiLine -> comma---- | Attach positions to 'Located' things in a list.-attachPositions ::-  [Located a] ->-  [Located ((Int, Int), a)]-attachPositions xs =-  let f i (L l x) = L l ((i, n), x)-      n = length xs-   in zipWith f [0 ..] xs
src/Ormolu/Printer/Meat/Module.hs view
@@ -12,7 +12,9 @@ import qualified Data.Text as T import GHC import Ormolu.Imports+import Ormolu.Parser.CommentStream import Ormolu.Parser.Pragma+import Ormolu.Parser.Shebang import Ormolu.Printer.Combinators import Ormolu.Printer.Comments import Ormolu.Printer.Meat.Common@@ -23,28 +25,28 @@  -- | Render a module. p_hsModule ::+  -- | Stack header+  Maybe (RealLocated Comment) ->   -- | Shebangs-  [Located String] ->-  -- | Pragmas-  [Pragma] ->+  [Shebang] ->+  -- | Pragmas and the associated comments+  [([RealLocated Comment], Pragma)] ->   -- | Whether to use postfix qualified imports   Bool ->   -- | AST to print   ParsedSource ->   R ()-p_hsModule shebangs pragmas qualifiedPost (L moduleSpan HsModule {..}) = do-  -- If span of exports in multiline, the whole thing is multiline. This is-  -- especially important because span of module itself always seems to have-  -- length zero, so it's not reliable for layout selection.-  let exportSpans = maybe [] (\(L s _) -> [s]) hsmodExports-      deprecSpan = maybe [] (\(L s _) -> [s]) hsmodDeprecMessage-      spans' = exportSpans ++ deprecSpan ++ [moduleSpan]-  switchLayout spans' $ do-    forM_ shebangs $ \x ->+p_hsModule mstackHeader shebangs pragmas qualifiedPost (L _ HsModule {..}) = do+  let deprecSpan = maybe [] (\(L s _) -> [s]) hsmodDeprecMessage+      exportSpans = maybe [] (\(L s _) -> [s]) hsmodExports+  switchLayout (deprecSpan <> exportSpans) $ do+    forM_ shebangs $ \(Shebang x) ->       located x $ \shebang -> do         txt (T.pack shebang)         newline-    spitStackHeader+    forM_ mstackHeader $ \(L spn comment) -> do+      spitCommentNow spn comment+      newline     newline     p_pragmas pragmas     newline@@ -54,15 +56,16 @@         located hsmodName' $ \name -> do           forM_ hsmodHaddockModHeader (p_hsDocString Pipe True)           p_hsmodName name+        breakpoint         forM_ hsmodDeprecMessage $ \w -> do-          breakpoint           located' p_moduleWarning w+          breakpoint         case hsmodExports of           Nothing -> return ()-          Just hsmodExports' -> do+          Just l -> do+            located l $ \exports -> do+              inci (p_hsmodExports exports)             breakpoint-            inci (p_hsmodExports (unLoc hsmodExports'))-        breakpoint         txt "where"         newline     newline
src/Ormolu/Printer/Meat/Pragma.hs view
@@ -8,12 +8,16 @@   ) where +import Control.Monad import Data.Char (isUpper)+import qualified Data.List as L import Data.Maybe (listToMaybe)-import qualified Data.Set as S import qualified Data.Text as T+import Ormolu.Parser.CommentStream import Ormolu.Parser.Pragma (Pragma (..)) import Ormolu.Printer.Combinators+import Ormolu.Printer.Comments+import SrcLoc  -- | Pragma classification. data PragmaTy@@ -42,25 +46,31 @@     Final   deriving (Eq, Ord) -p_pragmas :: [Pragma] -> R ()-p_pragmas ps =-  let prepare = concatMap $ \case-        PragmaLanguage xs ->-          let f x = (Language (classifyLanguagePragma x), x)+-- | Print a collection of 'Pragma's with their associated comments.+p_pragmas :: [([RealLocated Comment], Pragma)] -> R ()+p_pragmas ps = do+  let prepare = L.sortOn snd . L.nub . concatMap analyze+      analyze = \case+        (cs, PragmaLanguage xs) ->+          let f x = (cs, (Language (classifyLanguagePragma x), x))            in f <$> xs-        PragmaOptionsGHC x -> [(OptionsGHC, x)]-        PragmaOptionsHaddock x -> [(OptionsHaddock, x)]-   in mapM_ (uncurry p_pragma) (S.toAscList . S.fromList . prepare $ ps)+        (cs, PragmaOptionsGHC x) -> [(cs, (OptionsGHC, x))]+        (cs, PragmaOptionsHaddock x) -> [(cs, (OptionsHaddock, x))]+  forM_ (prepare ps) $ \(cs, (pragmaTy, x)) ->+    p_pragma cs pragmaTy x -p_pragma :: PragmaTy -> String -> R ()-p_pragma ty c = do+p_pragma :: [RealLocated Comment] -> PragmaTy -> String -> R ()+p_pragma comments ty x = do+  forM_ comments $ \(L l comment) -> do+    spitCommentNow l comment+    newline   txt "{-# "   txt $ case ty of     Language _ -> "LANGUAGE"     OptionsGHC -> "OPTIONS_GHC"     OptionsHaddock -> "OPTIONS_HADDOCK"   space-  txt (T.pack c)+  txt (T.pack x)   txt " #-}"   newline 
+ src/Ormolu/Processing/Common.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Common definitions for pre- and post- processing.+module Ormolu.Processing.Common+  ( OrmoluState (..),+    startDisabling,+    endDisabling,+  )+where++import Data.String (IsString (..))++-- | Ormolu state.+data OrmoluState+  = -- | Enabled+    OrmoluEnabled+  | -- | Disabled+    OrmoluDisabled+  deriving (Eq, Show)++-- | Marker for the beginning of the region where Ormolu should be disabled.+startDisabling :: IsString s => s+startDisabling = "{- ORMOLU_DISABLING_START"++-- | Marker for the end of the region where Ormolu should be disabled.+endDisabling :: IsString s => s+endDisabling = "ORMOLU_DISABLE_END -}"
+ src/Ormolu/Processing/Cpp.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Support for CPP.+module Ormolu.Processing.Cpp+  ( State (..),+    processLine,+    unmaskLine,+  )+where++import Control.Monad+import Data.Char (isSpace)+import qualified Data.List as L+import Data.Maybe (isJust)+import Data.String+import Data.Text (Text)+import qualified Data.Text as T++-- | State of the CPP processor.+data State+  = -- | Outside of CPP directives+    Outside+  | -- | In a conditional expression+    InConditional+  | -- | In a continuation (after @\\@)+    InContinuation+  deriving (Eq, Show)++-- | Automatically mask the line when needed and update the 'State'.+processLine :: String -> State -> (String, State)+processLine line state+  | for "define " = (masked, state')+  | for "include " = (masked, state')+  | for "undef " = (masked, state')+  | for "ifdef " = (masked, InConditional)+  | for "ifndef " = (masked, InConditional)+  | for "if " = (masked, InConditional)+  | for "else" = (masked, InConditional)+  | for "elif" = (masked, InConditional)+  | for "endif" = (masked, state')+  | otherwise =+    case state of+      Outside -> (line, Outside)+      InConditional -> (masked, InConditional)+      InContinuation -> (masked, state')+  where+    for directive = isJust $ do+      s <- dropWhile isSpace <$> L.stripPrefix "#" line+      void (L.stripPrefix directive s)+    masked = maskLine line+    state' =+      if "\\" `L.isSuffixOf` line+        then InContinuation+        else Outside++-- | Mask the given line.+maskLine :: String -> String+maskLine x = maskPrefix ++ x++-- | If the given line is masked, unmask it. Otherwise return the line+-- unchanged.+unmaskLine :: Text -> Text+unmaskLine x =+  case T.stripPrefix maskPrefix (T.stripStart x) of+    Nothing -> x+    Just x' -> x'++-- | Mask prefix for CPP.+maskPrefix :: IsString s => s+maskPrefix = "-- ORMOLU_CPP_MASK"
+ src/Ormolu/Processing/Postprocess.hs view
@@ -0,0 +1,21 @@+-- | Postprocessing for the results of printing.+module Ormolu.Processing.Postprocess+  ( postprocess,+  )+where++import Data.Text (Text)+import qualified Data.Text as T+import Ormolu.Processing.Common+import qualified Ormolu.Processing.Cpp as Cpp++-- | Postprocess output of the formatter.+postprocess :: Text -> Text+postprocess =+  T.unlines+    . fmap Cpp.unmaskLine+    . filter (not . magicComment)+    . T.lines+  where+    magicComment x =+      x == startDisabling || x == endDisabling
+ src/Ormolu/Processing/Preprocess.hs view
@@ -0,0 +1,149 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}++-- | Preprocessing for input source code.+module Ormolu.Processing.Preprocess+  ( preprocess,+  )+where++import Control.Monad+import Data.Char (isSpace)+import qualified Data.List as L+import Data.Maybe (isJust)+import Data.Maybe (maybeToList)+import FastString+import Ormolu.Config (RegionDeltas (..))+import Ormolu.Parser.Shebang (isShebang)+import Ormolu.Processing.Common+import qualified Ormolu.Processing.Cpp as Cpp+import SrcLoc++-- | Transform given input possibly returning comments extracted from it.+-- This handles LINE pragmas, CPP, shebangs, and the magic comments for+-- enabling\/disabling of Ormolu.+preprocess ::+  -- | File name, just to use in the spans+  FilePath ->+  -- | Input to process+  String ->+  -- | Region deltas+  RegionDeltas ->+  -- | Literal prefix, pre-processed input, literal suffix, extra comments+  (String, String, String, [Located String])+preprocess path input RegionDeltas {..} =+  go 1 OrmoluEnabled Cpp.Outside id id regionLines+  where+    (prefixLines, otherLines) = splitAt regionPrefixLength (lines input)+    (regionLines, suffixLines) =+      let regionLength = length otherLines - regionSuffixLength+       in splitAt regionLength otherLines+    go !n ormoluState cppState inputSoFar csSoFar = \case+      [] ->+        let input' = unlines (inputSoFar [])+         in ( unlines prefixLines,+              case ormoluState of+                OrmoluEnabled -> input'+                OrmoluDisabled -> input' ++ endDisabling,+              unlines suffixLines,+              csSoFar []+            )+      (x : xs) ->+        let (x', ormoluState', cppState', cs) =+              processLine path n ormoluState cppState x+         in go+              (n + 1)+              ormoluState'+              cppState'+              (inputSoFar . (x' :))+              (csSoFar . (maybeToList cs ++))+              xs++-- | Transform a given line possibly returning a comment extracted from it.+processLine ::+  -- | File name, just to use in the spans+  FilePath ->+  -- | Line number of this line+  Int ->+  -- | Whether Ormolu is currently enabled+  OrmoluState ->+  -- | CPP state+  Cpp.State ->+  -- | The actual line+  String ->+  -- | Adjusted line and possibly a comment extracted from it+  (String, OrmoluState, Cpp.State, Maybe (Located String))+processLine path n ormoluState Cpp.Outside line+  | "{-# LINE" `L.isPrefixOf` line =+    let (pragma, res) = getPragma line+        size = length pragma+        ss = mkSrcSpan (mkSrcLoc' 1) (mkSrcLoc' (size + 1))+     in (res, ormoluState, Cpp.Outside, Just (L ss pragma))+  | isOrmoluEnable line =+    case ormoluState of+      OrmoluEnabled ->+        (enableMarker, OrmoluEnabled, Cpp.Outside, Nothing)+      OrmoluDisabled ->+        (endDisabling ++ enableMarker, OrmoluEnabled, Cpp.Outside, Nothing)+  | isOrmoluDisable line =+    case ormoluState of+      OrmoluEnabled ->+        (disableMarker ++ startDisabling, OrmoluDisabled, Cpp.Outside, Nothing)+      OrmoluDisabled ->+        (disableMarker, OrmoluDisabled, Cpp.Outside, Nothing)+  | isShebang line =+    let ss = mkSrcSpan (mkSrcLoc' 1) (mkSrcLoc' (length line))+     in ("", ormoluState, Cpp.Outside, Just (L ss line))+  | otherwise =+    let (line', cppState') = Cpp.processLine line Cpp.Outside+     in (line', ormoluState, cppState', Nothing)+  where+    mkSrcLoc' = mkSrcLoc (mkFastString path) n+processLine _ _ ormoluState cppState line =+  let (line', cppState') = Cpp.processLine line cppState+   in (line', ormoluState, cppState', Nothing)++-- | Take a line pragma and output its replacement (where line pragma is+-- replaced with spaces) and the contents of the pragma itself.+getPragma ::+  -- | Pragma line to analyze+  String ->+  -- | Contents of the pragma and its replacement line+  (String, String)+getPragma [] = error "Ormolu.Preprocess.getPragma: input must not be empty"+getPragma s@(x : xs)+  | "#-}" `L.isPrefixOf` s = ("#-}", "   " ++ drop 3 s)+  | otherwise =+    let (prag, remline) = getPragma xs+     in (x : prag, ' ' : remline)++-- | Canonical enable marker.+enableMarker :: String+enableMarker = "{- ORMOLU_ENABLE -}"++-- | Canonical disable marker.+disableMarker :: String+disableMarker = "{- ORMOLU_DISABLE -}"++-- | Return 'True' if the given string is an enabling marker.+isOrmoluEnable :: String -> Bool+isOrmoluEnable = magicComment "ORMOLU_ENABLE"++-- | Return 'True' if the given string is a disabling marker.+isOrmoluDisable :: String -> Bool+isOrmoluDisable = magicComment "ORMOLU_DISABLE"++-- | Construct a function for whitespace-insensitive matching of string.+magicComment ::+  -- | What to expect+  String ->+  -- | String to test+  String ->+  -- | Whether or not the two strings watch+  Bool+magicComment expected s0 = isJust $ do+  s1 <- dropWhile isSpace <$> L.stripPrefix "{-" s0+  s2 <- dropWhile isSpace <$> L.stripPrefix expected s1+  s3 <- L.stripPrefix "-}" s2+  guard (all isSpace s3)
src/Ormolu/Utils.hs view
@@ -3,14 +3,18 @@  -- | Random utilities used by the code. module Ormolu.Utils-  ( combineSrcSpans',+  ( RelativePos (..),+    attachRelativePos,+    combineSrcSpans',     isModule,     notImplemented,     showOutputable,     splitDocString,     typeArgToType,     unSrcSpan,+    incSpanLine,     separatedByBlank,+    separatedByBlankNE,   ) where @@ -25,6 +29,25 @@ import GHC.DynFlags (baseDynFlags) import qualified Outputable as GHC +-- | Relative positions in a list.+data RelativePos+  = SinglePos+  | FirstPos+  | MiddlePos+  | LastPos+  deriving (Eq, Show)++-- | Attach 'RelativePos'es to elements of a given list.+attachRelativePos :: [a] -> [(RelativePos, a)]+attachRelativePos = \case+  [] -> []+  [x] -> [(SinglePos, x)]+  (x : xs) -> (FirstPos, x) : markLast xs+  where+    markLast [] = []+    markLast [x] = [(LastPos, x)]+    markLast (x : xs) = (MiddlePos, x) : markLast xs+ -- | Combine all source spans from the given list. combineSrcSpans' :: NonEmpty SrcSpan -> SrcSpan combineSrcSpans' (x :| xs) = foldr combineSrcSpans x xs@@ -78,20 +101,41 @@                 then dropSpace <$> xs                 else xs +-- | Get 'LHsType' out of 'LHsTypeArg'. typeArgToType :: LHsTypeArg p -> LHsType p typeArgToType = \case   HsValArg tm -> tm   HsTypeArg _ ty -> ty   HsArgPar _ -> notImplemented "HsArgPar" +-- | Get 'RealSrcSpan' out of 'SrcSpan' if the span is “helpful”. unSrcSpan :: SrcSpan -> Maybe RealSrcSpan-unSrcSpan (RealSrcSpan r) = Just r-unSrcSpan (UnhelpfulSpan _) = Nothing+unSrcSpan = \case+  RealSrcSpan r -> Just r+  UnhelpfulSpan _ -> Nothing --- | Do two declaration groups have a blank between them?-separatedByBlank :: (a -> SrcSpan) -> NonEmpty a -> NonEmpty a -> Bool+-- | Increment line number in a 'SrcSpan'.+incSpanLine :: Int -> SrcSpan -> SrcSpan+incSpanLine i = \case+  RealSrcSpan s ->+    let start = realSrcSpanStart s+        end = realSrcSpanEnd s+        incLine x =+          let file = srcLocFile x+              line = srcLocLine x+              col = srcLocCol x+           in mkRealSrcLoc file (line + i) col+     in RealSrcSpan (mkRealSrcSpan (incLine start) (incLine end))+  UnhelpfulSpan x -> UnhelpfulSpan x++-- | Do two declarations have a blank between them?+separatedByBlank :: (a -> SrcSpan) -> a -> a -> Bool separatedByBlank loc a b =   fromMaybe False $ do-    endA <- srcSpanEndLine <$> unSrcSpan (loc $ NE.last a)-    startB <- srcSpanStartLine <$> unSrcSpan (loc $ NE.head b)+    endA <- srcSpanEndLine <$> unSrcSpan (loc a)+    startB <- srcSpanStartLine <$> unSrcSpan (loc b)     pure (startB - endA >= 2)++-- | Do two declaration groups have a blank between them?+separatedByBlankNE :: (a -> SrcSpan) -> NonEmpty a -> NonEmpty a -> Bool+separatedByBlankNE loc a b = separatedByBlank loc (NE.last a) (NE.head b)
tests/Ormolu/PrinterSpec.hs view
@@ -1,9 +1,6 @@ {-# LANGUAGE TemplateHaskell #-} -module Ormolu.PrinterSpec-  ( spec,-  )-where+module Ormolu.PrinterSpec (spec) where  import Control.Exception import Control.Monad