packages feed

stylish-haskell 0.11.0.3 → 0.12.0.0

raw patch · 33 files changed

+4576/−1214 lines, 33 filesdep +ghc-lib-parserdep −haskell-src-extsdep ~Cabaldep ~basedep ~strictPVP ok

version bump matches the API change (PVP)

Dependencies added: ghc-lib-parser

Dependencies removed: haskell-src-exts

Dependency ranges changed: Cabal, base, strict

API changes (from Hackage documentation)

- Language.Haskell.Stylish.Step.Data: changeDecl :: [Comment] -> Config -> Decl LineBlock -> Maybe ChangeLine
- Language.Haskell.Stylish.Step.Data: commentsWithin :: LineBlock -> [Comment] -> [Comment]
- Language.Haskell.Stylish.Step.Data: datas :: Module l -> [Decl l]
- Language.Haskell.Stylish.Step.Data: findCommentBelowLine :: LineBlock -> [Comment] -> Maybe Comment
- Language.Haskell.Stylish.Step.Data: findCommentOnLine :: LineBlock -> [Comment] -> Maybe Comment
- Language.Haskell.Stylish.Step.Data: processConstructor :: [Comment] -> String -> Config -> QualConDecl LineBlock -> [String]
- Language.Haskell.Stylish.Step.Data: step' :: Config -> Lines -> Module -> Lines
- Language.Haskell.Stylish.Step.Data: type ChangeLine = Change String
- Language.Haskell.Stylish.Step.Imports: instance Data.Aeson.Types.FromJSON.FromJSON Language.Haskell.Stylish.Step.Imports.ListPadding
- Language.Haskell.Stylish.Step.Imports: instance GHC.Classes.Eq l => GHC.Classes.Eq (Language.Haskell.Stylish.Step.Imports.ImportEntity l)
- Language.Haskell.Stylish.Step.Imports: instance GHC.Classes.Ord l => GHC.Base.Monoid (Language.Haskell.Stylish.Step.Imports.ImportPortion l)
- Language.Haskell.Stylish.Step.Imports: instance GHC.Classes.Ord l => GHC.Base.Semigroup (Language.Haskell.Stylish.Step.Imports.ImportPortion l)
- Language.Haskell.Stylish.Step.Imports: instance GHC.Classes.Ord l => GHC.Classes.Ord (Language.Haskell.Stylish.Step.Imports.ImportEntity l)
+ Language.Haskell.Stylish: ErrorOnFormatExitBehavior :: ExitCodeBehavior
+ Language.Haskell.Stylish: NormalExitBehavior :: ExitCodeBehavior
+ Language.Haskell.Stylish: [configExitCode] :: Config -> ExitCodeBehavior
+ Language.Haskell.Stylish: data ExitCodeBehavior
+ Language.Haskell.Stylish.GHC: baseDynFlags :: DynFlags
+ Language.Haskell.Stylish.GHC: compareOutputable :: Outputable a => a -> a -> Ordering
+ Language.Haskell.Stylish.GHC: dropAfterLocated :: Maybe (Located a) -> [RealLocated b] -> [RealLocated b]
+ Language.Haskell.Stylish.GHC: dropBeforeAndAfter :: Located a -> [RealLocated b] -> [RealLocated b]
+ Language.Haskell.Stylish.GHC: dropBeforeLocated :: Maybe (Located a) -> [RealLocated b] -> [RealLocated b]
+ Language.Haskell.Stylish.GHC: getEndLineUnsafe :: Located a -> Int
+ Language.Haskell.Stylish.GHC: getStartLineUnsafe :: Located a -> Int
+ Language.Haskell.Stylish.GHC: showOutputable :: Outputable a => a -> String
+ Language.Haskell.Stylish.GHC: unLocated :: Located a -> a
+ Language.Haskell.Stylish.Module: Module :: [RealLocated AnnotationComment] -> [(ApiAnnKey, [SrcSpan])] -> Map RealSrcSpan [AnnKeywordId] -> Located (HsModule GhcPs) -> Module
+ Language.Haskell.Stylish.Module: [parsedAnnotSrcs] :: Module -> Map RealSrcSpan [AnnKeywordId]
+ Language.Haskell.Stylish.Module: [parsedAnnotations] :: Module -> [(ApiAnnKey, [SrcSpan])]
+ Language.Haskell.Stylish.Module: [parsedComments] :: Module -> [RealLocated AnnotationComment]
+ Language.Haskell.Stylish.Module: [parsedModule] :: Module -> Located (HsModule GhcPs)
+ Language.Haskell.Stylish.Module: canMergeImport :: Import -> Import -> Bool
+ Language.Haskell.Stylish.Module: data Comments
+ Language.Haskell.Stylish.Module: data Decls
+ Language.Haskell.Stylish.Module: data Import
+ Language.Haskell.Stylish.Module: data Module
+ Language.Haskell.Stylish.Module: data ModuleHeader
+ Language.Haskell.Stylish.Module: instance Data.Data.Data Language.Haskell.Stylish.Module.Module
+ Language.Haskell.Stylish.Module: instance GHC.Classes.Eq Language.Haskell.Stylish.Module.Import
+ Language.Haskell.Stylish.Module: instance GHC.Classes.Ord Language.Haskell.Stylish.Module.Import
+ Language.Haskell.Stylish.Module: instance Outputable.Outputable Language.Haskell.Stylish.Module.Import
+ Language.Haskell.Stylish.Module: lookupAnnotation :: SrcSpan -> Module -> [AnnKeywordId]
+ Language.Haskell.Stylish.Module: makeModule :: PState -> Located (HsModule GhcPs) -> Module
+ Language.Haskell.Stylish.Module: mergeModuleImport :: Located Import -> Located Import -> Located Import
+ Language.Haskell.Stylish.Module: moduleComments :: Module -> Comments
+ Language.Haskell.Stylish.Module: moduleDecls :: Module -> Decls
+ Language.Haskell.Stylish.Module: moduleHeader :: Module -> ModuleHeader
+ Language.Haskell.Stylish.Module: moduleImportGroups :: Module -> [NonEmpty (Located Import)]
+ Language.Haskell.Stylish.Module: moduleImports :: Module -> [Located Import]
+ Language.Haskell.Stylish.Module: moduleLanguagePragmas :: Module -> [(RealSrcSpan, NonEmpty Text)]
+ Language.Haskell.Stylish.Module: queryModule :: Typeable a => (a -> [b]) -> Module -> [b]
+ Language.Haskell.Stylish.Module: rawComments :: Comments -> [RealLocated AnnotationComment]
+ Language.Haskell.Stylish.Module: rawImport :: Import -> ImportDecl GhcPs
+ Language.Haskell.Stylish.Module: rawModuleAnnotations :: Module -> [(ApiAnnKey, [SrcSpan])]
+ Language.Haskell.Stylish.Module: rawModuleDecls :: Decls -> [LHsDecl GhcPs]
+ Language.Haskell.Stylish.Module: rawModuleExports :: ModuleHeader -> Maybe (Located [LIE GhcPs])
+ Language.Haskell.Stylish.Module: rawModuleHaddocks :: ModuleHeader -> Maybe LHsDocString
+ Language.Haskell.Stylish.Module: rawModuleName :: ModuleHeader -> Maybe (Located ModuleName)
+ Language.Haskell.Stylish.Module: type Lines = [String]
+ Language.Haskell.Stylish.Printer: Printer :: ReaderT PrinterConfig (State PrinterState) a -> Printer a
+ Language.Haskell.Stylish.Printer: PrinterConfig :: !Maybe Int -> PrinterConfig
+ Language.Haskell.Stylish.Printer: PrinterState :: !Lines -> !Int -> !String -> ![RealLocated AnnotationComment] -> !Module -> PrinterState
+ Language.Haskell.Stylish.Printer: [columns] :: PrinterConfig -> !Maybe Int
+ Language.Haskell.Stylish.Printer: [currentLine] :: PrinterState -> !String
+ Language.Haskell.Stylish.Printer: [linePos] :: PrinterState -> !Int
+ Language.Haskell.Stylish.Printer: [lines] :: PrinterState -> !Lines
+ Language.Haskell.Stylish.Printer: [parsedModule] :: PrinterState -> !Module
+ Language.Haskell.Stylish.Printer: [pendingComments] :: PrinterState -> ![RealLocated AnnotationComment]
+ Language.Haskell.Stylish.Printer: comma :: P ()
+ Language.Haskell.Stylish.Printer: data PrinterConfig
+ Language.Haskell.Stylish.Printer: data PrinterState
+ Language.Haskell.Stylish.Printer: dot :: P ()
+ Language.Haskell.Stylish.Printer: getAnnot :: SrcSpan -> P [AnnKeywordId]
+ Language.Haskell.Stylish.Printer: getCurrentLine :: P String
+ Language.Haskell.Stylish.Printer: getCurrentLineLength :: P Int
+ Language.Haskell.Stylish.Printer: getDocstrPrev :: SrcSpan -> P (Maybe AnnotationComment)
+ Language.Haskell.Stylish.Printer: groupAttachedComments :: [Located a] -> P [([AnnotationComment], NonEmpty (Located a))]
+ Language.Haskell.Stylish.Printer: instance Control.Monad.Reader.Class.MonadReader Language.Haskell.Stylish.Printer.PrinterConfig Language.Haskell.Stylish.Printer.Printer
+ Language.Haskell.Stylish.Printer: instance Control.Monad.State.Class.MonadState Language.Haskell.Stylish.Printer.PrinterState Language.Haskell.Stylish.Printer.Printer
+ Language.Haskell.Stylish.Printer: instance GHC.Base.Applicative Language.Haskell.Stylish.Printer.Printer
+ Language.Haskell.Stylish.Printer: instance GHC.Base.Functor Language.Haskell.Stylish.Printer.Printer
+ Language.Haskell.Stylish.Printer: instance GHC.Base.Monad Language.Haskell.Stylish.Printer.Printer
+ Language.Haskell.Stylish.Printer: modifyCurrentLine :: (String -> String) -> P ()
+ Language.Haskell.Stylish.Printer: newline :: P ()
+ Language.Haskell.Stylish.Printer: newtype Printer a
+ Language.Haskell.Stylish.Printer: parenthesize :: P a -> P a
+ Language.Haskell.Stylish.Printer: peekNextCommentPos :: P (Maybe SrcSpan)
+ Language.Haskell.Stylish.Printer: prefix :: P a -> P b -> P b
+ Language.Haskell.Stylish.Printer: putAllSpanComments :: P () -> SrcSpan -> P ()
+ Language.Haskell.Stylish.Printer: putComment :: AnnotationComment -> P ()
+ Language.Haskell.Stylish.Printer: putCond :: (PrinterState -> Bool) -> P b -> P b -> P b
+ Language.Haskell.Stylish.Printer: putEolComment :: SrcSpan -> P ()
+ Language.Haskell.Stylish.Printer: putOutputable :: Outputable a => a -> P ()
+ Language.Haskell.Stylish.Printer: putRdrName :: Located RdrName -> P ()
+ Language.Haskell.Stylish.Printer: putText :: String -> P ()
+ Language.Haskell.Stylish.Printer: putType :: Located (HsType GhcPs) -> P ()
+ Language.Haskell.Stylish.Printer: removeCommentTo :: SrcSpan -> P [AnnotationComment]
+ Language.Haskell.Stylish.Printer: removeCommentToEnd :: SrcSpan -> P [AnnotationComment]
+ Language.Haskell.Stylish.Printer: removeLineComment :: Int -> P (Maybe AnnotationComment)
+ Language.Haskell.Stylish.Printer: runPrinter :: PrinterConfig -> [RealLocated AnnotationComment] -> Module -> Printer a -> (a, Lines)
+ Language.Haskell.Stylish.Printer: runPrinter_ :: PrinterConfig -> [RealLocated AnnotationComment] -> Module -> Printer a -> Lines
+ Language.Haskell.Stylish.Printer: sep :: P a -> [P a] -> P ()
+ Language.Haskell.Stylish.Printer: space :: P ()
+ Language.Haskell.Stylish.Printer: spaces :: Int -> P ()
+ Language.Haskell.Stylish.Printer: suffix :: P a -> P b -> P a
+ Language.Haskell.Stylish.Printer: type P = Printer
+ Language.Haskell.Stylish.Printer: withColumns :: Maybe Int -> P a -> P a
+ Language.Haskell.Stylish.Printer: wrapping :: P a -> P a -> P a
+ Language.Haskell.Stylish.Step.Data: MaxColumns :: !Int -> MaxColumns
+ Language.Haskell.Stylish.Step.Data: NoMaxColumns :: MaxColumns
+ Language.Haskell.Stylish.Step.Data: [cBreakEnums] :: Config -> !Bool
+ Language.Haskell.Stylish.Step.Data: [cBreakSingleConstructors] :: Config -> !Bool
+ Language.Haskell.Stylish.Step.Data: [cCurriedContext] :: Config -> !Bool
+ Language.Haskell.Stylish.Step.Data: [cMaxColumns] :: Config -> !MaxColumns
+ Language.Haskell.Stylish.Step.Data: [cVia] :: Config -> !Indent
+ Language.Haskell.Stylish.Step.Data: data MaxColumns
+ Language.Haskell.Stylish.Step.Data: instance GHC.Classes.Eq Language.Haskell.Stylish.Step.Data.Indent
+ Language.Haskell.Stylish.Step.Data: instance GHC.Classes.Eq Language.Haskell.Stylish.Step.Data.MaxColumns
+ Language.Haskell.Stylish.Step.Data: instance GHC.Show.Show Language.Haskell.Stylish.Step.Data.MaxColumns
+ Language.Haskell.Stylish.Step.Imports: Repeat :: ListAlign
+ Language.Haskell.Stylish.Step.Imports: instance GHC.Base.Monoid Language.Haskell.Stylish.Step.Imports.ImportStats
+ Language.Haskell.Stylish.Step.Imports: instance GHC.Base.Semigroup Language.Haskell.Stylish.Step.Imports.ImportStats
+ Language.Haskell.Stylish.Step.ModuleHeader: Config :: Int -> Bool -> Config
+ Language.Haskell.Stylish.Step.ModuleHeader: [indent] :: Config -> Int
+ Language.Haskell.Stylish.Step.ModuleHeader: [sort] :: Config -> Bool
+ Language.Haskell.Stylish.Step.ModuleHeader: data Config
+ Language.Haskell.Stylish.Step.ModuleHeader: defaultConfig :: Config
+ Language.Haskell.Stylish.Step.ModuleHeader: step :: Config -> Step
- Language.Haskell.Stylish: Config :: [Step] -> Maybe Int -> [String] -> Newline -> Bool -> Config
+ Language.Haskell.Stylish: Config :: [Step] -> Maybe Int -> [String] -> Newline -> Bool -> ExitCodeBehavior -> Config
- Language.Haskell.Stylish.Step.Data: Config :: !Indent -> !Indent -> !Int -> !Int -> Config
+ Language.Haskell.Stylish.Step.Data: Config :: !Indent -> !Indent -> !Int -> !Int -> !Bool -> !Bool -> !Indent -> !Bool -> !MaxColumns -> Config
- Language.Haskell.Stylish.Step.LanguagePragmas: addLanguagePragma :: String -> String -> Module SrcSpanInfo -> [Change String]
+ Language.Haskell.Stylish.Step.LanguagePragmas: addLanguagePragma :: String -> String -> Module -> [Change String]

Files

CHANGELOG view
@@ -1,5 +1,38 @@ # CHANGELOG +- 0.12.0.0 (2020-10-02)+     *  Use ghc-lib-parser rather than haskell-src-exts++        This patch swaps out the parsing library from `haskell-src-exts` to+        `ghc-lib-parser`, which gives us better compatibility with GHC.++        Because almost every module heavily used the Haskell AST provided by+        `haskell-src-exts`, this was a huge effort and it would not have been+        possible without Felix Mulder doing an initial port, GSoC student+        Beatrice Vergani porting several other steps, and Łukasz Gołębiewski and+        Paweł Szulc who helped me finish up things in the home stretch.++        I've generally tried to keep styling 100% compatible with what was there+        before, but some issues may have unintentionally slipped in so please+        report those.++        This introduces one new import styling contributed by Felix: when+        wrapping import lists over multiple lines, you can repeat the module+        name, e.g.:++            import Control.Monad.Except as X (ExceptT (..), MonadError (..))+            import Control.Monad.Except as X (runExceptT, withExceptT)++        This is activated by using `import_align: repeat`.++        Secondly, a new Step was added, `module_header`, which formats the+        export list of a module, including the trailing `where` clause.  Details+        for this new step can be found in the `data/stylish-haskell.yaml`.++     *   Remove `semigroup` dependency for GHC >= 8.0+     *   Bump `strict` upper bound to 0.4+     *   Bump `Cabal` upper bound to 3.3 for test suite+ - 0.11.0.3 (2020-08-02)     * Set default-language to Haskell2010 
data/stylish-haskell.yaml view
@@ -15,6 +15,19 @@   #     # true.   #     add_language_pragma: true +  # Format module header+  #+  # Currently, this option is not configurable and will format all exports and+  # module declarations to minimize diffs+  #+  # - module_header:+  #     # How many spaces use for indentation in the module header.+  #     indent: 4+  #+  #     # Should export lists be sorted?  Sorting is only performed within the+  #     # export section, as delineated by Haddock comments.+  #     sort: true+   # Format record definitions. This is disabled by default.   #   # You can control the layout of record fields. The only rules that can't be configured@@ -42,6 +55,31 @@   #   #     # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines.   #     deriving: 2+  #+  #     # How many spaces to insert before "via" clause counted from indentation of deriving clause+  #     # Possible values:+  #     # - "same_line" -- "{" and first field goes on the same line as the data constructor.+  #     # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor+  #     via: "indent 2"+  #+  #     # Wheter or not to break enums onto several lines+  #     #+  #     # Default: false+  #     break_enums: false+  #+  #     # Whether or not to break single constructor data types before `=` sign+  #     #+  #     # Default: true+  #     break_single_constructors: true+  #+  #     # Whether or not to curry constraints on function.+  #     #+  #     # E.g: @allValues :: Enum a => Bounded a => Proxy a -> [a]@+  #     #+  #     # Instead of @allValues :: (Enum a, Bounded a) => Proxy a -> [a]@+  #     #+  #     # Default: false+  #     curried_context: false    # Align the right hand side of some elements.  This is quite conservative   # and only applies to statements where each element occupies a single@@ -101,6 +139,11 @@       #   > import qualified Data.List      as List       #   >     (concat, foldl, foldr, head, init, last, length)       #+      # - repeat: Repeat the module name to align the import list.+      #+      #   > import qualified Data.List      as List (concat, foldl, foldr, head)+      #   > import qualified Data.List      as List (init, last, length)+      #       # Default: after_alias       list_align: after_alias @@ -202,6 +245,22 @@       #       # Default: false       space_surround: false++      # Enabling this argument will use the new GHC lib parse to format imports.+      #+      # This currently assumes a few things, it will assume that you want post+      # qualified imports. It is also not as feature complete as the old+      # imports formatting.+      #+      # It does not remove redundant lines or merge lines. As such, the full+      # feature scope is still pending.+      #+      # It _is_ however, a fine alternative if you are using features that are+      # not parseable by haskell src extensions and you're comfortable with the+      # presets.+      #+      # Default: false+      ghc_lib_parser: false    # Language pragmas   - language_pragmas:
lib/Language/Haskell/Stylish.hs view
@@ -91,14 +91,19 @@  -------------------------------------------------------------------------------- runStep :: Extensions -> Maybe FilePath -> Lines -> Step -> Either String Lines-runStep exts mfp ls step =-    stepFilter step ls <$> parseModule exts mfp (unlines ls)-+runStep exts mfp ls = \case+  Step _name step ->+    step ls <$> parseModule exts mfp (unlines ls)  ---------------------------------------------------------------------------------runSteps :: Extensions -> Maybe FilePath -> [Step] -> Lines-         -> Either String Lines-runSteps exts mfp steps ls = foldM (runStep exts mfp) ls steps+runSteps ::+     Extensions+  -> Maybe FilePath+  -> [Step]+  -> Lines+  -> Either String Lines+runSteps exts mfp steps ls =+ foldM (runStep exts mfp) ls steps  newtype ConfigPath = ConfigPath { unConfigPath :: FilePath } 
lib/Language/Haskell/Stylish/Align.hs view
@@ -8,7 +8,7 @@  -------------------------------------------------------------------------------- import           Data.List                       (nub)-import qualified Language.Haskell.Exts           as H+import qualified SrcLoc                          as S   --------------------------------------------------------------------------------@@ -51,49 +51,48 @@     , aRightLead :: !Int     } deriving (Show) - -------------------------------------------------------------------------------- -- | Create changes that perform the alignment.+ align-    :: Maybe Int              -- ^ Max columns-    -> [Alignable H.SrcSpan]  -- ^ Alignables-    -> [Change String]        -- ^ Changes performing the alignment.+  :: Maybe Int                    -- ^ Max columns+  -> [Alignable S.RealSrcSpan]    -- ^ Alignables+  -> [Change String]              -- ^ Changes performing the alignment align _ [] = [] align maxColumns alignment-    -- Do not make any change if we would go past the maximum number of columns.-    | exceedsColumns (longestLeft + longestRight) = []-    | not (fixable alignment)                     = []-    | otherwise                                   = map align' alignment+  -- Do not make an changes if we would go past the maximum number of columns+  | exceedsColumns (longestLeft + longestRight)  = []+  | not (fixable alignment)                      = []+  | otherwise                                    = map align' alignment   where     exceedsColumns i = case maxColumns of-        Nothing -> False  -- No number exceeds a maximum column count of-                          -- Nothing, because there is no limit to exceed.-        Just c -> i > c+      Nothing -> False+      Just c  -> i > c -    -- The longest thing in the left column.-    longestLeft = maximum $ map (H.srcSpanEndColumn . aLeft) alignment+    -- The longest thing in the left column+    longestLeft = maximum $ map (S.srcSpanEndCol . aLeft) alignment -    -- The longest thing in the right column.+    -- The longest thing in the right column     longestRight = maximum-        [ H.srcSpanEndColumn (aRight a) - H.srcSpanStartColumn (aRight a)-            + aRightLead a-        | a <- alignment-        ]--    align' a = changeLine (H.srcSpanStartLine $ aContainer a) $ \str ->-        let column      = H.srcSpanEndColumn $ aLeft a-            (pre, post) = splitAt column str-        in [padRight longestLeft (trimRight pre) ++ trimLeft post]+      [ S.srcSpanEndCol (aRight a) - S.srcSpanStartCol (aRight a)+          + aRightLead a+      | a <- alignment+      ] +    align' a = changeLine (S.srcSpanStartLine $ aContainer a) $ \str ->+      let column = S.srcSpanEndCol $ aLeft a+          (pre, post) = splitAt column str+      in [padRight longestLeft (trimRight pre) ++ trimLeft post]   -------------------------------------------------------------------------------- -- | Checks that all the alignables appear on a single line, and that they do -- not overlap.-fixable :: [Alignable H.SrcSpan] -> Bool++fixable :: [Alignable S.RealSrcSpan] -> Bool fixable []     = False fixable [_]    = False fixable fields = all singleLine containers && nonOverlapping containers   where     containers        = map aContainer fields-    singleLine s      = H.srcSpanStartLine s == H.srcSpanEndLine s-    nonOverlapping ss = length ss == length (nub $ map H.srcSpanStartLine ss)+    singleLine s      = S.srcSpanStartLine s == S.srcSpanEndLine s+    nonOverlapping ss = length ss == length (nub $ map S.srcSpanStartLine ss)
lib/Language/Haskell/Stylish/Block.hs view
@@ -4,20 +4,17 @@     , LineBlock     , SpanBlock     , blockLength-    , linesFromSrcSpan-    , spanFromSrcSpan     , moveBlock     , adjacent     , merge+    , mergeAdjacent     , overlapping     , groupAdjacent     ) where   ---------------------------------------------------------------------------------import           Control.Arrow         (arr, (&&&), (>>>))-import qualified Data.IntSet           as IS-import qualified Language.Haskell.Exts as H+import qualified Data.IntSet as IS   --------------------------------------------------------------------------------@@ -25,7 +22,8 @@ data Block a = Block     { blockStart :: Int     , blockEnd   :: Int-    } deriving (Eq, Ord, Show)+    }+    deriving (Eq, Ord, Show)   --------------------------------------------------------------------------------@@ -40,22 +38,7 @@ blockLength :: Block a -> Int blockLength (Block start end) = end - start + 1 - ---------------------------------------------------------------------------------linesFromSrcSpan :: H.SrcSpanInfo -> LineBlock-linesFromSrcSpan = H.srcInfoSpan >>>-    H.srcSpanStartLine &&& H.srcSpanEndLine >>>-    arr (uncurry Block)------------------------------------------------------------------------------------spanFromSrcSpan :: H.SrcSpanInfo -> SpanBlock-spanFromSrcSpan = H.srcInfoSpan >>>-    H.srcSpanStartColumn &&& H.srcSpanEndColumn >>>-    arr (uncurry Block)----------------------------------------------------------------------------------- moveBlock :: Int -> Block a -> Block a moveBlock offset (Block start end) = Block (start + offset) (end + offset) @@ -94,3 +77,8 @@     go (b1, x) gs = case break (adjacent b1 . fst) gs of         (_, [])               -> (b1, [x]) : gs         (ys, ((b2, xs) : zs)) -> (merge b1 b2, x : xs) : (ys ++ zs)++mergeAdjacent :: [Block a] -> [Block a]+mergeAdjacent (a : b : rest) | a `adjacent` b = merge a b : mergeAdjacent rest+mergeAdjacent (a : rest)     = a : mergeAdjacent rest+mergeAdjacent []             = []
lib/Language/Haskell/Stylish/Config.hs view
@@ -1,9 +1,12 @@ --------------------------------------------------------------------------------+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE LambdaCase        #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell   #-} module Language.Haskell.Stylish.Config     ( Extensions     , Config (..)+    , ExitCodeBehavior (..)     , defaultConfigBytes     , configFilePath     , loadConfig@@ -40,6 +43,7 @@ import           Language.Haskell.Stylish.Step import qualified Language.Haskell.Stylish.Step.Data               as Data import qualified Language.Haskell.Stylish.Step.Imports            as Imports+import qualified Language.Haskell.Stylish.Step.ModuleHeader       as ModuleHeader import qualified Language.Haskell.Stylish.Step.LanguagePragmas    as LanguagePragmas import qualified Language.Haskell.Stylish.Step.SimpleAlign        as SimpleAlign import qualified Language.Haskell.Stylish.Step.Squash             as Squash@@ -60,9 +64,19 @@     , configLanguageExtensions :: [String]     , configNewline            :: IO.Newline     , configCabal              :: Bool+    , configExitCode           :: ExitCodeBehavior     } +--------------------------------------------------------------------------------+data ExitCodeBehavior+  = NormalExitBehavior+  | ErrorOnFormatExitBehavior+  deriving (Eq) +instance Show ExitCodeBehavior where+  show NormalExitBehavior = "normal"+  show ErrorOnFormatExitBehavior = "error_on_format"+ -------------------------------------------------------------------------------- instance FromJSON Config where     parseJSON = parseConfig@@ -126,6 +140,7 @@         <*> (o A..:? "language_extensions" A..!= [])         <*> (o A..:? "newline"             >>= parseEnum newlines IO.nativeNewline)         <*> (o A..:? "cabal"               A..!= True)+        <*> (o A..:? "exit_code"           >>= parseEnum exitCodes NormalExitBehavior)      -- Then fill in the steps based on the partial config we already have     stepValues <- o A..: "steps" :: A.Parser [A.Value]@@ -137,6 +152,10 @@         , ("lf",     IO.LF)         , ("crlf",   IO.CRLF)         ]+    exitCodes =+        [ ("normal", NormalExitBehavior)+        , ("error_on_format", ErrorOnFormatExitBehavior)+        ] parseConfig _            = mzero  @@ -144,6 +163,7 @@ catalog :: Map String (Config -> A.Object -> A.Parser Step) catalog = M.fromList     [ ("imports",             parseImports)+    , ("module_header",       parseModuleHeader)     , ("records",             parseRecords)     , ("language_pragmas",    parseLanguagePragmas)     , ("simple_align",        parseSimpleAlign)@@ -172,6 +192,11 @@     Nothing -> fail $ "Unknown option: " ++ k ++ ", should be one of: " ++         intercalate ", " (map fst strs) +--------------------------------------------------------------------------------+parseModuleHeader :: Config -> A.Object -> A.Parser Step+parseModuleHeader _ o = fmap ModuleHeader.step $ ModuleHeader.Config+    <$> o A..:? "indent" A..!= (ModuleHeader.indent ModuleHeader.defaultConfig)+    <*> o A..:? "sort"   A..!= (ModuleHeader.sort ModuleHeader.defaultConfig)  -------------------------------------------------------------------------------- parseSimpleAlign :: Config -> A.Object -> A.Parser Step@@ -186,13 +211,20 @@  -------------------------------------------------------------------------------- parseRecords :: Config -> A.Object -> A.Parser Step-parseRecords _ o = Data.step+parseRecords c o = Data.step     <$> (Data.Config         <$> (o A..: "equals" >>= parseIndent)         <*> (o A..: "first_field" >>= parseIndent)         <*> (o A..: "field_comment")-        <*> (o A..: "deriving"))-+        <*> (o A..: "deriving")+        <*> (o A..:? "break_enums" A..!= False)+        <*> (o A..:? "break_single_constructors" A..!= True)+        <*> (o A..: "via" >>= parseIndent)+        <*> (o A..:? "curried_context" A..!= False)+        <*> pure configMaxColumns)+  where+    configMaxColumns =+      maybe Data.NoMaxColumns Data.MaxColumns (configColumns c)  parseIndent :: A.Value -> A.Parser Data.Indent parseIndent = A.withText "Indent" $ \t ->@@ -214,23 +246,21 @@  -------------------------------------------------------------------------------- parseImports :: Config -> A.Object -> A.Parser Step-parseImports config o = Imports.step-    <$> pure (configColumns config)-    <*> (Imports.Options-        <$> (o A..:? "align" >>= parseEnum aligns (def Imports.importAlign))-        <*> (o A..:? "list_align" >>= parseEnum listAligns (def Imports.listAlign))-        <*> (o A..:? "pad_module_names" A..!= def Imports.padModuleNames)-        <*> (o A..:? "long_list_align"-            >>= parseEnum longListAligns (def Imports.longListAlign))-        -- Note that padding has to be at least 1. Default is 4.-        <*> (o A..:? "empty_list_align"-            >>= parseEnum emptyListAligns (def Imports.emptyListAlign))-        <*> o A..:? "list_padding" A..!= def Imports.listPadding-        <*> o A..:? "separate_lists" A..!= def Imports.separateLists-        <*> o A..:? "space_surround" A..!= def Imports.spaceSurround)+parseImports config o = fmap (Imports.step columns) $ Imports.Options+      <$> (o A..:? "align" >>= parseEnum aligns (def Imports.importAlign))+      <*> (o A..:? "list_align" >>= parseEnum listAligns (def Imports.listAlign))+      <*> (o A..:? "pad_module_names" A..!= def Imports.padModuleNames)+      <*> (o A..:? "long_list_align" >>= parseEnum longListAligns (def Imports.longListAlign))+      <*> (o A..:? "empty_list_align" >>= parseEnum emptyListAligns (def Imports.emptyListAlign))+      -- Note that padding has to be at least 1. Default is 4.+      <*> (o A..:? "list_padding" >>= maybe (pure $ def Imports.listPadding) parseListPadding)+      <*> o A..:? "separate_lists" A..!= def Imports.separateLists+      <*> o A..:? "space_surround" A..!= def Imports.spaceSurround   where     def f = f Imports.defaultOptions +    columns = configColumns config+     aligns =         [ ("global", Imports.Global)         , ("file",   Imports.File)@@ -243,6 +273,7 @@         , ("with_module_name",  Imports.WithModuleName)         , ("with_alias",        Imports.WithAlias)         , ("after_alias",       Imports.AfterAlias)+        , ("repeat",            Imports.Repeat)         ]      longListAligns =@@ -256,6 +287,11 @@         [ ("inherit", Imports.Inherit)         , ("right_after", Imports.RightAfter)         ]++    parseListPadding = \case+        A.String "module_name" -> pure Imports.LPModuleName+        A.Number n | n >= 1 -> pure $ Imports.LPConstant (truncate n)+        v -> A.typeMismatch "'module_name' or >=1 number" v  -------------------------------------------------------------------------------- parseLanguagePragmas :: Config -> A.Object -> A.Parser Step
+ lib/Language/Haskell/Stylish/GHC.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE LambdaCase #-}+{-# OPTIONS_GHC -Wno-missing-fields #-}+-- | Utility functions for working with the GHC AST+module Language.Haskell.Stylish.GHC+  ( dropAfterLocated+  , dropBeforeLocated+  , dropBeforeAndAfter+    -- * Unsafe getters+  , getEndLineUnsafe+  , getStartLineUnsafe+    -- * Standard settings+  , baseDynFlags+    -- * Positions+  , unLocated+    -- * Outputable operators+  , showOutputable+  , compareOutputable+  ) where++--------------------------------------------------------------------------------+import           Data.Function (on)++--------------------------------------------------------------------------------+import           DynFlags                        (Settings(..), defaultDynFlags)+import qualified DynFlags                        as GHC+import           FileSettings                    (FileSettings(..))+import           GHC.Fingerprint                 (fingerprint0)+import           GHC.Platform+import           GHC.Version                     (cProjectVersion)+import           GhcNameVersion                  (GhcNameVersion(..))+import           PlatformConstants               (PlatformConstants(..))+import           SrcLoc                          (GenLocated(..), SrcSpan(..))+import           SrcLoc                          (Located, RealLocated)+import           SrcLoc                          (srcSpanStartLine, srcSpanEndLine)+import           ToolSettings                    (ToolSettings(..))+import qualified Outputable                      as GHC++getStartLineUnsafe :: Located a -> Int+getStartLineUnsafe = \case+  (L (RealSrcSpan s) _) -> srcSpanStartLine s+  _ -> error "could not get start line of block"++getEndLineUnsafe :: Located a -> Int+getEndLineUnsafe = \case+  (L (RealSrcSpan s) _) -> srcSpanEndLine s+  _ -> error "could not get end line of block"++dropAfterLocated :: Maybe (Located a) -> [RealLocated b] -> [RealLocated b]+dropAfterLocated loc xs = case loc of+  Just (L (RealSrcSpan rloc) _) ->+    filter (\(L x _) -> srcSpanEndLine rloc >= srcSpanStartLine x) xs+  _ -> xs++dropBeforeLocated :: Maybe (Located a) -> [RealLocated b] -> [RealLocated b]+dropBeforeLocated loc xs = case loc of+  Just (L (RealSrcSpan rloc) _) ->+    filter (\(L x _) -> srcSpanStartLine rloc <= srcSpanEndLine x) xs+  _ -> xs++dropBeforeAndAfter :: Located a -> [RealLocated b] -> [RealLocated b]+dropBeforeAndAfter loc = dropBeforeLocated (Just loc) . dropAfterLocated (Just loc)++baseDynFlags :: GHC.DynFlags+baseDynFlags = defaultDynFlags fakeSettings llvmConfig+  where+    fakeSettings = GHC.Settings+      { sGhcNameVersion = GhcNameVersion "stylish-haskell" cProjectVersion+      , sFileSettings = FileSettings {}+      , sToolSettings = ToolSettings+        { toolSettings_opt_P_fingerprint = fingerprint0,+          toolSettings_pgm_F = ""+        }+      , sPlatformConstants = PlatformConstants+        { pc_DYNAMIC_BY_DEFAULT = False+        , pc_WORD_SIZE = 8+        }+      , sTargetPlatform = Platform+        { platformMini = PlatformMini+          { platformMini_arch = ArchUnknown+          , platformMini_os = OSUnknown+          }+        , platformWordSize = PW8+        , platformUnregisterised = True+        , platformHasIdentDirective = False+        , platformHasSubsectionsViaSymbols = False+        , platformIsCrossCompiling = False+        }+      , sPlatformMisc = PlatformMisc {}+      , sRawSettings = []+      }++    llvmConfig = GHC.LlvmConfig [] []++unLocated :: Located a -> a+unLocated (L _ a) = a++showOutputable :: GHC.Outputable a => a -> String+showOutputable = GHC.showPpr baseDynFlags++compareOutputable :: GHC.Outputable a => a -> a -> Ordering+compareOutputable = compare `on` showOutputable
+ lib/Language/Haskell/Stylish/Module.hs view
@@ -0,0 +1,280 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE DeriveDataTypeable #-}+module Language.Haskell.Stylish.Module+  ( -- * Data types+    Module (..)+  , ModuleHeader+  , Import+  , Decls+  , Comments+  , Lines+  , makeModule++    -- * Getters+  , moduleHeader+  , moduleImports+  , moduleImportGroups+  , moduleDecls+  , moduleComments+  , moduleLanguagePragmas+  , queryModule++    -- * Imports+  , canMergeImport+  , mergeModuleImport++    -- * Annotations+  , lookupAnnotation++    -- * Internal API getters+  , rawComments+  , rawImport+  , rawModuleAnnotations+  , rawModuleDecls+  , rawModuleExports+  , rawModuleHaddocks+  , rawModuleName+  ) where++--------------------------------------------------------------------------------+import           Data.Function                   ((&), on)+import           Data.Functor                    ((<&>))+import           Data.Generics                   (Typeable, everything, mkQ)+import           Data.Maybe                      (mapMaybe)+import           Data.Map                        (Map)+import qualified Data.Map                        as Map+import           Data.List                       (nubBy, sort)+import           Data.List.NonEmpty              (NonEmpty (..), nonEmpty)+import           Data.Text                       (Text)+import qualified Data.Text                       as T+import           Data.Data                       (Data)++--------------------------------------------------------------------------------+import qualified ApiAnnotation                   as GHC+import qualified Lexer                           as GHC+import           GHC.Hs                          (ImportDecl(..), ImportDeclQualifiedStyle(..))+import qualified GHC.Hs                          as GHC+import           GHC.Hs.Extension                (GhcPs)+import           GHC.Hs.Decls                    (LHsDecl)+import           Outputable                      (Outputable)+import           SrcLoc                          (GenLocated(..), RealLocated)+import           SrcLoc                          (RealSrcSpan(..), SrcSpan(..))+import           SrcLoc                          (Located)+import qualified SrcLoc                          as GHC+import qualified Module                          as GHC++--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.GHC++--------------------------------------------------------------------------------+type Lines = [String]+++--------------------------------------------------------------------------------+-- | Concrete module type+data Module = Module+  { parsedComments :: [GHC.RealLocated GHC.AnnotationComment]+  , parsedAnnotations :: [(GHC.ApiAnnKey, [GHC.SrcSpan])]+  , parsedAnnotSrcs :: Map RealSrcSpan [GHC.AnnKeywordId]+  , parsedModule :: GHC.Located (GHC.HsModule GhcPs)+  } deriving (Data)++-- | Declarations in module+newtype Decls = Decls [LHsDecl GhcPs]++-- | Import declaration in module+newtype Import = Import { unImport :: ImportDecl GhcPs }+  deriving newtype (Outputable)++-- | Returns true if the two import declarations can be merged+canMergeImport :: Import -> Import -> Bool+canMergeImport (Import i0) (Import i1) = and $ fmap (\f -> f i0 i1)+  [ (==) `on` unLocated . ideclName+  , (==) `on` ideclPkgQual+  , (==) `on` ideclSource+  , hasMergableQualified `on` ideclQualified+  , (==) `on` ideclImplicit+  , (==) `on` fmap unLocated . ideclAs+  , (==) `on` fmap fst . ideclHiding -- same 'hiding' flags+  ]+  where+    hasMergableQualified QualifiedPre QualifiedPost = True+    hasMergableQualified QualifiedPost QualifiedPre = True+    hasMergableQualified q0 q1 = q0 == q1++instance Eq Import where+  i0 == i1 = canMergeImport i0 i1 && hasSameImports (unImport i0) (unImport i1)+    where+      hasSameImports = (==) `on` fmap snd . ideclHiding++instance Ord Import where+  compare (Import i0) (Import i1) =+    ideclName i0 `compareOutputable` ideclName i1 <>+    fmap showOutputable (ideclPkgQual i0) `compare`+        fmap showOutputable (ideclPkgQual i1) <>+    compareOutputable i0 i1++-- | Comments associated with module+newtype Comments = Comments [GHC.RealLocated GHC.AnnotationComment]++-- | A module header is its name, exports and haddock docstring+data ModuleHeader = ModuleHeader+  { name :: Maybe (GHC.Located GHC.ModuleName)+  , exports :: Maybe (GHC.Located [GHC.LIE GhcPs])+  , haddocks :: Maybe GHC.LHsDocString+  }++-- | Create a module from GHC internal representations+makeModule :: GHC.PState -> GHC.Located (GHC.HsModule GHC.GhcPs) -> Module+makeModule pstate = Module comments annotations annotationMap+  where+    comments+      = sort+      . filterRealLocated+      $ GHC.comment_q pstate ++ (GHC.annotations_comments pstate >>= snd)++    filterRealLocated = mapMaybe \case+      GHC.L (GHC.RealSrcSpan s) e -> Just (GHC.L s e)+      GHC.L (GHC.UnhelpfulSpan _) _ -> Nothing++    annotations+      = GHC.annotations pstate++    annotationMap+      = GHC.annotations pstate+      & mapMaybe x+      & Map.fromListWith (++)++    x = \case+      ((RealSrcSpan rspan, annot), _) -> Just (rspan, [annot])+      _ -> Nothing++-- | Get all declarations in module+moduleDecls :: Module -> Decls+moduleDecls = Decls . GHC.hsmodDecls . unLocated . parsedModule++-- | Get comments in module+moduleComments :: Module -> Comments+moduleComments = Comments . parsedComments++-- | Get module language pragmas+moduleLanguagePragmas :: Module -> [(RealSrcSpan, NonEmpty Text)]+moduleLanguagePragmas = mapMaybe toLanguagePragma . parsedComments+  where+    toLanguagePragma :: RealLocated GHC.AnnotationComment -> Maybe (RealSrcSpan, NonEmpty Text)+    toLanguagePragma = \case+      L pos (GHC.AnnBlockComment s) ->+        Just (T.pack s)+          >>= T.stripPrefix "{-#"+          >>= T.stripSuffix "#-}"+          <&> T.strip+          <&> T.splitAt 8 -- length "LANGUAGE"+          <&> fmap (T.splitOn ",")+          <&> fmap (fmap T.strip)+          <&> fmap (filter (not . T.null))+          >>= (\(T.toUpper . T.strip -> lang, xs) -> (lang,) <$> nonEmpty xs)+          >>= (\(lang, nel) -> if lang == "LANGUAGE" then Just (pos, nel) else Nothing)+      _ -> Nothing++-- | Get module imports+moduleImports :: Module -> [Located Import]+moduleImports m+  = parsedModule m+  & unLocated+  & GHC.hsmodImports+  & fmap \(L pos i) -> L pos (Import i)++-- | Get groups of imports from module+moduleImportGroups :: Module -> [NonEmpty (Located Import)]+moduleImportGroups = go [] Nothing . moduleImports+  where+    -- Run through all imports (assume they are sorted already in order of+    -- appearance in the file) and group the ones that are on consecutive+    -- lines.+    go  :: [Located Import] -> Maybe Int -> [Located Import]+        -> [NonEmpty (Located Import)]+    go acc _        []                   = ne acc+    go acc mbCurrentLine (imp : impRest) =+        let l2 = getStartLineUnsafe imp in+        case mbCurrentLine of+            Just l1 | l1 + 1 < l2 -> ne acc ++ go [imp] (Just l2) impRest+            _                     -> go (acc ++ [imp]) (Just l2) impRest++    ne []       = []+    ne (x : xs) = [x :| xs]++-- | Merge two import declarations, keeping positions from the first+--+--   As alluded, this highlights an issue with merging imports. The GHC+--   annotation comments aren't attached to any particular AST node. This+--   means that right now, we're manually reconstructing the attachment. By+--   merging two import declarations, we lose that mapping.+--+--   It's not really a big deal if we consider that people don't usually+--   comment imports themselves. It _is_ however, systemic and it'd be better+--   if we processed comments beforehand and attached them to all AST nodes in+--   our own representation.+mergeModuleImport :: Located Import -> Located Import -> Located Import+mergeModuleImport (L p0 (Import i0)) (L _p1 (Import i1)) =+  L p0 $ Import i0 { ideclHiding = newImportNames }+  where+    newImportNames =+      case (ideclHiding i0, ideclHiding i1) of+        (Just (b, L p imps0), Just (_, L _ imps1)) -> Just (b, L p (imps0 `merge` imps1))+        (Nothing, Nothing) -> Nothing+        (Just x, Nothing) -> Just x+        (Nothing, Just x) -> Just x+    merge xs ys+      = nubBy ((==) `on` showOutputable) (xs ++ ys)++-- | Get module header+moduleHeader :: Module -> ModuleHeader+moduleHeader (Module _ _ _ (GHC.L _ m)) = ModuleHeader+  { name = GHC.hsmodName m+  , exports = GHC.hsmodExports m+  , haddocks = GHC.hsmodHaddockModHeader m+  }++-- | Query for annotations associated with a 'SrcSpan'+lookupAnnotation :: SrcSpan -> Module -> [GHC.AnnKeywordId]+lookupAnnotation (RealSrcSpan rspan) m = Map.findWithDefault [] rspan (parsedAnnotSrcs m)+lookupAnnotation (UnhelpfulSpan _) _ = []++-- | Query the module AST using @f@+queryModule :: Typeable a => (a -> [b]) -> Module -> [b]+queryModule f = everything (++) (mkQ [] f) . parsedModule++--------------------------------------------------------------------------------+-- | Getter for internal components in imports newtype+rawImport :: Import -> ImportDecl GhcPs+rawImport (Import i) = i++-- | Getter for internal module name representation+rawModuleName :: ModuleHeader -> Maybe (GHC.Located GHC.ModuleName)+rawModuleName = name++-- | Getter for internal module exports representation+rawModuleExports :: ModuleHeader -> Maybe (GHC.Located [GHC.LIE GhcPs])+rawModuleExports = exports++-- | Getter for internal module haddocks representation+rawModuleHaddocks :: ModuleHeader -> Maybe GHC.LHsDocString+rawModuleHaddocks = haddocks++-- | Getter for internal module decls representation+rawModuleDecls :: Decls -> [LHsDecl GhcPs]+rawModuleDecls (Decls xs) = xs++-- | Getter for internal module comments representation+rawComments :: Comments -> [GHC.RealLocated GHC.AnnotationComment]+rawComments (Comments xs) = xs++-- | Getter for internal module annotation representation+rawModuleAnnotations :: Module -> [(GHC.ApiAnnKey, [GHC.SrcSpan])]+rawModuleAnnotations = parsedAnnotations
+ lib/Language/Haskell/Stylish/Ordering.hs view
@@ -0,0 +1,61 @@+--------------------------------------------------------------------------------+-- | There are a number of steps that sort items: 'Imports' and 'ModuleHeader',+-- and maybe more in the future.  This module provides consistent sorting+-- utilities.+{-# LANGUAGE LambdaCase #-}+module Language.Haskell.Stylish.Ordering+    ( compareLIE+    , compareWrappedName+    , unwrapName+    ) where+++--------------------------------------------------------------------------------+import           Data.Char                    (isUpper)+import           Data.Ord                     (comparing)+import           GHC.Hs+import           RdrName                      (RdrName)+import           SrcLoc                       (unLoc)+++--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.GHC (showOutputable)+import           Outputable                   (Outputable)+++--------------------------------------------------------------------------------+-- | NOTE: Can we get rid off this by adding a properly sorting newtype around+-- 'RdrName'?+compareLIE :: LIE GhcPs -> LIE GhcPs -> Ordering+compareLIE = comparing $ ieKey . unLoc+  where+    -- | The implementation is a bit hacky to get proper sorting for input specs:+    -- constructors first, followed by functions, and then operators.+    ieKey :: IE GhcPs -> (Int, String)+    ieKey = \case+        IEVar _ n             -> nameKey n+        IEThingAbs _ n        -> nameKey n+        IEThingAll _ n        -> nameKey n+        IEThingWith _ n _ _ _ -> nameKey n+        IEModuleContents _ n  -> nameKey n+        _                     -> (2, "")+++--------------------------------------------------------------------------------+compareWrappedName :: IEWrappedName RdrName -> IEWrappedName RdrName -> Ordering+compareWrappedName = comparing nameKey+++--------------------------------------------------------------------------------+unwrapName :: IEWrappedName n -> n+unwrapName (IEName n)    = unLoc n+unwrapName (IEPattern n) = unLoc n+unwrapName (IEType n)    = unLoc n+++--------------------------------------------------------------------------------+nameKey :: Outputable name => name -> (Int, String)+nameKey n = case showOutputable n of+    o@('(' : _)             -> (2, o)+    o@(o0 : _) | isUpper o0 -> (0, o)+    o                       -> (1, o)
lib/Language/Haskell/Stylish/Parse.hs view
@@ -1,35 +1,39 @@+{-# LANGUAGE LambdaCase #-} -------------------------------------------------------------------------------- module Language.Haskell.Stylish.Parse-    ( parseModule-    ) where+  ( parseModule+  ) where   ---------------------------------------------------------------------------------import           Data.List                       (isPrefixOf, nub)+import           Data.Function                   ((&)) import           Data.Maybe                      (fromMaybe, listToMaybe)-import qualified Language.Haskell.Exts           as H-+import           System.IO.Unsafe                (unsafePerformIO)  ---------------------------------------------------------------------------------import           Language.Haskell.Stylish.Config-import           Language.Haskell.Stylish.Step-+import           Bag                             (bagToList)+import qualified DynFlags                        as GHC+import qualified ErrUtils                        as GHC+import           FastString                      (mkFastString)+import qualified GHC.Hs                          as GHC+import qualified GHC.LanguageExtensions          as GHC+import qualified HeaderInfo                      as GHC+import qualified HscTypes                        as GHC+import           Lexer                           (ParseResult (..))+import           Lexer                           (mkPState, unP)+import qualified Lexer                           as GHC+import qualified Panic                           as GHC+import qualified Parser                          as GHC+import           SrcLoc                          (mkRealSrcLoc)+import qualified SrcLoc                          as GHC+import           StringBuffer                    (stringToStringBuffer)+import qualified StringBuffer                    as GHC  ----------------------------------------------------------------------------------- | Syntax-related language extensions are always enabled for parsing. Since we--- can't authoritatively know which extensions are enabled at compile-time, we--- should try not to throw errors when parsing any GHC-accepted code.-defaultExtensions :: [H.Extension]-defaultExtensions = map H.EnableExtension-  [ H.GADTs-  , H.HereDocuments-  , H.KindSignatures-  , H.NewQualifiedOperators-  , H.PatternGuards-  , H.StandaloneDeriving-  , H.UnicodeSyntax-  ]+import           Language.Haskell.Stylish.GHC    (baseDynFlags)+import           Language.Haskell.Stylish.Module +type Extensions = [String]  -------------------------------------------------------------------------------- -- | Filter out lines which use CPP macros@@ -42,16 +46,7 @@             nextMultiline = isCpp && not (null x) && last x == '\\'         in (if isCpp then "" else x) : go nextMultiline xs - ----------------------------------------------------------------------------------- | Remove shebang lines-unShebang :: String -> String-unShebang str =-    let (shebangs, other) = break (not . ("#!" `isPrefixOf`)) (lines str) in-    unlines $ map (const "") shebangs ++ other----------------------------------------------------------------------------------- -- | If the given string is prefixed with an UTF-8 Byte Order Mark, drop it -- because haskell-src-exts can't handle it. dropBom :: String -> String@@ -60,32 +55,69 @@   ----------------------------------------------------------------------------------- | Abstraction over HSE's parsing+-- | Abstraction over GHC lib's parsing parseModule :: Extensions -> Maybe FilePath -> String -> Either String Module-parseModule extraExts mfp string = do-    -- Determine the extensions: those specified in the file and the extra ones-    let noPrefixes       = unShebang . dropBom $ string-        extraExts'       = map H.classifyExtension extraExts-        (lang, fileExts) = fromMaybe (Nothing, []) $ H.readExtensions noPrefixes-        exts             = nub $ fileExts ++ extraExts' ++ defaultExtensions+parseModule exts fp string =+  parsePragmasIntoDynFlags baseDynFlags userExtensions filePath string >>= \dynFlags ->+    dropBom string+      & removeCpp dynFlags+      & runParser dynFlags+      & toModule dynFlags+  where+    toModule :: GHC.DynFlags -> GHC.ParseResult (GHC.Located (GHC.HsModule GHC.GhcPs)) -> Either String Module+    toModule dynFlags res = case res of+      POk ps m ->+        Right (makeModule ps m)+      PFailed failureState ->+        let+          withFileName x = maybe "" (<> ": ") fp <> x+        in+        Left . withFileName . unlines . getParserStateErrors dynFlags $ failureState -        -- Parsing options...-        fp       = fromMaybe "<unknown>" mfp-        mode     = H.defaultParseMode-            { H.extensions   = exts-            , H.fixities     = Nothing-            , H.baseLanguage = case lang of-                Nothing -> H.baseLanguage H.defaultParseMode-                Just l  -> l-            }+    removeCpp dynFlags s =+      if GHC.xopt GHC.Cpp dynFlags then unCpp s+      else s -        -- Preprocessing-        processed = if H.EnableExtension H.CPP `elem` exts-                       then unCpp noPrefixes-                       else noPrefixes+    userExtensions =+      fmap toLocatedExtensionFlag ("Haskell2010" : exts) -- FIXME: do we need `Haskell2010` here? -    case H.parseModuleWithComments mode processed of-        H.ParseOk md -> return md-        err          -> Left $-            "Language.Haskell.Stylish.Parse.parseModule: could not parse " ++-            fp ++ ": " ++ show err+    toLocatedExtensionFlag flag+      = "-X" <> flag+      & GHC.L GHC.noSrcSpan++    getParserStateErrors dynFlags state+      = GHC.getErrorMessages state dynFlags+      & bagToList+      & fmap (\errMsg -> show (GHC.errMsgSpan errMsg) <> ": " <> show errMsg)++    filePath =+      fromMaybe "<interactive>" fp++    runParser :: GHC.DynFlags -> String -> GHC.ParseResult (GHC.Located (GHC.HsModule GHC.GhcPs))+    runParser flags str =+      let+        filename = mkFastString filePath+        parseState = mkPState flags (stringToStringBuffer str) (mkRealSrcLoc filename 1 1)+      in+        unP GHC.parseModule parseState++-- | Parse 'DynFlags' from the extra options+--+--   /Note:/ this function would be IO, but we're not using any of the internal+--   features that constitute side effectful computation. So I think it's fine+--   if we run this to avoid changing the interface too much.+parsePragmasIntoDynFlags ::+     GHC.DynFlags+  -> [GHC.Located String]+  -> FilePath+  -> String+  -> Either String GHC.DynFlags+{-# NOINLINE parsePragmasIntoDynFlags #-}+parsePragmasIntoDynFlags originalFlags extraOpts filepath str = unsafePerformIO $ catchErrors $ do+  let opts = GHC.getOptions originalFlags (GHC.stringToStringBuffer str) filepath+  (parsedFlags, _invalidFlags, _warnings) <- GHC.parseDynamicFilePragma originalFlags (opts <> extraOpts)+  -- FIXME: have a look at 'leftovers' since it should be empty+  return $ Right $ parsedFlags `GHC.gopt_set` GHC.Opt_KeepRawTokenStream+  where+    catchErrors act = GHC.handleGhcException reportErr (GHC.handleSourceError reportErr act)+    reportErr e = return $ Left (show e)
+ lib/Language/Haskell/Stylish/Printer.hs view
@@ -0,0 +1,450 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+module Language.Haskell.Stylish.Printer+  ( Printer(..)+  , PrinterConfig(..)+  , PrinterState(..)++    -- * Alias+  , P++    -- * Functions to use the printer+  , runPrinter+  , runPrinter_++    -- ** Combinators+  , comma+  , dot+  , getAnnot+  , getCurrentLine+  , getCurrentLineLength+  , getDocstrPrev+  , newline+  , parenthesize+  , peekNextCommentPos+  , prefix+  , putComment+  , putEolComment+  , putOutputable+  , putAllSpanComments+  , putCond+  , putType+  , putRdrName+  , putText+  , removeCommentTo+  , removeCommentToEnd+  , removeLineComment+  , sep+  , groupAttachedComments+  , space+  , spaces+  , suffix++    -- ** Advanced combinators+  , withColumns+  , modifyCurrentLine+  , wrapping+  ) where++--------------------------------------------------------------------------------+import           Prelude                         hiding (lines)++--------------------------------------------------------------------------------+import           ApiAnnotation                   (AnnKeywordId(..), AnnotationComment(..))+import           GHC.Hs.Extension                (GhcPs, NoExtField(..))+import           GHC.Hs.Types                    (HsType(..))+import           Module                          (ModuleName, moduleNameString)+import           RdrName                         (RdrName(..))+import           SrcLoc                          (GenLocated(..), RealLocated)+import           SrcLoc                          (Located, SrcSpan(..))+import           SrcLoc                          (srcSpanStartLine, srcSpanEndLine)+import           Outputable                      (Outputable)++--------------------------------------------------------------------------------+import           Control.Monad                   (forM_, replicateM_)+import           Control.Monad.Reader            (MonadReader, ReaderT(..), asks, local)+import           Control.Monad.State             (MonadState, State)+import           Control.Monad.State             (runState)+import           Control.Monad.State             (get, gets, modify, put)+import           Data.Foldable                   (find)+import           Data.Functor                    ((<&>))+import           Data.List                       (delete, isPrefixOf)+import           Data.List.NonEmpty              (NonEmpty(..))++--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.Module (Module, Lines, lookupAnnotation)+import           Language.Haskell.Stylish.GHC    (showOutputable, unLocated)++-- | Shorthand for 'Printer' monad+type P = Printer++-- | Printer that keeps state of file+newtype Printer a = Printer (ReaderT PrinterConfig (State PrinterState) a)+  deriving (Applicative, Functor, Monad, MonadReader PrinterConfig, MonadState PrinterState)++-- | Configuration for printer, currently empty+data PrinterConfig = PrinterConfig+    { columns :: !(Maybe Int)+    }++-- | State of printer+data PrinterState = PrinterState+  { lines :: !Lines+  , linePos :: !Int+  , currentLine :: !String+  , pendingComments :: ![RealLocated AnnotationComment]+  , parsedModule :: !Module+  }++-- | Run printer to get printed lines out of module as well as return value of monad+runPrinter :: PrinterConfig -> [RealLocated AnnotationComment] -> Module -> Printer a -> (a, Lines)+runPrinter cfg comments m (Printer printer) =+  let+    (a, PrinterState parsedLines _ startedLine _ _) = runReaderT printer cfg `runState` PrinterState [] 0 "" comments m+  in+    (a, parsedLines <> if startedLine == [] then [] else [startedLine])++-- | Run printer to get printed lines only+runPrinter_ :: PrinterConfig -> [RealLocated AnnotationComment] -> Module -> Printer a -> Lines+runPrinter_ cfg comments m printer = snd (runPrinter cfg comments m printer)++-- | Print text+putText :: String -> P ()+putText txt = do+  l <- gets currentLine+  modify \s -> s { currentLine = l <> txt }++-- | Check condition post action, and use fallback if false+putCond :: (PrinterState -> Bool) -> P b -> P b -> P b+putCond p action fallback = do+  prevState <- get+  res <- action+  currState <- get+  if p currState then pure res+  else put prevState >> fallback++-- | Print an 'Outputable'+putOutputable :: Outputable a => a -> P ()+putOutputable = putText . showOutputable++-- | Put all comments that has positions within 'SrcSpan' and separate by+--   passed @P ()@+putAllSpanComments :: P () -> SrcSpan -> P ()+putAllSpanComments suff = \case+  UnhelpfulSpan _ -> pure ()+  RealSrcSpan rspan -> do+    cmts <- removeComments \(L rloc _) ->+      srcSpanStartLine rloc >= srcSpanStartLine rspan &&+      srcSpanEndLine rloc <= srcSpanEndLine rspan++    forM_ cmts (\c -> putComment c >> suff)++-- | Print any comment+putComment :: AnnotationComment -> P ()+putComment = \case+  AnnLineComment s -> putText s+  AnnDocCommentNext s -> putText s+  AnnDocCommentPrev s -> putText s+  AnnDocCommentNamed s -> putText s+  AnnDocSection _ s -> putText s+  AnnDocOptions s -> putText s+  AnnBlockComment s -> putText s++-- | Given the current start line of 'SrcSpan', remove and put EOL comment for same line+putEolComment :: SrcSpan -> P ()+putEolComment = \case+  RealSrcSpan rspan -> do+    cmt <- removeComment \case+      L rloc (AnnLineComment s) ->+        and+          [ srcSpanStartLine rspan == srcSpanStartLine rloc+          , not ("-- ^" `isPrefixOf` s)+          , not ("-- |" `isPrefixOf` s)+          ]+      _ -> False+    forM_ cmt (\c -> space >> putComment c)+  UnhelpfulSpan _ -> pure ()++-- | Print a 'RdrName'+putRdrName :: Located RdrName -> P ()+putRdrName (L pos n) = case n of+  Unqual name -> do+    annots <- getAnnot pos+    if AnnOpenP `elem` annots then do+      putText "("+      putText (showOutputable name)+      putText ")"+    else if AnnBackquote `elem` annots then do+      putText "`"+      putText (showOutputable name)+      putText "`"+    else if AnnSimpleQuote `elem` annots then do+      putText "'"+      putText (showOutputable name)+    else+      putText (showOutputable name)+  Qual modulePrefix name ->+    putModuleName modulePrefix >> dot >> putText (showOutputable name)+  Orig _ name ->+    putText (showOutputable name)+  Exact name ->+    putText (showOutputable name)++-- | Print module name+putModuleName :: ModuleName -> P ()+putModuleName = putText . moduleNameString++-- | Print type+putType :: Located (HsType GhcPs) -> P ()+putType ltp = case unLocated ltp of+  HsFunTy NoExtField argTp funTp -> do+    putOutputable argTp+    space+    putText "->"+    space+    putType funTp+  HsAppTy NoExtField t1 t2 ->+    putType t1 >> space >> putType t2+  HsExplicitListTy NoExtField _ xs -> do+    putText "'["+    sep+      (comma >> space)+      (fmap putType xs)+    putText "]"+  HsExplicitTupleTy NoExtField xs -> do+    putText "'("+    sep+      (comma >> space)+      (fmap putType xs)+    putText ")"+  HsOpTy NoExtField lhs op rhs -> do+    putType lhs+    space+    putRdrName op+    space+    putType rhs+  HsTyVar NoExtField _ rdrName ->+    putRdrName rdrName+  HsTyLit _ tp ->+    putOutputable tp+  HsParTy _ tp -> do+    putText "("+    putType tp+    putText ")"+  HsTupleTy NoExtField _ xs -> do+    putText "("+    sep+      (comma >> space)+      (fmap putType xs)+    putText ")"+  HsForAllTy NoExtField _ _ _ ->+    putOutputable ltp+  HsQualTy NoExtField _ _ ->+    putOutputable ltp+  HsAppKindTy _ _ _ ->+    putOutputable ltp+  HsListTy _ _ ->+    putOutputable ltp+  HsSumTy _ _ ->+    putOutputable ltp+  HsIParamTy _ _ _ ->+    putOutputable ltp+  HsKindSig _ _ _ ->+    putOutputable ltp+  HsStarTy _ _ ->+    putOutputable ltp+  HsSpliceTy _ _ ->+    putOutputable ltp+  HsDocTy _ _ _ ->+    putOutputable ltp+  HsBangTy _ _ _ ->+    putOutputable ltp+  HsRecTy _ _ ->+    putOutputable ltp+  HsWildCardTy _ ->+    putOutputable ltp+  XHsType _ ->+    putOutputable ltp++-- | Get a docstring on the start line of 'SrcSpan' that is a @-- ^@ comment+getDocstrPrev :: SrcSpan -> P (Maybe AnnotationComment)+getDocstrPrev = \case+  UnhelpfulSpan _ -> pure Nothing+  RealSrcSpan rspan -> do+    removeComment \case+      L rloc (AnnLineComment s) ->+        and+          [ srcSpanStartLine rspan == srcSpanStartLine rloc+          , "-- ^" `isPrefixOf` s+          ]+      _ -> False++-- | Print a newline+newline :: P ()+newline = do+  l <- gets currentLine+  modify \s -> s { currentLine = "", linePos = 0, lines = lines s <> [l] }++-- | Print a space+space :: P ()+space = putText " "++-- | Print a number of spaces+spaces :: Int -> P ()+spaces i = replicateM_ i space++-- | Print a dot+dot :: P ()+dot = putText "."++-- | Print a comma+comma :: P ()+comma = putText ","++-- | Add parens around a printed action+parenthesize :: P a -> P a+parenthesize action = putText "(" *> action <* putText ")"++-- | Add separator between each element of the given printers+sep :: P a -> [P a] -> P ()+sep _ [] = pure ()+sep s (first : rest) = first >> forM_ rest ((>>) s)++-- | Prefix a printer with another one+prefix :: P a -> P b -> P b+prefix pa pb = pa >> pb++-- | Suffix a printer with another one+suffix :: P a -> P b -> P a+suffix pa pb = pb >> pa++-- | Gets comment on supplied 'line' and removes it from the state+removeLineComment :: Int -> P (Maybe AnnotationComment)+removeLineComment line =+  removeComment (\(L rloc _) -> srcSpanStartLine rloc == line)++-- | Removes comments from the state up to start line of 'SrcSpan' and returns+--   the ones that were removed+removeCommentTo :: SrcSpan -> P [AnnotationComment]+removeCommentTo = \case+  UnhelpfulSpan _ -> pure []+  RealSrcSpan rspan -> removeCommentTo' (srcSpanStartLine rspan)++-- | Removes comments from the state up to end line of 'SrcSpan' and returns+--   the ones that were removed+removeCommentToEnd :: SrcSpan -> P [AnnotationComment]+removeCommentToEnd = \case+  UnhelpfulSpan _ -> pure []+  RealSrcSpan rspan -> removeCommentTo' (srcSpanEndLine rspan)++-- | Removes comments to the line number given and returns the ones removed+removeCommentTo' :: Int -> P [AnnotationComment]+removeCommentTo' line =+  removeComment (\(L rloc _) -> srcSpanStartLine rloc < line) >>= \case+    Nothing -> pure []+    Just c -> do+      rest <- removeCommentTo' line+      pure (c : rest)++-- | Removes comments from the state while given predicate 'p' is true+removeComments :: (RealLocated AnnotationComment -> Bool) -> P [AnnotationComment]+removeComments p =+  removeComment p >>= \case+    Just c -> do+      rest <- removeComments p+      pure (c : rest)+    Nothing -> pure []++-- | Remove a comment from the state given predicate 'p'+removeComment :: (RealLocated AnnotationComment -> Bool) -> P (Maybe AnnotationComment)+removeComment p = do+  comments <- gets pendingComments++  let+    foundComment =+      find p comments++    newPendingComments =+      maybe comments (`delete` comments) foundComment++  modify \s -> s { pendingComments = newPendingComments }+  pure $ fmap (\(L _ c) -> c) foundComment++-- | Get all annotations for 'SrcSpan'+getAnnot :: SrcSpan -> P [AnnKeywordId]+getAnnot spn = gets (lookupAnnotation spn . parsedModule)++-- | Get current line+getCurrentLine :: P String+getCurrentLine = gets currentLine++-- | Get current line length+getCurrentLineLength :: P Int+getCurrentLineLength = fmap length getCurrentLine++-- | Peek at the next comment in the state+peekNextCommentPos :: P (Maybe SrcSpan)+peekNextCommentPos = do+  gets pendingComments <&> \case+    (L next _ : _) -> Just (RealSrcSpan next)+    [] -> Nothing++-- | Get attached comments belonging to '[Located a]' given+groupAttachedComments :: [Located a] -> P [([AnnotationComment], NonEmpty (Located a))]+groupAttachedComments = go+  where+    go :: [Located a] -> P [([AnnotationComment], NonEmpty (Located a))]+    go (L rspan x : xs) = do+      comments <- removeCommentTo rspan+      nextGroupStartM <- peekNextCommentPos++      let+        sameGroupOf = maybe xs \nextGroupStart ->+          takeWhile (\(L p _)-> p < nextGroupStart) xs++        restOf = maybe [] \nextGroupStart ->+          dropWhile (\(L p _) -> p <= nextGroupStart) xs++      restGroups <- go (restOf nextGroupStartM)+      pure $ (comments, L rspan x :| sameGroupOf nextGroupStartM) : restGroups++    go _ = pure []++modifyCurrentLine :: (String -> String) -> P ()+modifyCurrentLine f = do+    s0 <- get+    put s0 {currentLine = f $ currentLine s0}++wrapping+    :: P a  -- ^ First printer to run+    -> P a  -- ^ Printer to run if first printer violates max columns+    -> P a  -- ^ Result of either the first or the second printer+wrapping p1 p2 = do+    maxCols <- asks columns+    case maxCols of+        -- No wrapping+        Nothing -> p1+        Just c  -> do+            s0 <- get+            x <- p1+            s1 <- get+            if length (currentLine s1) <= c+                -- No need to wrap+                then pure x+                else do+                    put s0+                    y <- p2+                    s2 <- get+                    if length (currentLine s1) == length (currentLine s2)+                        -- Wrapping didn't help!+                        then put s1 >> pure x+                        -- Wrapped+                        else pure y++withColumns :: Maybe Int -> P a -> P a+withColumns c = local $ \pc -> pc {columns = c}
lib/Language/Haskell/Stylish/Step.hs view
@@ -1,31 +1,19 @@ -------------------------------------------------------------------------------- module Language.Haskell.Stylish.Step     ( Lines-    , Module     , Step (..)     , makeStep     ) where   ---------------------------------------------------------------------------------import qualified Language.Haskell.Exts as H------------------------------------------------------------------------------------type Lines = [String]-------------------------------------------------------------------------------------- | Concrete module type-type Module = (H.Module H.SrcSpanInfo, [H.Comment])-+import           Language.Haskell.Stylish.Module  -------------------------------------------------------------------------------- data Step = Step     { stepName   :: String     , stepFilter :: Lines -> Module -> Lines     }-  -------------------------------------------------------------------------------- makeStep :: String -> (Lines -> Module -> Lines) -> Step
lib/Language/Haskell/Stylish/Step/Data.hs view
@@ -1,126 +1,518 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-}+module Language.Haskell.Stylish.Step.Data+  ( Config(..)+  , Indent(..)+  , MaxColumns(..)+  , step+  ) where -module Language.Haskell.Stylish.Step.Data where+--------------------------------------------------------------------------------+import           Prelude                          hiding (init) -import           Data.List                       (find, intercalate)-import           Data.Maybe                      (fromMaybe, maybeToList)-import qualified Language.Haskell.Exts           as H-import           Language.Haskell.Exts.Comments+--------------------------------------------------------------------------------+import           Control.Monad                    (forM_, unless, when)+import           Data.Function                    ((&))+import           Data.Functor                     ((<&>))+import           Data.List                        (sortBy)+import           Data.Maybe                       (listToMaybe)++--------------------------------------------------------------------------------+import           ApiAnnotation                    (AnnotationComment)+import           BasicTypes                       (LexicalFixity(..))+import           GHC.Hs.Decls                     (HsDecl(..), HsDataDefn(..))+import           GHC.Hs.Decls                     (TyClDecl(..), NewOrData(..))+import           GHC.Hs.Decls                     (HsDerivingClause(..), DerivStrategy(..))+import           GHC.Hs.Decls                     (ConDecl(..))+import           GHC.Hs.Extension                 (GhcPs, NoExtField(..), noExtCon)+import           GHC.Hs.Types                     (ConDeclField(..), HsContext)+import           GHC.Hs.Types                     (HsType(..), ForallVisFlag(..))+import           GHC.Hs.Types                     (LHsQTyVars(..), HsTyVarBndr(..))+import           GHC.Hs.Types                     (HsConDetails(..), HsImplicitBndrs(..))+import           RdrName                          (RdrName)+import           SrcLoc                           (Located, RealLocated)+import           SrcLoc                           (GenLocated(..))++-------------------------------------------------------------------------------- import           Language.Haskell.Stylish.Block import           Language.Haskell.Stylish.Editor+import           Language.Haskell.Stylish.GHC+import           Language.Haskell.Stylish.Module+import           Language.Haskell.Stylish.Printer import           Language.Haskell.Stylish.Step-import           Language.Haskell.Stylish.Util-import           Prelude                         hiding (init)  data Indent     = SameLine     | Indent !Int-  deriving (Show)+  deriving (Show, Eq) +data MaxColumns+  = MaxColumns !Int+  | NoMaxColumns+  deriving (Show, Eq)+ data Config = Config-    { cEquals           :: !Indent+    { cEquals                  :: !Indent       -- ^ Indent between type constructor and @=@ sign (measured from column 0)-    , cFirstField       :: !Indent+    , cFirstField              :: !Indent       -- ^ Indent between data constructor and @{@ line (measured from column with data constructor name)-    , cFieldComment     :: !Int+    , cFieldComment            :: !Int       -- ^ Indent between column with @{@ and start of field line comment (this line has @cFieldComment = 2@)-    , cDeriving         :: !Int+    , cDeriving                :: !Int       -- ^ Indent before @deriving@ lines (measured from column 0)+    , cBreakEnums              :: !Bool+      -- ^ Break enums by newlines and follow the above rules+    , cBreakSingleConstructors :: !Bool+      -- ^ Break single constructors when enabled, e.g. @Indent 2@ will not cause newline after @=@+    , cVia                     :: !Indent+      -- ^ Indentation between @via@ clause and start of deriving column start+    , cCurriedContext          :: !Bool+      -- ^ If true, use curried context. E.g: @allValues :: Enum a => Bounded a => Proxy a -> [a]@+    , cMaxColumns              :: !MaxColumns     } deriving (Show) -datas :: H.Module l -> [H.Decl l]-datas (H.Module _ _ _ _ decls) = decls-datas _                        = []+step :: Config -> Step+step cfg = makeStep "Data" \ls m -> applyChanges (changes m) ls+  where+    changes :: Module -> [ChangeLine]+    changes m = fmap (formatDataDecl cfg m) (dataDecls m) -type ChangeLine = Change String+    dataDecls :: Module -> [Located DataDecl]+    dataDecls = queryModule \case+      L pos (TyClD _ (DataDecl _ name tvars fixity defn)) -> pure . L pos $ MkDataDecl+        { dataDeclName = name+        , dataTypeVars = tvars+        , dataDefn = defn+        , dataFixity = fixity+        }+      _ -> [] -step :: Config -> Step-step cfg = makeStep "Data" (step' cfg)+type ChangeLine = Change String -step' :: Config -> Lines -> Module -> Lines-step' cfg ls (module', allComments) = applyChanges changes ls+formatDataDecl :: Config -> Module -> Located DataDecl -> ChangeLine+formatDataDecl cfg@Config{..} m ldecl@(L declPos decl) =+  change originalDeclBlock (const printedDecl)   where-    datas' = datas $ fmap linesFromSrcSpan module'-    changes = datas' >>= maybeToList . changeDecl allComments cfg+    relevantComments :: [RealLocated AnnotationComment]+    relevantComments+      = moduleComments m+      & rawComments+      & dropBeforeAndAfter ldecl -findCommentOnLine :: LineBlock -> [Comment] -> Maybe Comment-findCommentOnLine lb = find commentOnLine-  where-    commentOnLine (Comment _ (H.SrcSpan _ start _ end _) _) =-      blockStart lb == start && blockEnd lb == end+    defn = dataDefn decl -findCommentBelowLine :: LineBlock -> [Comment] -> Maybe Comment-findCommentBelowLine lb = find commentOnLine-  where-    commentOnLine (Comment _ (H.SrcSpan _ start _ end _) _) =-      blockStart lb == start - 1 && blockEnd lb == end - 1+    originalDeclBlock =+      Block (getStartLineUnsafe ldecl) (getEndLineUnsafe ldecl) -commentsWithin :: LineBlock -> [Comment] -> [Comment]-commentsWithin lb = filter within+    printerConfig = PrinterConfig+      { columns = case cMaxColumns of+          NoMaxColumns -> Nothing+          MaxColumns n -> Just n+      }++    printedDecl = runPrinter_ printerConfig relevantComments m do+      putText (newOrData decl)+      space+      putName decl++      when (isGADT decl) (space >> putText "where")++      when (hasConstructors decl) do+        breakLineBeforeEq <- case (cEquals, cFirstField) of+          (_, Indent x) | isEnum decl && cBreakEnums -> do+            putEolComment declPos+            newline >> spaces x+            pure True+          (_, _) | not (isNewtype decl) && singleConstructor decl && not cBreakSingleConstructors ->+            False <$ space+          (Indent x, _)+            | isEnum decl && not cBreakEnums -> False <$ space+            | otherwise -> do+              putEolComment declPos+              newline >> spaces x+              pure True+          (SameLine, _) -> False <$ space++        lineLengthAfterEq <- fmap (+2) getCurrentLineLength++        if isEnum decl && not cBreakEnums then+          putText "=" >> space >> putUnbrokenEnum cfg decl+        else if isNewtype decl then+          putText "=" >> space >> forM_ (dd_cons defn) (putNewtypeConstructor cfg)+        else+          case dd_cons defn of+            [] -> pure ()+            lcon@(L pos _) : consRest -> do+              when breakLineBeforeEq do+                removeCommentTo pos >>= mapM_ \c -> putComment c >> consIndent lineLengthAfterEq++              unless+                (isGADT decl)+                (putText "=" >> space)++              putConstructor cfg lineLengthAfterEq lcon+              forM_ consRest \con@(L conPos _) -> do+                unless (cFirstField == SameLine) do+                  removeCommentTo conPos >>= mapM_ \c -> consIndent lineLengthAfterEq >> putComment c+                consIndent lineLengthAfterEq++                unless+                  (isGADT decl)+                  (putText "|" >> space)++                putConstructor cfg lineLengthAfterEq con+                putEolComment conPos++        when (hasDeriving decl) do+          if isEnum decl && not cBreakEnums then+            space+          else do+            removeCommentTo (defn & dd_derivs & \(L pos _) -> pos) >>=+              mapM_ \c -> newline >> spaces cDeriving >> putComment c+            newline+            spaces cDeriving++        sep (newline >> spaces cDeriving) $ defn & dd_derivs & \(L pos ds) -> ds <&> \d -> do+          putAllSpanComments (newline >> spaces cDeriving) pos+          putDeriving cfg d++    consIndent eqIndent = newline >> case (cEquals, cFirstField) of+      (SameLine, SameLine) -> spaces (eqIndent - 2)+      (SameLine, Indent y) -> spaces (eqIndent + y - 4)+      (Indent x, Indent _) -> spaces x+      (Indent x, SameLine) -> spaces x++data DataDecl = MkDataDecl+  { dataDeclName :: Located RdrName+  , dataTypeVars :: LHsQTyVars GhcPs+  , dataDefn :: HsDataDefn GhcPs+  , dataFixity :: LexicalFixity+  }++putDeriving :: Config -> Located (HsDerivingClause GhcPs) -> P ()+putDeriving Config{..} (L pos clause) = do+  putText "deriving"++  forM_ (deriv_clause_strategy clause) \case+    L _ StockStrategy -> space >> putText "stock"+    L _ AnyclassStrategy -> space >> putText "anyclass"+    L _ NewtypeStrategy -> space >> putText "newtype"+    L _ (ViaStrategy _) -> pure ()++  putCond+    withinColumns+    oneLinePrint+    multilinePrint++  forM_ (deriv_clause_strategy clause) \case+    L _ (ViaStrategy tp) -> do+      case cVia of+        SameLine -> space+        Indent x -> newline >> spaces (x + cDeriving)++      putText "via"+      space+      putType (getType tp)+    _ -> pure ()++  putEolComment pos+   where-    within (Comment _ (H.SrcSpan _ start _ end _) _) =-      start >= blockStart lb && end <= blockEnd lb+    getType = \case+      HsIB _ tp -> tp+      XHsImplicitBndrs x -> noExtCon x -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+    withinColumns PrinterState{currentLine} =+      case cMaxColumns of+        MaxColumns maxCols -> length currentLine <= maxCols+        NoMaxColumns -> True++    oneLinePrint = do+      space+      putText "("+      sep+        (comma >> space)+        (fmap putOutputable tys)+      putText ")"++    multilinePrint = do+      newline+      spaces indentation+      putText "("++      forM_ headTy \t ->+        space >> putOutputable t++      forM_ tailTy \t -> do+        newline+        spaces indentation+        comma+        space+        putOutputable t++      newline+      spaces indentation+      putText ")"++    indentation =+      cDeriving + case cFirstField of+        Indent x -> x+        SameLine -> 0++    tys+      = clause+      & deriv_clause_tys+      & unLocated+      & sortBy compareOutputable+      & fmap hsib_body++    headTy =+      listToMaybe tys++    tailTy =+      drop 1 tys++putUnbrokenEnum :: Config -> DataDecl -> P ()+putUnbrokenEnum cfg decl =+  sep+    (space >> putText "|" >> space)+    (fmap (putConstructor cfg 0) . dd_cons . dataDefn $ decl)++putName :: DataDecl -> P ()+putName decl@MkDataDecl{..} =+  if isInfix decl then do+    forM_ firstTvar (\t -> putOutputable t >> space)+    putRdrName dataDeclName+    space+    forM_ secondTvar putOutputable+  else do+    putRdrName dataDeclName+    forM_ (hsq_explicit dataTypeVars) (\t -> space >> putOutputable t)+   where-    hasRecordFields = any-      (\qual -> case qual of-                  (H.QualConDecl _ _ _ (H.RecDecl {})) -> True-                  _ -> False)-      decls+    firstTvar :: Maybe (Located (HsTyVarBndr GhcPs))+    firstTvar+      = dataTypeVars+      & hsq_explicit+      & listToMaybe -    typeConstructor = "data " <> H.prettyPrint dhead+    secondTvar :: Maybe (Located (HsTyVarBndr GhcPs))+    secondTvar+      = dataTypeVars+      & hsq_explicit+      & drop 1+      & listToMaybe -    -- 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)+putConstructor :: Config -> Int -> Located (ConDecl GhcPs) -> P ()+putConstructor cfg consIndent (L _ cons) = case cons of+  ConDeclGADT{..} -> do+    -- Put argument to constructor first:+    case con_args of+      PrefixCon _ -> do+        sep+          (comma >> space)+          (fmap putRdrName con_names) -    newLines = fromMaybe [] firstLine ++ fmap constructors zipped <> [fmap (indent cDeriving . H.prettyPrint) derivings]-    zipped = zip decls ([1..] ::[Int])+      InfixCon arg1 arg2 -> do+        putType arg1+        space+        forM_ con_names putRdrName+        space+        putType arg2+      RecCon _ ->+        error . mconcat $+          [ "Language.Haskell.Stylish.Step.Data.putConstructor: "+          , "encountered a GADT with record constructors, not supported yet"+          ] -    constructors (decl, 1) = processConstructor allComments firstLineInit cfg decl-    constructors (decl, _) = processConstructor allComments (indent pipeIndent "| ") cfg decl-changeDecl _ _ _ = Nothing+    -- Put type of constructor:+    space+    putText "::"+    space -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 "}"]+    when (unLocated con_forall) do+      putText "forall"+      space+      sep space (fmap putOutputable $ hsq_explicit con_qvars)+      dot+      space++    forM_ con_mb_cxt (putContext cfg . unLocated)+    putType con_res_ty++  XConDecl x ->+    noExtCon x+  ConDeclH98{..} ->+    case con_args of+      InfixCon arg1 arg2 -> do+        putType arg1+        space+        putRdrName con_name+        space+        putType arg2+      PrefixCon xs -> do+        putRdrName con_name+        unless (null xs) space+        sep space (fmap putOutputable xs)+      RecCon (L recPos (L posFirst firstArg : args)) -> do+        putRdrName con_name+        skipToBrace >> putText "{"+        bracePos <- getCurrentLineLength+        space++        -- Unless everything's configured to be on the same line, put pending+        -- comments+        unless (cFirstField cfg == SameLine) do+          removeCommentTo posFirst >>= mapM_ \c -> putComment c >> sepDecl bracePos++        -- Put first decl field+        putConDeclField cfg firstArg+        unless (cFirstField cfg == SameLine) (putEolComment posFirst)++        -- Put tail decl fields+        forM_ args \(L pos arg) -> do+          sepDecl bracePos+          removeCommentTo pos >>= mapM_ \c ->+            spaces (cFieldComment cfg) >> putComment c >> sepDecl bracePos+          comma+          space+          putConDeclField cfg arg+          putEolComment pos++        -- Print docstr after final field+        removeCommentToEnd recPos >>= mapM_ \c ->+          sepDecl bracePos >> spaces (cFieldComment cfg) >> putComment c++        -- Print whitespace to closing brace+        sepDecl bracePos >> putText "}"+      RecCon (L _ []) -> do+        skipToBrace >> putText "{"+        skipToBrace >> putText "}"++    where+      skipToBrace = case (cEquals cfg, cFirstField cfg) of+        (_, Indent y) | not (cBreakSingleConstructors cfg) -> newline >> spaces y+        (SameLine, SameLine) -> space+        (Indent x, Indent y) -> newline >> spaces (x + y + 2)+        (SameLine, Indent y) -> newline >> spaces (consIndent + y)+        (Indent _, SameLine) -> space++      sepDecl bracePos = newline >> spaces case (cEquals cfg, cFirstField cfg) of+        (_, Indent y) | not (cBreakSingleConstructors cfg) -> y+        (SameLine, SameLine) -> bracePos - 1 -- back one from brace pos to place comma+        (Indent x, Indent y) -> x + y + 2+        (SameLine, Indent y) -> bracePos - 1 + y - 2+        (Indent x, SameLine) -> bracePos - 1 + x - 2++putNewtypeConstructor :: Config -> Located (ConDecl GhcPs) -> P ()+putNewtypeConstructor cfg (L _ cons) = case cons of+  ConDeclH98{..} ->+    putRdrName con_name >> case con_args of+      PrefixCon xs -> do+        unless (null xs) space+        sep space (fmap putOutputable xs)+      RecCon (L _ [L _posFirst firstArg]) -> do+        space+        putText "{"+        space+        putConDeclField cfg firstArg+        space+        putText "}"+      RecCon (L _ _args) ->+        error . mconcat $+          [ "Language.Haskell.Stylish.Step.Data.putNewtypeConstructor: "+          , "encountered newtype with several arguments"+          ]+      InfixCon {} ->+        error . mconcat $+          [ "Language.Haskell.Stylish.Step.Data.putNewtypeConstructor: "+          , "infix newtype constructor"+          ]+  XConDecl x ->+    noExtCon x+  ConDeclGADT{} ->+    error . mconcat $+      [ "Language.Haskell.Stylish.Step.Data.putNewtypeConstructor: "+      , "GADT encountered in newtype"+      ]++putContext :: Config -> HsContext GhcPs -> P ()+putContext Config{..} = suffix (space >> putText "=>" >> space) . \case+  [L _ (HsParTy _ tp)] | cCurriedContext ->+    putType tp+  [ctx] ->+    putType ctx+  ctxs | cCurriedContext ->+    sep (space >> putText "=>" >> space) (fmap putType ctxs)+  ctxs ->+    parenthesize $ sep (comma >> space) (fmap putType ctxs)++putConDeclField :: Config -> ConDeclField GhcPs -> P ()+putConDeclField cfg = \case+  ConDeclField{..} -> do+    sep+      (comma >> space)+      (fmap putOutputable cd_fld_names)+    space+    putText "::"+    space+    putType' cfg cd_fld_type+  XConDeclField{} ->+    error . mconcat $+      [ "Language.Haskell.Stylish.Step.Data.putConDeclField: "+      , "XConDeclField encountered"+      ]++-- | A variant of 'putType' that takes 'cCurriedContext' into account+putType' :: Config -> Located (HsType GhcPs) -> P ()+putType' cfg = \case+  L _ (HsForAllTy NoExtField vis bndrs tp) -> do+    putText "forall"+    space+    sep space (fmap putOutputable bndrs)+    putText+      if vis == ForallVis then "->"+      else "."+    space+    putType' cfg tp+  L _ (HsQualTy NoExtField ctx tp) -> do+    putContext cfg (unLocated ctx)+    putType' cfg tp+  other -> putType other++newOrData :: DataDecl -> String+newOrData decl = if isNewtype decl then "newtype" else "data"++isGADT :: DataDecl -> Bool+isGADT = any isGADTCons . dd_cons . dataDefn   where-    n1 = processName firstLinePrefix (extractField f)-    ns = fs >>= processName (indent fieldIndent ", ") . extractField+    isGADTCons = \case+      L _ (ConDeclGADT {}) -> True+      _ -> False -    -- 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-          )+isNewtype :: DataDecl -> Bool+isNewtype = (== NewType) . dd_ND . dataDefn -    processName prefix (fnames, _type, lineComment, commentBelowLine) =-      [prefix <> intercalate ", " (fmap H.prettyPrint fnames) <> " :: " <> H.prettyPrint _type <> addLineComment lineComment-      ] ++ addCommentBelow commentBelowLine+isInfix :: DataDecl -> Bool+isInfix = (== Infix) . dataFixity -    addLineComment (Just (Comment _ _ c)) = " --" <> c-    addLineComment Nothing                = ""+isEnum :: DataDecl -> Bool+isEnum = all isUnary . dd_cons . dataDefn+  where+    isUnary = \case+      L _ (ConDeclH98 {..}) -> case con_args of+        PrefixCon [] -> True+        _ -> False+      _ -> False -    -- Field comment indent is measured from the column with @{@, hence adding of @fieldIndent@ here.-    addCommentBelow Nothing                = []-    addCommentBelow (Just (Comment _ _ c)) = [indent (fieldIndent + cFieldComment) "--" <> c]+hasConstructors :: DataDecl -> Bool+hasConstructors = not . null . dd_cons . dataDefn -    extractField (H.FieldDecl lb names _type) =-      (names, _type, findCommentOnLine lb allComments, findCommentBelowLine lb allComments)+singleConstructor :: DataDecl -> Bool+singleConstructor = (== 1) . length . dd_cons . dataDefn -processConstructor _ init _ decl = [init <> trimLeft (H.prettyPrint decl)]+hasDeriving :: DataDecl -> Bool+hasDeriving = not . null . unLocated . dd_derivs . dataDefn
lib/Language/Haskell/Stylish/Step/Imports.hs view
@@ -1,61 +1,76 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}---------------------------------------------------------------------------------+{-# LANGUAGE BlockArguments  #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE LambdaCase      #-}+{-# LANGUAGE RecordWildCards #-} module Language.Haskell.Stylish.Step.Imports-    ( Options (..)-    , defaultOptions-    , ImportAlign (..)-    , ListAlign (..)-    , LongListAlign (..)-    , EmptyListAlign (..)-    , ListPadding (..)-    , step-    ) where+  ( Options (..)+  , defaultOptions+  , ImportAlign (..)+  , ListAlign (..)+  , LongListAlign (..)+  , EmptyListAlign (..)+  , ListPadding (..)+  , step+  ) where +--------------------------------------------------------------------------------+import           Control.Monad                   (forM_, when, void)+import           Data.Function                   ((&), on)+import           Data.Functor                    (($>))+import           Data.Foldable                   (toList)+import           Data.Maybe                      (isJust)+import           Data.List                       (sortBy)+import           Data.List.NonEmpty              (NonEmpty(..))+import qualified Data.List.NonEmpty              as NonEmpty+import qualified Data.Map                        as Map+import qualified Data.Set                        as Set + ---------------------------------------------------------------------------------import           Control.Arrow                   ((&&&))-import           Control.Monad                   (void)-import qualified Data.Aeson                      as A-import qualified Data.Aeson.Types                as A-import           Data.Char                       (toLower)-import           Data.List                       (intercalate, sortBy)-import qualified Data.Map                        as M-import           Data.Maybe                      (isJust, maybeToList)-import           Data.Ord                        (comparing)-import qualified Data.Set                        as S-import           Data.Semigroup                  (Semigroup ((<>)))-import qualified Language.Haskell.Exts           as H+import           BasicTypes                      (StringLiteral (..),+                                                  SourceText (..))+import qualified FastString                      as FS+import           GHC.Hs.Extension                (GhcPs)+import qualified GHC.Hs.Extension                as GHC+import           GHC.Hs.ImpExp+import           Module                          (moduleNameString)+import           RdrName                         (RdrName)+import           SrcLoc                          (Located, GenLocated(..), unLoc)   -------------------------------------------------------------------------------- import           Language.Haskell.Stylish.Block-import           Language.Haskell.Stylish.Editor+import           Language.Haskell.Stylish.Module+import           Language.Haskell.Stylish.Ordering+import           Language.Haskell.Stylish.Printer import           Language.Haskell.Stylish.Step+import           Language.Haskell.Stylish.Editor+import           Language.Haskell.Stylish.GHC import           Language.Haskell.Stylish.Util + -------------------------------------------------------------------------------- data Options = Options-    { importAlign    :: ImportAlign-    , listAlign      :: ListAlign-    , padModuleNames :: Bool-    , longListAlign  :: LongListAlign-    , emptyListAlign :: EmptyListAlign-    , listPadding    :: ListPadding-    , separateLists  :: Bool-    , spaceSurround  :: Bool+    { importAlign     :: ImportAlign+    , listAlign       :: ListAlign+    , padModuleNames  :: Bool+    , longListAlign   :: LongListAlign+    , emptyListAlign  :: EmptyListAlign+    , listPadding     :: ListPadding+    , separateLists   :: Bool+    , spaceSurround   :: Bool     } deriving (Eq, Show)  defaultOptions :: Options defaultOptions = Options-    { importAlign    = Global-    , listAlign      = AfterAlias-    , padModuleNames = True-    , longListAlign  = Inline-    , emptyListAlign = Inherit-    , listPadding    = LPConstant 4-    , separateLists  = True-    , spaceSurround  = False+    { importAlign     = Global+    , listAlign       = AfterAlias+    , padModuleNames  = True+    , longListAlign   = Inline+    , emptyListAlign  = Inherit+    , listPadding     = LPConstant 4+    , separateLists   = True+    , spaceSurround   = False     }  data ListPadding@@ -75,6 +90,7 @@     | WithModuleName     | WithAlias     | AfterAlias+    | Repeat     deriving (Eq, Show)  data EmptyListAlign@@ -83,375 +99,382 @@     deriving (Eq, Show)  data LongListAlign-    = Inline-    | InlineWithBreak-    | InlineToMultiline-    | Multiline+    = Inline -- inline+    | InlineWithBreak -- new_line+    | InlineToMultiline -- new_line_multiline+    | Multiline -- multiline     deriving (Eq, Show)   --------------------------------------------------------------------------------+step :: Maybe Int -> Options -> Step+step columns = makeStep "Imports (ghc-lib-parser)" . printImports columns -modifyImportSpecs :: ([H.ImportSpec l] -> [H.ImportSpec l])-                  -> H.ImportDecl l -> H.ImportDecl l-modifyImportSpecs f imp = imp {H.importSpecs = f' <$> H.importSpecs imp}++--------------------------------------------------------------------------------+printImports :: Maybe Int -> Options -> Lines -> Module -> Lines+printImports maxCols align ls m = applyChanges changes ls   where-    f' (H.ImportSpecList l h specs) = H.ImportSpecList l h (f specs)+    groups = moduleImportGroups m+    moduleStats = foldMap importStats . fmap unLoc $ concatMap toList groups+    changes = do+        group <- groups+        pure $ formatGroup maxCols align m moduleStats group +formatGroup+    :: Maybe Int -> Options -> Module -> ImportStats+    -> NonEmpty (Located Import) -> Change String+formatGroup maxCols options m moduleStats imports =+    let newLines = formatImports maxCols options m moduleStats imports in+    change (importBlock imports) (const newLines) ----------------------------------------------------------------------------------imports :: H.Module l -> [H.ImportDecl l]-imports (H.Module _ _ _ is _) = is-imports _                     = []+importBlock :: NonEmpty (Located a) -> Block String+importBlock group = Block+    (getStartLineUnsafe $ NonEmpty.head group)+    (getEndLineUnsafe   $ NonEmpty.last group) +formatImports+    :: Maybe Int    -- ^ Max columns.+    -> Options      -- ^ Options.+    -> Module       -- ^ Module.+    -> ImportStats  -- ^ Module stats.+    -> NonEmpty (Located Import) -> Lines+formatImports maxCols options m moduleStats rawGroup =+  runPrinter_ (PrinterConfig maxCols) [] m do+  let +     +    group+      = NonEmpty.sortWith unLocated rawGroup+      & mergeImports ----------------------------------------------------------------------------------importName :: H.ImportDecl l -> String-importName i = let (H.ModuleName _ n) = H.importModule i in n+    unLocatedGroup = fmap unLocated $ toList group -importPackage :: H.ImportDecl l -> Maybe String-importPackage i = H.importPkg i+    align' = importAlign options+    padModuleNames' = padModuleNames options+    padNames = align' /= None && padModuleNames' +    stats = case align' of+        Global -> moduleStats {isAnyQualified = True}+        File   -> moduleStats+        Group  -> foldMap importStats unLocatedGroup+        None   -> mempty +  forM_ group \imp -> printQualified options padNames stats imp >> newline+ ----------------------------------------------------------------------------------- | A "compound import name" is import's name and package (if present). For--- instance, if you have an import @Foo.Bar@ from package @foobar@, the full--- name will be @"foobar" Foo.Bar@.-compoundImportName :: H.ImportDecl l -> String-compoundImportName i =-  case importPackage i of-    Nothing  -> importName i-    Just pkg -> show pkg ++ " " ++ importName i+printQualified :: Options -> Bool -> ImportStats -> Located Import -> P ()+printQualified Options{..} padNames stats (L _ decl) = do+  let decl' = rawImport decl +  putText "import" >> space ----------------------------------------------------------------------------------longestImport :: [H.ImportDecl l] -> Int-longestImport = maximum . map (length . compoundImportName)+  case (isSource decl, isAnySource stats) of+    (True, _) -> putText "{-# SOURCE #-}" >> space+    (_, True) -> putText "              " >> space+    _         -> pure () +  when (isSafe decl) (putText "safe" >> space) ------------------------------------------------------------------------------------ | Compare imports for ordering-compareImports :: H.ImportDecl l -> H.ImportDecl l -> Ordering-compareImports =-  comparing (map toLower . importName &&&-             fmap (map toLower) . importPackage &&&-             H.importQualified)+  case (isQualified decl, isAnyQualified stats) of+    (True, _) -> putText "qualified" >> space+    (_, True) -> putText "         " >> space+    _         -> pure () +  moduleNamePosition <- length <$> getCurrentLine+  forM_ (ideclPkgQual decl') $ \pkg -> putText (stringLiteral pkg) >> space+  putText (moduleName decl) ------------------------------------------------------------------------------------ | Remove (or merge) duplicated import specs.------ * When something is mentioned twice, it's removed: @A, A@ -> A--- * More general forms take priority: @A, A(..)@ -> @A(..)@--- * Sometimes we have to combine imports: @A(x), A(y)@ -> @A(x, y)@------ Import specs are always sorted by subsequent steps so we don't have to care--- about preserving order.-deduplicateImportSpecs :: Ord l => H.ImportDecl l -> H.ImportDecl l-deduplicateImportSpecs =-  modifyImportSpecs $-    map recomposeImportSpec .-    M.toList . M.fromListWith (<>) .-    map decomposeImportSpec+  -- Only print spaces if something follows.+  when padNames $+    when (isJust (ideclAs decl') || isHiding decl ||+            not (null $ ideclHiding decl')) $+      putText $+        replicate (isLongestImport stats - importModuleNameLength decl) ' ' --- | What we are importing (variable, class, etc)-data ImportEntity l-  -- | A variable-  = ImportVar l (H.Name l)-  -- | Something that can be imported partially-  | ImportClassOrData l (H.Name l)-  -- | Something else ('H.IAbs')-  | ImportOther l (H.Namespace l) (H.Name l)-  deriving (Eq, Ord)+  beforeAliasPosition <- length <$> getCurrentLine+  forM_ (ideclAs decl') \(L _ name) ->+    space >> putText "as" >> space >> putText (moduleNameString name)+  afterAliasPosition <- length <$> getCurrentLine --- | What we are importing from an 'ImportClassOrData'-data ImportPortion l-  = ImportSome [H.CName l]  -- ^ @A(x, y, z)@-  | ImportAll               -- ^ @A(..)@+  when (isHiding decl) (space >> putText "hiding") -instance Ord l => Semigroup (ImportPortion l) where-  ImportSome a <> ImportSome b = ImportSome (setUnion a b)-  _ <> _                       = ImportAll+  let putOffset = putText $ replicate offset ' '+      offset = case listPadding of+        LPConstant n -> n+        LPModuleName -> moduleNamePosition -instance Ord l => Monoid (ImportPortion l) where-  mempty = ImportSome []-  mappend = (<>)+  case snd <$> ideclHiding decl' of+    Nothing            -> pure ()+    Just (L _ [])      -> case emptyListAlign of+      RightAfter -> modifyCurrentLine trimRight >> space >> putText "()"+      Inherit -> case listAlign of+        NewLine ->+          modifyCurrentLine trimRight >> newline >> putOffset >> putText "()"+        _ -> space >> putText "()"+    Just (L _ imports) -> do+      let printedImports = flagEnds $ -- [P ()]+            fmap ((printImport Options{..}) . unLocated)+            (prepareImportList imports) --- | O(n log n) union.-setUnion :: Ord a => [a] -> [a] -> [a]-setUnion a b = S.toList (S.fromList a `S.union` S.fromList b)+      -- Since we might need to output the import module name several times, we+      -- need to save it to a variable:+      wrapPrefix <- case listAlign of+        AfterAlias -> pure $ replicate (afterAliasPosition + 1) ' '+        WithAlias -> pure $ replicate (beforeAliasPosition + 1) ' '+        Repeat -> fmap (++ " (") getCurrentLine+        WithModuleName -> pure $ replicate (moduleNamePosition + offset) ' '+        NewLine -> pure $ replicate offset ' ' -decomposeImportSpec :: H.ImportSpec l -> (ImportEntity l, ImportPortion l)-decomposeImportSpec x = case x of-  -- I checked and it looks like namespace's 'l' is always equal to x's 'l'-  H.IAbs l space n -> case space of-    H.NoNamespace      _ -> (ImportClassOrData l n, ImportSome [])-    H.TypeNamespace    _ -> (ImportOther l space n, ImportSome [])-    H.PatternNamespace _ -> (ImportOther l space n, ImportSome [])-  H.IVar l n             -> (ImportVar l n, ImportSome [])-  H.IThingAll l n        -> (ImportClassOrData l n, ImportAll)-  H.IThingWith l n names -> (ImportClassOrData l n, ImportSome names)+      let -- Helper+          doSpaceSurround = when spaceSurround space -recomposeImportSpec :: (ImportEntity l, ImportPortion l) -> H.ImportSpec l-recomposeImportSpec (e, p) = case e of-  ImportClassOrData l n -> case p of-    ImportSome []    -> H.IAbs l (H.NoNamespace l) n-    ImportSome names -> H.IThingWith l n names-    ImportAll        -> H.IThingAll l n-  ImportVar l n         -> H.IVar l n-  ImportOther l space n -> H.IAbs l space n+          -- Try to put everything on one line.+          printAsSingleLine = forM_ printedImports $ \(imp, start, end) -> do+            when start $ putText "(" >> doSpaceSurround+            imp+            if end then doSpaceSurround >> putText ")" else comma >> space +          -- Try to put everything one by one, wrapping if that fails.+          printAsInlineWrapping wprefix = forM_ printedImports $+            \(imp, start, end) ->+            patchForRepeatHiding $ wrapping+              (do+                if start then putText "(" >> doSpaceSurround else space+                imp+                if end then doSpaceSurround >> putText ")" else comma)+              (do+                case listAlign of+                    -- In 'Repeat' mode, end lines with ')' rather than ','.+                    Repeat | not start -> modifyCurrentLine . withLast $+                        \c -> if c == ',' then ')' else c+                    _ | start && spaceSurround ->+                        -- Only necessary if spaceSurround is enabled.+                        modifyCurrentLine trimRight+                    _ -> pure ()+                newline+                void wprefix+                case listAlign of+                  -- '(' already included in repeat+                  Repeat         -> pure ()+                  -- Print the much needed '('+                  _ | start      -> putText "(" >> doSpaceSurround+                  -- Don't bother aligning if we're not in inline mode.+                  _ | longListAlign /= Inline -> pure ()+                  -- 'Inline + AfterAlias' is really where we want to be careful+                  -- with spacing.+                  AfterAlias -> space >> doSpaceSurround+                  WithModuleName -> pure ()+                  WithAlias -> pure ()+                  NewLine -> pure ()+                imp+                if end then doSpaceSurround >> putText ")" else comma) ------------------------------------------------------------------------------------ | The implementation is a bit hacky to get proper sorting for input specs:--- constructors first, followed by functions, and then operators.-compareImportSpecs :: H.ImportSpec l -> H.ImportSpec l -> Ordering-compareImportSpecs = comparing key+          -- Put everything on a separate line.  'spaceSurround' can be+          -- ignored.+          printAsMultiLine = forM_ printedImports $ \(imp, start, end) -> do+            when start $ modifyCurrentLine trimRight  -- We added some spaces.+            newline+            putOffset+            if start then putText "( " else putText ", "+            imp+            when end $ newline >> putOffset >> putText ")"++      case longListAlign of+        Multiline -> wrapping+          (space >> printAsSingleLine)+          printAsMultiLine+        Inline | NewLine <- listAlign -> do+          modifyCurrentLine trimRight+          newline >> putOffset >> printAsInlineWrapping (putText wrapPrefix)+        Inline -> space >> printAsInlineWrapping (putText wrapPrefix)+        InlineWithBreak -> wrapping+          (space >> printAsSingleLine)+          (do+            modifyCurrentLine trimRight+            newline >> putOffset >> printAsInlineWrapping putOffset)+        InlineToMultiline -> wrapping+          (space >> printAsSingleLine)+          (wrapping+            (do+              modifyCurrentLine trimRight+              newline >> putOffset >> printAsSingleLine)+            printAsMultiLine)   where-    key :: H.ImportSpec l -> (Int, Bool, String)-    key (H.IVar _ x)         = (1, isOperator x, nameToString x)-    key (H.IAbs _ _ x)       = (0, False, nameToString x)-    key (H.IThingAll _ x)    = (0, False, nameToString x)-    key (H.IThingWith _ x _) = (0, False, nameToString x)+    -- We cannot wrap/repeat 'hiding' imports since then we would get multiple+    -- imports hiding different things.+    patchForRepeatHiding = case listAlign of+        Repeat | isHiding decl -> withColumns Nothing+        _                      -> id   ----------------------------------------------------------------------------------- | Sort the input spec list inside an 'H.ImportDecl'-sortImportSpecs :: H.ImportDecl l -> H.ImportDecl l-sortImportSpecs = modifyImportSpecs (sortBy compareImportSpecs)-+printImport :: Options -> IE GhcPs -> P ()+printImport Options{..} (IEVar _ name) = do+    printIeWrappedName name+printImport _ (IEThingAbs _ name) = do+    printIeWrappedName name+printImport _ (IEThingAll _ name) = do+    printIeWrappedName name+    space+    putText "(..)"+printImport _ (IEModuleContents _ (L _ m)) = do+    putText (moduleNameString m)+printImport Options{..} (IEThingWith _ name _wildcard imps _) = do+    printIeWrappedName name+    when separateLists space+    parenthesize $+      sep (comma >> space) (printIeWrappedName <$> imps)+printImport _ (IEGroup _ _ _ ) =+    error "Language.Haskell.Stylish.Printer.Imports.printImportExport: unhandled case 'IEGroup'"+printImport _ (IEDoc _ _) =+    error "Language.Haskell.Stylish.Printer.Imports.printImportExport: unhandled case 'IEDoc'"+printImport _ (IEDocNamed _ _) =+    error "Language.Haskell.Stylish.Printer.Imports.printImportExport: unhandled case 'IEDocNamed'"+printImport _ (XIE ext) =+    GHC.noExtCon ext  ----------------------------------------------------------------------------------- | Order of imports in sublist is:--- Constructors, accessors/methods, operators.-compareImportSubSpecs :: H.CName l -> H.CName l -> Ordering-compareImportSubSpecs = comparing key-  where-    key :: H.CName l -> (Int, Bool, String)-    key (H.ConName _ x) = (0, False,        nameToString x)-    key (H.VarName _ x) = (1, isOperator x, nameToString x)-+printIeWrappedName :: LIEWrappedName RdrName -> P ()+printIeWrappedName lie = unLocated lie & \case+  IEName n -> putRdrName n+  IEPattern n -> putText "pattern" >> space >> putRdrName n+  IEType n -> putText "type" >> space >> putRdrName n ------------------------------------------------------------------------------------ | By default, haskell-src-exts pretty-prints------ > import Foo (Bar(..))------ but we want------ > import Foo (Bar (..))------ instead.-prettyImportSpec :: (Ord l) => Bool -> H.ImportSpec l -> String-prettyImportSpec separate = prettyImportSpec'+mergeImports :: NonEmpty (Located Import) -> NonEmpty (Located Import)+mergeImports (x :| []) = x :| []+mergeImports (h :| (t : ts))+  | canMergeImport (unLocated h) (unLocated t) = mergeImports (mergeModuleImport h t :| ts)+  | otherwise = h :| mergeImportsTail (t : ts)   where-    prettyImportSpec' (H.IThingAll  _ n)     = H.prettyPrint n ++ sep "(..)"-    prettyImportSpec' (H.IThingWith _ n cns) = H.prettyPrint n-        ++ sep "("-        ++ intercalate ", "-          (map H.prettyPrint $ sortBy compareImportSubSpecs cns)-        ++ ")"-    prettyImportSpec' x                      = H.prettyPrint x+    mergeImportsTail (x : y : ys)+      | canMergeImport (unLocated x) (unLocated y) = mergeImportsTail ((mergeModuleImport x y) : ys)+      | otherwise = x : mergeImportsTail (y : ys)+    mergeImportsTail xs = xs -    sep = if separate then (' ' :) else id+moduleName :: Import -> String+moduleName+  = moduleNameString+  . unLocated+  . ideclName+  . rawImport   ---------------------------------------------------------------------------------prettyImport :: (Ord l, Show l) =>-    Maybe Int -> Options -> Bool -> Bool -> Int -> H.ImportDecl l -> [String]-prettyImport columns Options{..} padQualified padName longest imp-    | (void `fmap` H.importSpecs imp) == emptyImportSpec = emptyWrap-    | otherwise = case longListAlign of-        Inline            -> inlineWrap-        InlineWithBreak   -> longListWrapper inlineWrap inlineWithBreakWrap-        InlineToMultiline -> longListWrapper inlineWrap inlineToMultilineWrap-        Multiline         -> longListWrapper inlineWrap multilineWrap-  where-    emptyImportSpec = Just (H.ImportSpecList () False [])-    -- "import" + space + qualifiedLength has space in it.-    listPadding' = listPaddingValue (6 + 1 + qualifiedLength) listPadding-      where-        qualifiedLength =-            if null qualified then 0 else 1 + sum (map length qualified)+data ImportStats = ImportStats+    { isLongestImport :: !Int+    , isAnySource     :: !Bool+    , isAnyQualified  :: !Bool+    , isAnySafe       :: !Bool+    } -    longListWrapper shortWrap longWrap-        | listAlign == NewLine-        || length shortWrap > 1-        || exceedsColumns (length (head shortWrap))-            = longWrap-        | otherwise = shortWrap+instance Semigroup ImportStats where+    l <> r = ImportStats+        { isLongestImport = isLongestImport l `max` isLongestImport r+        , isAnySource     = isAnySource     l ||    isAnySource     r+        , isAnyQualified  = isAnyQualified  l ||    isAnyQualified  r+        , isAnySafe       = isAnySafe       l ||    isAnySafe       r+        } -    emptyWrap = case emptyListAlign of-        Inherit    -> inlineWrap-        RightAfter -> [paddedNoSpecBase ++ " ()"]+instance Monoid ImportStats where+    mappend = (<>)+    mempty  = ImportStats 0 False False False -    inlineWrap = inlineWrapper-        $ mapSpecs-        $ withInit (++ ",")-        . withHead (("(" ++ maybeSpace) ++)-        . withLast (++ (maybeSpace ++ ")"))+importStats :: Import -> ImportStats+importStats i =+    ImportStats (importModuleNameLength i) (isSource i) (isQualified i) (isSafe i) -    inlineWrapper = case listAlign of-        NewLine        -> (paddedNoSpecBase :) . wrapRestMaybe columns listPadding'-        WithModuleName -> wrapMaybe columns paddedBase (withModuleNameBaseLength + 4)-        WithAlias      -> wrapMaybe columns paddedBase (inlineBaseLength + 1)-        -- Add 1 extra space to ensure same padding as in original code.-        AfterAlias     -> withTail ((' ' : maybeSpace) ++)-            . wrapMaybe columns paddedBase (afterAliasBaseLength + 1)+-- Computes length till module name, includes package name.+-- TODO: this should reuse code with the printer+importModuleNameLength :: Import -> Int+importModuleNameLength imp =+    (case ideclPkgQual (rawImport imp) of+        Nothing -> 0+        Just sl -> 1 + length (stringLiteral sl)) ++    (length $ moduleName imp) -    inlineWithBreakWrap = paddedNoSpecBase : wrapRestMaybe columns listPadding'-        ( mapSpecs-        $ withInit (++ ",")-        . withHead (("(" ++ maybeSpace) ++)-        . withLast (++ (maybeSpace ++ ")"))) -    inlineToMultilineWrap-        | length inlineWithBreakWrap > 2-        || any (exceedsColumns . length) (tail inlineWithBreakWrap)-            = multilineWrap-        | otherwise = inlineWithBreakWrap--    -- 'wrapRest 0' ensures that every item of spec list is on new line.-    multilineWrap = paddedNoSpecBase : wrapRest 0 listPadding'-        ( mapSpecs-          ( withHead ("( " ++)-          . withTail (", " ++))-        ++ closer)-      where-        closer = if null importSpecs-            then []-            else [")"]--    paddedBase = base $ padImport $ compoundImportName imp--    paddedNoSpecBase = base $ padImportNoSpec $ compoundImportName imp--    padImport = if hasExtras && padName-        then padRight longest-        else id--    padImportNoSpec = if (isJust (H.importAs imp) || hasHiding) && padName-        then padRight longest-        else id--    base' baseName importAs hasHiding' = unwords $ concat $-        [ ["import"]-        , source-        , safe-        , qualified-        , [baseName]-        , importAs-        , hasHiding'-        ]--    base baseName = base' baseName-        ["as " ++ as | H.ModuleName _ as <- maybeToList $ H.importAs imp]-        ["hiding" | hasHiding]--    inlineBaseLength = length $-                       base' (padImport $ compoundImportName imp) [] []--    withModuleNameBaseLength = length $ base' "" [] []--    afterAliasBaseLength = length $ base' (padImport $ compoundImportName imp)-        ["as " ++ as | H.ModuleName _ as <- maybeToList $ H.importAs imp] []--    (hasHiding, importSpecs) = case H.importSpecs imp of-        Just (H.ImportSpecList _ h l) -> (h, Just l)-        _                             -> (False, Nothing)--    hasExtras = isJust (H.importAs imp) || isJust (H.importSpecs imp)--    qualified-        | H.importQualified imp = ["qualified"]-        | padQualified          =-              if H.importSrc imp-                  then []-                  else if H.importSafe imp-                           then ["    "]-                           else ["         "]-        | otherwise             = []--    safe-        | H.importSafe imp = ["safe"]-        | otherwise        = []--    source-        | H.importSrc imp = ["{-# SOURCE #-}"]-        | otherwise       = []--    mapSpecs f = case importSpecs of-        Nothing -> []     -- Import everything-        Just [] -> ["()"] -- Instance only imports-        Just is -> f $ map (prettyImportSpec separateLists) is--    maybeSpace = case spaceSurround of-        True  -> " "-        False -> ""--    exceedsColumns i = case columns of-        Nothing -> False  -- No number exceeds a maximum column count of-                          -- Nothing, because there is no limit to exceed.-        Just c -> i > c+--------------------------------------------------------------------------------+stringLiteral :: StringLiteral -> String+stringLiteral sl = case sl_st sl of+    NoSourceText -> FS.unpackFS $ sl_fs sl+    SourceText s -> s   ---------------------------------------------------------------------------------prettyImportGroup :: Maybe Int -> Options -> Bool -> Int-                  -> [H.ImportDecl LineBlock]-                  -> Lines-prettyImportGroup columns align fileAlign longest imps =-    concatMap (prettyImport columns align padQual padName longest') $-    sortBy compareImports imps-  where-    align' = importAlign align-    padModuleNames' = padModuleNames align--    longest' = case align' of-        Group -> longestImport imps-        _     -> longest+isQualified :: Import -> Bool+isQualified+  = (/=) NotQualified+  . ideclQualified+  . rawImport -    padName = align' /= None && padModuleNames'+isHiding :: Import -> Bool+isHiding+  = maybe False fst+  . ideclHiding+  . rawImport -    padQual = case align' of-        Global -> True-        File   -> fileAlign-        Group  -> any H.importQualified imps-        None   -> False+isSource :: Import -> Bool+isSource+  = ideclSource+  . rawImport +isSafe :: Import -> Bool+isSafe+  = ideclSafe+  . rawImport  ---------------------------------------------------------------------------------step :: Maybe Int -> Options -> Step-step columns = makeStep "Imports" . step' columns+-- | Cleans up an import item list.+--+-- * Sorts import items.+-- * Sort inner import lists, e.g. `import Control.Monad (Monad (return, join))`+-- * Removes duplicates from import lists.+prepareImportList :: [LIE GhcPs] -> [LIE GhcPs]+prepareImportList =+  sortBy compareLIE . map (fmap prepareInner) .+  concatMap (toList . snd) . Map.toAscList . mergeByName+ where+  mergeByName :: [LIE GhcPs] -> Map.Map RdrName (NonEmpty (LIE GhcPs))+  mergeByName imports0 = Map.fromListWith+    -- Note that ideally every NonEmpty will just have a single entry and we+    -- will be able to merge everything into that entry.  Exotic imports can+    -- mess this up, though.  So they end up in the tail of the list.+    (\(x :| xs) (y :| ys) -> case ieMerge (unLocated x) (unLocated y) of+      Just z -> (x $> z) :| (xs ++ ys)  -- Keep source from `x`+      Nothing -> x :| (xs ++ y : ys))+    [(ieName $ unLocated imp, imp :| []) | imp <- imports0] +  prepareInner :: IE GhcPs -> IE GhcPs+  prepareInner = \case+    -- Simplify `A ()` to `A`.+    IEThingWith x n NoIEWildcard [] [] -> IEThingAbs x n+    IEThingWith x n w ns fs ->+      IEThingWith x n w (sortBy (compareWrappedName `on` unLoc) ns) fs+    ie -> ie ----------------------------------------------------------------------------------step' :: Maybe Int -> Options -> Lines -> Module -> Lines-step' columns align ls (module', _) = applyChanges-    [ change block $ const $-        prettyImportGroup columns align fileAlign longest importGroup-    | (block, importGroup) <- groups-    ]-    ls-  where-    imps    = map (sortImportSpecs . deduplicateImportSpecs) $-              imports $ fmap linesFromSrcSpan module'-    longest = longestImport imps-    groups  = groupAdjacent [(H.ann i, i) | i <- imps]+  -- Merge two import items, assuming they have the same name.+  ieMerge :: IE GhcPs -> IE GhcPs -> Maybe (IE GhcPs)+  ieMerge l@(IEVar _ _)      _                  = Just l+  ieMerge _                  r@(IEVar _ _)      = Just r+  ieMerge (IEThingAbs _ _)   r                  = Just r+  ieMerge l                  (IEThingAbs _ _)   = Just l+  ieMerge l@(IEThingAll _ _) _                  = Just l+  ieMerge _                  r@(IEThingAll _ _) = Just r+  ieMerge (IEThingWith x0 n0 w0 ns0 []) (IEThingWith _ _ w1 ns1 [])+    | w0 /= w1  = Nothing+    | otherwise = Just $+        -- TODO: sort the `ns0 ++ ns1`?+        IEThingWith x0 n0 w0 (nubOn (unwrapName . unLoc) $ ns0 ++ ns1) []+  ieMerge _ _ = Nothing -    fileAlign = case importAlign align of-        File -> any H.importQualified imps-        _    -> False  ---------------------------------------------------------------------------------listPaddingValue :: Int -> ListPadding -> Int-listPaddingValue _ (LPConstant n) = n-listPaddingValue n LPModuleName   = n------------------------------------------------------------------------------------instance A.FromJSON ListPadding where-    parseJSON (A.String "module_name") = return LPModuleName-    parseJSON (A.Number n) | n' >= 1   = return $ LPConstant n'-      where-        n' = truncate n-    parseJSON v                        = A.typeMismatch "'module_name' or >=1 number" v+nubOn :: Ord k => (a -> k) -> [a] -> [a]+nubOn f = go Set.empty+ where+  go _   []              = []+  go acc (x : xs)+    | y `Set.member` acc = go acc xs+    | otherwise          = x : go (Set.insert y acc) xs+   where+    y = f x
lib/Language/Haskell/Stylish/Step/LanguagePragmas.hs view
@@ -1,4 +1,7 @@ --------------------------------------------------------------------------------+{-# LANGUAGE BlockArguments    #-}+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-} module Language.Haskell.Stylish.Step.LanguagePragmas     ( Style (..)     , step@@ -8,13 +11,23 @@   --------------------------------------------------------------------------------+import           Data.List.NonEmpty              (NonEmpty, fromList, toList) import qualified Data.Set                        as S-import qualified Language.Haskell.Exts           as H+import           Data.Text                       (Text)+import qualified Data.Text                       as T   --------------------------------------------------------------------------------+import qualified GHC.Hs                          as Hs+import           SrcLoc                          (RealSrcSpan, realSrcSpanStart,+                                                  srcLocLine, srcSpanEndLine,+                                                  srcSpanStartLine)+++-------------------------------------------------------------------------------- import           Language.Haskell.Stylish.Block import           Language.Haskell.Stylish.Editor+import           Language.Haskell.Stylish.Module import           Language.Haskell.Stylish.Step import           Language.Haskell.Stylish.Util @@ -28,19 +41,6 @@   ---------------------------------------------------------------------------------pragmas :: H.Module l -> [(l, [String])]-pragmas (H.Module _ _ ps _ _) =-    [(l, map nameToString names) | H.LanguagePragma l names <- ps]-pragmas _                     = []-------------------------------------------------------------------------------------- | The start of the first block-firstLocation :: [(Block a, [String])] -> Int-firstLocation = minimum . map (blockStart . fst)----------------------------------------------------------------------------------- verticalPragmas :: String -> Int -> Bool -> [String] -> Lines verticalPragmas lg longest align pragmas' =     [ "{-# " ++ lg ++ " " ++ pad pragma ++ " #-}"@@ -91,10 +91,10 @@ -------------------------------------------------------------------------------- -- | Filter redundant (and duplicate) pragmas out of the groups. As a side -- effect, we also sort the pragmas in their group...-filterRedundant :: (String -> Bool)-                -> [(l, [String])]-                -> [(l, [String])]-filterRedundant isRedundant' = snd . foldr filterRedundant' (S.empty, [])+filterRedundant :: (Text -> Bool)+                -> [(l, NonEmpty Text)]+                -> [(l, [Text])]+filterRedundant isRedundant' = snd . foldr filterRedundant' (S.empty, []) . fmap (fmap toList)   where     filterRedundant' (l, xs) (known, zs)         | S.null xs' = (known', zs)@@ -111,38 +111,54 @@  -------------------------------------------------------------------------------- step' :: Maybe Int -> Style -> Bool -> Bool -> String -> Lines -> Module -> Lines-step' columns style align removeRedundant lngPrefix ls (module', _)-    | null pragmas' = ls-    | otherwise     = applyChanges changes ls+step' columns style align removeRedundant lngPrefix ls m+  | null languagePragmas = ls+  | otherwise = applyChanges changes ls   where     isRedundant'-        | removeRedundant = isRedundant module'+        | removeRedundant = isRedundant m         | otherwise       = const False-    pragmas' = pragmas $ fmap linesFromSrcSpan module'-    longest  = maximum $ map length $ snd =<< pragmas'-    groups   = [(b, concat pgs) | (b, pgs) <- groupAdjacent pragmas']-    changes  =-        [ change b (const $ prettyPragmas lngPrefix columns longest align style pg)-        | (b, pg) <- filterRedundant isRedundant' groups-        ] +    languagePragmas = moduleLanguagePragmas m +    convertFstToBlock :: [(RealSrcSpan, a)] -> [(Block String, a)]+    convertFstToBlock = fmap \(rspan, a) ->+      (Block (srcSpanStartLine rspan) (srcSpanEndLine rspan), a)++    groupAdjacent' =+      fmap turnSndBackToNel . groupAdjacent . fmap (fmap toList)+      where+        turnSndBackToNel (a, bss) = (a, fromList . concat $ bss)++    longest :: Int+    longest  = maximum $ map T.length $ toList . snd =<< languagePragmas++    groups :: [(Block String, NonEmpty Text)]+    groups = [(b, pgs) | (b, pgs) <- groupAdjacent' (convertFstToBlock languagePragmas)]++    changes =+      [ change b (const $ prettyPragmas lngPrefix columns longest align style (fmap T.unpack pg))+      | (b, pg) <- filterRedundant isRedundant' groups+      ]+ -------------------------------------------------------------------------------- -- | Add a LANGUAGE pragma to a module if it is not present already.-addLanguagePragma :: String -> String -> H.Module H.SrcSpanInfo -> [Change String]+addLanguagePragma :: String -> String -> Module -> [Change String] addLanguagePragma lg prag modu     | prag `elem` present = []     | otherwise           = [insert line ["{-# " ++ lg ++ " " ++ prag ++ " #-}"]]   where-    pragmas' = pragmas (fmap linesFromSrcSpan modu)-    present  = concatMap snd pragmas'-    line     = if null pragmas' then 1 else firstLocation pragmas'+    pragmas'      = moduleLanguagePragmas modu+    present       = concatMap ((fmap T.unpack) . toList . snd) pragmas'+    line          = if null pragmas' then 1 else firstLocation pragmas'+    firstLocation :: [(RealSrcSpan, NonEmpty Text)] -> Int+    firstLocation = minimum . fmap (srcLocLine . realSrcSpanStart . fst)   -------------------------------------------------------------------------------- -- | Check if a language pragma is redundant. We can't do this for all pragmas, -- but we do a best effort.-isRedundant :: H.Module H.SrcSpanInfo -> String -> Bool+isRedundant :: Module -> Text -> Bool isRedundant m "ViewPatterns" = isRedundantViewPatterns m isRedundant m "BangPatterns" = isRedundantBangPatterns m isRedundant _ _              = False@@ -150,13 +166,29 @@  -------------------------------------------------------------------------------- -- | Check if the ViewPatterns language pragma is redundant.-isRedundantViewPatterns :: H.Module H.SrcSpanInfo -> Bool-isRedundantViewPatterns m = null-    [() | H.PViewPat {} <- everything m :: [H.Pat H.SrcSpanInfo]]+isRedundantViewPatterns :: Module -> Bool+isRedundantViewPatterns = null . queryModule getViewPat+  where+    getViewPat :: Hs.Pat Hs.GhcPs -> [()]+    getViewPat = \case+      Hs.ViewPat{} -> [()]+      _            -> []   -------------------------------------------------------------------------------- -- | Check if the BangPatterns language pragma is redundant.-isRedundantBangPatterns :: H.Module H.SrcSpanInfo -> Bool-isRedundantBangPatterns m = null-    [() | H.PBangPat _ _ <- everything m :: [H.Pat H.SrcSpanInfo]]+isRedundantBangPatterns :: Module -> Bool+isRedundantBangPatterns modul =+    (null $ queryModule getBangPat modul) &&+    (null $ queryModule getMatchStrict modul)+  where+    getBangPat :: Hs.Pat Hs.GhcPs -> [()]+    getBangPat = \case+      Hs.BangPat{} -> [()]+      _            -> []++    getMatchStrict :: Hs.Match Hs.GhcPs (Hs.LHsExpr Hs.GhcPs) -> [()]+    getMatchStrict (Hs.XMatch m) = Hs.noExtCon m+    getMatchStrict (Hs.Match _ ctx _ _) = case ctx of+      Hs.FunRhs _ _ Hs.SrcStrict -> [()]+      _                          -> []
+ lib/Language/Haskell/Stylish/Step/ModuleHeader.hs view
@@ -0,0 +1,250 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE LambdaCase     #-}+module Language.Haskell.Stylish.Step.ModuleHeader+  ( Config (..)+  , defaultConfig+  , step+  ) where++--------------------------------------------------------------------------------+import           ApiAnnotation                    (AnnKeywordId (..),+                                                   AnnotationComment (..))+import           Control.Monad                    (forM_, join, when)+import           Data.Bifunctor                   (second)+import           Data.Foldable                    (find, toList)+import           Data.Function                    (on, (&))+import qualified Data.List                        as L+import           Data.List.NonEmpty               (NonEmpty (..))+import qualified Data.List.NonEmpty               as NonEmpty+import           Data.Maybe                       (isJust, listToMaybe)+import qualified GHC.Hs.Doc                       as GHC+import           GHC.Hs.Extension                 (GhcPs)+import qualified GHC.Hs.Extension                 as GHC+import           GHC.Hs.ImpExp                    (IE (..))+import qualified GHC.Hs.ImpExp                    as GHC+import qualified Module                           as GHC+import           SrcLoc                           (GenLocated (..), Located,+                                                   RealLocated, SrcSpan (..),+                                                   srcSpanEndLine,+                                                   srcSpanStartLine, unLoc)+import           Util                             (notNull)++--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.Block+import           Language.Haskell.Stylish.Editor+import           Language.Haskell.Stylish.GHC+import           Language.Haskell.Stylish.Module+import           Language.Haskell.Stylish.Ordering+import           Language.Haskell.Stylish.Printer+import           Language.Haskell.Stylish.Step+++data Config = Config+    -- TODO(jaspervdj): Use the same sorting as in `Imports`?+    -- TODO: make sorting optional?+    { indent :: Int+    , sort   :: Bool+    }++defaultConfig :: Config+defaultConfig = Config+    { indent = 4+    , sort   = True+    }++step :: Config -> Step+step = makeStep "Module header" . printModuleHeader++printModuleHeader :: Config -> Lines -> Module -> Lines+printModuleHeader conf ls m =+  let+    header = moduleHeader m+    name = rawModuleName header+    haddocks = rawModuleHaddocks header+    exports = rawModuleExports header+    annotations = rawModuleAnnotations m++    relevantComments :: [RealLocated AnnotationComment]+    relevantComments+      = moduleComments m+      & rawComments+      & dropAfterLocated exports+      & dropBeforeLocated name++    -- TODO: pass max columns?+    printedModuleHeader = runPrinter_ (PrinterConfig Nothing) relevantComments+        m (printHeader conf name exports haddocks)++    getBlock loc =+      Block <$> fmap getStartLineUnsafe loc <*> fmap getEndLineUnsafe loc++    adjustOffsetFrom :: Block a -> Block a -> Maybe (Block a)+    adjustOffsetFrom (Block s0 _) b2@(Block s1 e1)+      | s0 >= s1 && s0 >= e1 = Nothing+      | s0 >= s1 = Just (Block (s0 + 1) e1)+      | otherwise = Just b2++    nameBlock =+      getBlock name++    exportsBlock =+      join $ adjustOffsetFrom <$> nameBlock <*> getBlock exports++    whereM :: Maybe SrcSpan+    whereM+      = annotations+      & filter (\(((_, w), _)) -> w == AnnWhere)+      & fmap (head . snd) -- get position of annot+      & L.sort+      & listToMaybe++    isModuleHeaderWhere :: Block a -> Bool+    isModuleHeaderWhere w+      = not+      . overlapping+      $ [w] <> toList nameBlock <> toList exportsBlock++    toLineBlock :: SrcSpan -> Block a+    toLineBlock (RealSrcSpan s) = Block (srcSpanStartLine s) (srcSpanEndLine s)+    toLineBlock s+      = error+      $ "'where' block was not a RealSrcSpan" <> show s++    whereBlock+      = whereM+      & fmap toLineBlock+      & find isModuleHeaderWhere++    deletes =+      fmap delete $ mergeAdjacent $ toList nameBlock <> toList exportsBlock <> toList whereBlock++    startLine =+      maybe 1 blockStart nameBlock++    additions = [insert startLine printedModuleHeader]++    changes = deletes <> additions+  in+    applyChanges changes ls++printHeader+  :: Config+  -> Maybe (Located GHC.ModuleName)+  -> Maybe (Located [GHC.LIE GhcPs])+  -> Maybe GHC.LHsDocString+  -> P ()+printHeader conf mname mexps _ = do+  forM_ mname \(L loc name) -> do+    putText "module"+    space+    putText (showOutputable name)+    attachEolComment loc++  maybe+    (when (isJust mname) do newline >> spaces (indent conf) >> putText "where")+    (printExportList conf)+    mexps++attachEolComment :: SrcSpan -> P ()+attachEolComment = \case+  UnhelpfulSpan _ -> pure ()+  RealSrcSpan rspan ->+    removeLineComment (srcSpanStartLine rspan) >>= mapM_ \c -> space >> putComment c++attachEolCommentEnd :: SrcSpan -> P ()+attachEolCommentEnd = \case+  UnhelpfulSpan _ -> pure ()+  RealSrcSpan rspan ->+    removeLineComment (srcSpanEndLine rspan) >>= mapM_ \c -> space >> putComment c++printExportList :: Config -> Located [GHC.LIE GhcPs] -> P ()+printExportList conf (L srcLoc exports) = do+  newline+  doIndent >> putText "(" >> when (notNull exports) space++  exportsWithComments <- fmap (second doSort) <$> groupAttachedComments exports++  printExports exportsWithComments++  putText ")" >> space >> putText "where" >> attachEolCommentEnd srcLoc+  where+    -- 'doIndent' is @x@:+    --+    -- > module Foo+    -- > xxxx( foo+    -- > xxxx, bar+    -- > xxxx) where+    --+    -- 'doHang' is @y@:+    --+    -- > module Foo+    -- > xxxx( -- Some comment+    -- > xxxxyyfoo+    -- > xxxx) where+    doIndent = spaces (indent conf)+    doHang = do+        len <- length <$> getCurrentLine+        spaces $ indent conf + 2 - len++    doSort = if sort conf then NonEmpty.sortBy compareLIE else id++    printExports :: [([AnnotationComment], NonEmpty (GHC.LIE GhcPs))] -> P ()+    printExports (([], firstInGroup :| groupRest) : rest) = do+      printExport firstInGroup+      newline+      doIndent+      printExportsGroupTail groupRest+      printExportsTail rest+    printExports ((firstComment : comments, firstExport :| groupRest) : rest) = do+      putComment firstComment >> newline >> doIndent+      forM_ comments \c -> doHang >> putComment c >> newline >> doIndent+      doHang+      printExport firstExport+      newline+      doIndent+      printExportsGroupTail groupRest+      printExportsTail rest+    printExports [] =+      newline >> doIndent++    printExportsTail :: [([AnnotationComment], NonEmpty (GHC.LIE GhcPs))] -> P ()+    printExportsTail = mapM_ \(comments, exported) -> do+      forM_ comments \c -> doHang >> putComment c >> newline >> doIndent+      forM_ exported \export -> do+        comma >> space >> printExport export+        newline >> doIndent++    printExportsGroupTail :: [GHC.LIE GhcPs] -> P ()+    printExportsGroupTail (x : xs) = printExportsTail [([], x :| xs)]+    printExportsGroupTail []       = pure ()++    printExport :: GHC.LIE GhcPs -> P ()+    printExport (L _ export) = case export of+      IEVar _ name -> putOutputable name+      IEThingAbs _ name -> putOutputable name+      IEThingAll _ name -> do+        putOutputable name+        space+        putText "(..)"+      IEModuleContents _ (L _ m) -> do+        putText "module"+        space+        putText (showOutputable m)+      IEThingWith _ name _wildcard imps _ -> do+        putOutputable name+        space+        putText "("+        sep (comma >> space) $+          fmap putOutputable $ L.sortBy (compareWrappedName `on` unLoc) imps+        putText ")"+      IEGroup _ _ _ ->+        error $+          "Language.Haskell.Stylish.Printer.Imports.printImportExport: unhandled case 'IEGroup'" <> showOutputable export+      IEDoc _ _ ->+        error $+          "Language.Haskell.Stylish.Printer.Imports.printImportExport: unhandled case 'IEDoc'" <> showOutputable export+      IEDocNamed _ _ ->+        error $+          "Language.Haskell.Stylish.Printer.Imports.printImportExport: unhandled case 'IEDocNamed'" <> showOutputable export+      XIE ext ->+        GHC.noExtCon ext
lib/Language/Haskell/Stylish/Step/SimpleAlign.hs view
@@ -1,4 +1,5 @@ --------------------------------------------------------------------------------+{-# LANGUAGE TypeFamilies #-} module Language.Haskell.Stylish.Step.SimpleAlign     ( Config (..)     , defaultConfig@@ -7,15 +8,17 @@   ---------------------------------------------------------------------------------import           Data.Data                       (Data)+import           Control.Monad                   (guard) import           Data.List                       (foldl')-import           Data.Maybe                      (maybeToList)-import qualified Language.Haskell.Exts           as H+import           Data.Maybe                      (fromMaybe)+import qualified GHC.Hs                          as Hs+import qualified SrcLoc                          as S   -------------------------------------------------------------------------------- import           Language.Haskell.Stylish.Align import           Language.Haskell.Stylish.Editor+import           Language.Haskell.Stylish.Module import           Language.Haskell.Stylish.Step import           Language.Haskell.Stylish.Util @@ -38,91 +41,111 @@   ---------------------------------------------------------------------------------cases :: Data l => H.Module l -> [[H.Alt l]]-cases modu = [alts | H.Case _ _ alts <- everything modu]+type Record = [S.Located (Hs.ConDeclField Hs.GhcPs)]   ----------------------------------------------------------------------------------- | For this to work well, we require a way to merge annotations.  This merge--- operation should follow the semigroup laws.-altToAlignable :: (l -> l -> l) -> H.Alt l -> Maybe (Alignable l)-altToAlignable _ (H.Alt _   _   _   (Just _)) = Nothing-altToAlignable _ (H.Alt ann pat rhs@(H.UnGuardedRhs _ _) Nothing) = Just $-    Alignable-        { aContainer = ann-        , aLeft      = H.ann pat-        , aRight     = H.ann rhs-        , aRightLead = length "-> "-        }-altToAlignable-        merge-        (H.Alt ann pat (H.GuardedRhss _ [H.GuardedRhs _ guards rhs]) Nothing) =-    -- We currently only support the case where an alternative has a single-    -- guarded RHS.  If there are more, we would need to return multiple-    -- `Alignable`s from this function, which would be a significant change.-    Just $ Alignable-        { aContainer = ann-        , aLeft      = foldl' merge (H.ann pat) (map H.ann guards)-        , aRight     = H.ann rhs-        , aRightLead = length "-> "-        }-altToAlignable _ _ = Nothing+records :: S.Located (Hs.HsModule Hs.GhcPs) -> [Record]+records modu = do+  let decls           = map S.unLoc (Hs.hsmodDecls (S.unLoc modu))+      tyClDecls       = [ tyClDecl | Hs.TyClD _ tyClDecl <- decls ]+      dataDecls       = [ d | d@(Hs.DataDecl _ _ _ _ _)  <- tyClDecls ]+      dataDefns       = map Hs.tcdDataDefn dataDecls+  d@Hs.ConDeclH98 {} <- concatMap getConDecls dataDefns+  case Hs.con_args d of+      Hs.RecCon rec -> [S.unLoc rec]+      _             -> []+ where+  getConDecls :: Hs.HsDataDefn Hs.GhcPs -> [Hs.ConDecl Hs.GhcPs]+  getConDecls d@Hs.HsDataDefn {} = map S.unLoc $ Hs.dd_cons d+  getConDecls (Hs.XHsDataDefn x) = Hs.noExtCon x   ---------------------------------------------------------------------------------tlpats :: Data l => H.Module l -> [[H.Match l]]-tlpats modu = [matches | H.FunBind _ matches <- everything modu]+recordToAlignable :: Record -> [Alignable S.RealSrcSpan]+recordToAlignable = fromMaybe [] . traverse fieldDeclToAlignable   ---------------------------------------------------------------------------------matchToAlignable :: H.Match l -> Maybe (Alignable l)-matchToAlignable (H.InfixMatch _ _ _ _ _ _)           = Nothing-matchToAlignable (H.Match _   _    []   _   _)        = Nothing-matchToAlignable (H.Match _   _    _    _   (Just _)) = Nothing-matchToAlignable (H.Match ann name pats rhs Nothing)  = Just $ Alignable-    { aContainer = ann-    , aLeft      = last (H.ann name : map H.ann pats)-    , aRight     = H.ann rhs-    , aRightLead = length "= "+fieldDeclToAlignable+    :: S.Located (Hs.ConDeclField Hs.GhcPs) -> Maybe (Alignable S.RealSrcSpan)+fieldDeclToAlignable (S.L _ (Hs.XConDeclField x)) = Hs.noExtCon x+fieldDeclToAlignable (S.L matchLoc (Hs.ConDeclField _ names ty _)) = do+  matchPos <- toRealSrcSpan matchLoc+  leftPos  <- toRealSrcSpan $ S.getLoc $ last names+  tyPos    <- toRealSrcSpan $ S.getLoc ty+  Just $ Alignable+    { aContainer = matchPos+    , aLeft      = leftPos+    , aRight     = tyPos+    , aRightLead = length ":: "     }   ---------------------------------------------------------------------------------records :: H.Module l -> [[H.FieldDecl l]]-records modu =-    [ fields-    | H.Module _ _ _ _ decls                     <- [modu]-    , H.DataDecl _ _ _ _ cons _                  <- decls-    , H.QualConDecl _ _ _ (H.RecDecl _ _ fields) <- cons-    ]+matchGroupToAlignable+    :: Config+    -> Hs.MatchGroup Hs.GhcPs (Hs.LHsExpr Hs.GhcPs)+    -> [Alignable S.RealSrcSpan]+matchGroupToAlignable _conf (Hs.XMatchGroup x) = Hs.noExtCon x+matchGroupToAlignable conf (Hs.MG _ alts _) =+  fromMaybe [] $ traverse (matchToAlignable conf) (S.unLoc alts)   ---------------------------------------------------------------------------------fieldDeclToAlignable :: H.FieldDecl a -> Maybe (Alignable a)-fieldDeclToAlignable (H.FieldDecl ann names ty) = Just $ Alignable-    { aContainer = ann-    , aLeft      = H.ann (last names)-    , aRight     = H.ann ty-    , aRightLead = length ":: "+matchToAlignable+    :: Config+    -> S.Located (Hs.Match Hs.GhcPs (Hs.LHsExpr Hs.GhcPs))+    -> Maybe (Alignable S.RealSrcSpan)+matchToAlignable conf (S.L matchLoc m@(Hs.Match _ Hs.CaseAlt pats@(_ : _) grhss)) = do+  let patsLocs   = map S.getLoc pats+      pat        = last patsLocs+      guards     = getGuards m+      guardsLocs = map S.getLoc guards+      left       = foldl' S.combineSrcSpans pat guardsLocs+  guard $ cCases conf+  body     <- rhsBody grhss+  matchPos <- toRealSrcSpan matchLoc+  leftPos  <- toRealSrcSpan left+  rightPos <- toRealSrcSpan $ S.getLoc body+  Just $ Alignable+    { aContainer = matchPos+    , aLeft      = leftPos+    , aRight     = rightPos+    , aRightLead = length "-> "     }+matchToAlignable conf (S.L matchLoc (Hs.Match _ (Hs.FunRhs name _ _) pats@(_ : _) grhss)) = do+  guard $ cTopLevelPatterns conf+  body <- unguardedRhsBody grhss+  let patsLocs = map S.getLoc pats+      nameLoc  = S.getLoc name+      left     = last (nameLoc : patsLocs)+      bodyLoc  = S.getLoc body+  matchPos <- toRealSrcSpan matchLoc+  leftPos  <- toRealSrcSpan left+  bodyPos  <- toRealSrcSpan bodyLoc+  Just $ Alignable+    { aContainer = matchPos+    , aLeft      = leftPos+    , aRight     = bodyPos+    , aRightLead = length "= "+    }+matchToAlignable _conf (S.L _ (Hs.XMatch x))       = Hs.noExtCon x+matchToAlignable _conf (S.L _ (Hs.Match _ _ _ _))  = Nothing   -------------------------------------------------------------------------------- step :: Maybe Int -> Config -> Step-step maxColumns config = makeStep "Cases" $ \ls (module', _) ->-    let module''               = fmap H.srcInfoSpan module'-        changes search toAlign =-            [ change_-            | case_   <- search module''-            , aligns  <- maybeToList (mapM toAlign case_)-            , change_ <- align maxColumns aligns-            ]+step maxColumns config = makeStep "Cases" $ \ls module' ->+    let changes+            :: (S.Located (Hs.HsModule Hs.GhcPs) -> [a])+            -> (a -> [Alignable S.RealSrcSpan])+            -> [Change String]+        changes search toAlign = concat $+            map (align maxColumns) . map toAlign $ search (parsedModule module') +        configured :: [Change String]         configured = concat $-            [ changes cases (altToAlignable H.mergeSrcSpan)-            | cCases config-            ] ++-            [changes tlpats  matchToAlignable | cTopLevelPatterns config] ++-            [changes records fieldDeclToAlignable | cRecords config]--    in applyChanges configured ls+            [changes records recordToAlignable | cRecords config] +++            [changes everything (matchGroupToAlignable config)] in+    applyChanges configured ls
lib/Language/Haskell/Stylish/Step/Squash.hs view
@@ -1,4 +1,7 @@ --------------------------------------------------------------------------------+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE PatternGuards         #-}+{-# LANGUAGE TypeFamilies          #-} module Language.Haskell.Stylish.Step.Squash     ( step     ) where@@ -6,7 +9,8 @@  -------------------------------------------------------------------------------- import           Data.Maybe                      (mapMaybe)-import qualified Language.Haskell.Exts           as H+import qualified GHC.Hs                          as Hs+import qualified SrcLoc                          as S   --------------------------------------------------------------------------------@@ -17,46 +21,43 @@  -------------------------------------------------------------------------------- squash-    :: (H.Annotated l, H.Annotated r)-    => l H.SrcSpan -> r H.SrcSpan -> Maybe (Change String)-squash left right-  | H.srcSpanEndLine lAnn == H.srcSpanStartLine rAnn = Just $-        changeLine (H.srcSpanEndLine lAnn) $ \str ->-        let (pre, post) = splitAt (H.srcSpanEndColumn lAnn) str-        in  [trimRight pre ++ " " ++ trimLeft post]-  | otherwise = Nothing-  where-    lAnn = H.ann left-    rAnn = H.ann right+    :: (S.HasSrcSpan l, S.HasSrcSpan r)+    => l -> r -> Maybe (Change String)+squash left right = do+  lAnn <- toRealSrcSpan $ S.getLoc left+  rAnn <- toRealSrcSpan $ S.getLoc right+  if S.srcSpanEndLine lAnn == S.srcSpanStartLine rAnn ||+      S.srcSpanEndLine lAnn + 1 == S.srcSpanStartLine rAnn+    then Just $+          changeLine (S.srcSpanEndLine lAnn) $ \str ->+          let (pre, post) = splitAt (S.srcSpanEndCol lAnn) str+          in [trimRight pre ++ " " ++ trimLeft post]+    else Nothing   ---------------------------------------------------------------------------------squashFieldDecl :: H.FieldDecl H.SrcSpan -> Maybe (Change String)-squashFieldDecl (H.FieldDecl _ names type')+squashFieldDecl :: Hs.ConDeclField Hs.GhcPs -> Maybe (Change String)+squashFieldDecl (Hs.ConDeclField _ names type' _)   | null names = Nothing   | otherwise  = squash (last names) type'------------------------------------------------------------------------------------squashMatch :: H.Match H.SrcSpan -> Maybe (Change String)-squashMatch (H.InfixMatch _ _ _ _ _ _) = Nothing-squashMatch (H.Match _ name pats rhs _)-  | null pats = squash name        rhs-  | otherwise = squash (last pats) rhs+squashFieldDecl (Hs.XConDeclField x) = Hs.noExtCon x   ---------------------------------------------------------------------------------squashAlt :: H.Alt H.SrcSpan -> Maybe (Change String)-squashAlt (H.Alt _ pat rhs _) = squash pat rhs+squashMatch :: Hs.Match Hs.GhcPs (Hs.LHsExpr Hs.GhcPs) -> Maybe (Change String)+squashMatch (Hs.Match _ (Hs.FunRhs name _ _) [] grhss) = do+    body <- unguardedRhsBody grhss+    squash name body+squashMatch (Hs.Match _ _ pats grhss) = do+    body <- unguardedRhsBody grhss+    squash (last pats) body+squashMatch (Hs.XMatch x) = Hs.noExtCon x   -------------------------------------------------------------------------------- step :: Step-step = makeStep "Squash" $ \ls (module', _) ->-    let module'' = fmap H.srcInfoSpan module'-        changes  = concat-            [ mapMaybe squashAlt       (everything module'')-            , mapMaybe squashMatch     (everything module'')-            , mapMaybe squashFieldDecl (everything module'')-            ]-    in applyChanges changes ls+step = makeStep "Squash" $ \ls (module') ->+    let changes =+            mapMaybe squashFieldDecl (everything module') +++            mapMaybe squashMatch (everything module') in+    applyChanges changes ls
lib/Language/Haskell/Stylish/Step/UnicodeSyntax.hs view
@@ -10,17 +10,17 @@ import           Data.Map                                      (Map) import qualified Data.Map                                      as M import           Data.Maybe                                    (maybeToList)-import qualified Language.Haskell.Exts                         as H--+import           GHC.Hs.Binds+import           GHC.Hs.Extension                              (GhcPs)+import           GHC.Hs.Types -------------------------------------------------------------------------------- import           Language.Haskell.Stylish.Block import           Language.Haskell.Stylish.Editor+import           Language.Haskell.Stylish.Module import           Language.Haskell.Stylish.Step import           Language.Haskell.Stylish.Step.LanguagePragmas (addLanguagePragma) import           Language.Haskell.Stylish.Util - -------------------------------------------------------------------------------- unicodeReplacements :: Map String String unicodeReplacements = M.fromList@@ -39,7 +39,7 @@ replaceAll = map changeLine'   where     changeLine' (r, ns) = changeLine r $ \str -> return $-        applyChanges +        applyChanges             [ change (Block c ec) (const repl)             | (c, needle) <- sort ns             , let ec = c + length needle - 1@@ -54,33 +54,32 @@   ---------------------------------------------------------------------------------typeSigs :: H.Module H.SrcSpanInfo -> Lines -> [((Int, Int), String)]+typeSigs :: Module -> Lines -> [((Int, Int), String)] typeSigs module' ls =     [ (pos, "::")-    | H.TypeSig loc _ _  <- everything module' :: [H.Decl H.SrcSpanInfo]-    , (start, end)       <- infoPoints loc-    , pos                <- maybeToList $ between start end "::" ls+    | TypeSig _ funLoc typeLoc <- everything (rawModuleDecls $ moduleDecls module') :: [Sig GhcPs]+    , (_, funEnd)              <- infoPoints funLoc+    , (typeStart, _)           <- infoPoints [hsSigWcType typeLoc]+    , pos                      <- maybeToList $ between funEnd typeStart "::" ls     ] - ---------------------------------------------------------------------------------contexts :: H.Module H.SrcSpanInfo -> Lines -> [((Int, Int), String)]+contexts :: Module -> Lines -> [((Int, Int), String)] contexts module' ls =     [ (pos, "=>")-    | context      <- everything module' :: [H.Context H.SrcSpanInfo]-    , (start, end) <- infoPoints $ H.ann context-    , pos          <- maybeToList $ between start end "=>" ls+    | TypeSig _ _ typeLoc <- everything (rawModuleDecls $ moduleDecls module') :: [Sig GhcPs]+    , (start, end)        <- infoPoints [hsSigWcType typeLoc]+    , pos                 <- maybeToList $ between start end "=>" ls     ]   ---------------------------------------------------------------------------------typeFuns :: H.Module H.SrcSpanInfo -> Lines -> [((Int, Int), String)]+typeFuns :: Module -> Lines -> [((Int, Int), String)] typeFuns module' ls =     [ (pos, "->")-    | H.TyFun _ t1 t2 <- everything module'-    , let start = H.srcSpanEnd $ H.srcInfoSpan $ H.ann t1-    , let end   = H.srcSpanStart $ H.srcInfoSpan $ H.ann t2-    , pos <- maybeToList $ between start end "->" ls+    | TypeSig _ _ typeLoc <- everything (rawModuleDecls $ moduleDecls module') :: [Sig GhcPs]+    , (start, end)        <- infoPoints [hsSigWcType typeLoc]+    , pos                 <- maybeToList $ between start end "->" ls     ]  @@ -110,7 +109,7 @@  -------------------------------------------------------------------------------- step' :: Bool -> String -> Lines -> Module -> Lines-step' alp lg ls (module', _) = applyChanges changes ls+step' alp lg ls module' = applyChanges changes ls   where     changes = (if alp then addLanguagePragma lg "UnicodeSyntax" module' else []) ++         replaceAll perLine
lib/Language/Haskell/Stylish/Util.hs view
@@ -1,8 +1,8 @@ --------------------------------------------------------------------------------+{-# LANGUAGE LambdaCase    #-}+{-# LANGUAGE PatternGuards #-} module Language.Haskell.Stylish.Util-    ( nameToString-    , isOperator-    , indent+    ( indent     , padRight     , everything     , infoPoints@@ -13,22 +13,35 @@     , wrapMaybe     , wrapRestMaybe +    -- * Extra list functions     , withHead     , withInit     , withTail     , withLast+    , flagEnds++    , toRealSrcSpan++    , traceOutputtable+    , traceOutputtableM++    , unguardedRhsBody+    , rhsBody++    , getGuards     ) where   ---------------------------------------------------------------------------------import           Control.Arrow                 ((&&&), (>>>))-import           Data.Char                     (isAlpha, isSpace)+import           Data.Char                     (isSpace) import           Data.Data                     (Data) import qualified Data.Generics                 as G-import           Data.Maybe                    (fromMaybe, listToMaybe,-                                                maybeToList)+import           Data.Maybe                    (maybeToList) import           Data.Typeable                 (cast)-import qualified Language.Haskell.Exts         as H+import           Debug.Trace                   (trace)+import qualified GHC.Hs                        as Hs+import qualified Outputable+import qualified SrcLoc                        as S   --------------------------------------------------------------------------------@@ -36,18 +49,6 @@   ---------------------------------------------------------------------------------nameToString :: H.Name l -> String-nameToString (H.Ident _ str)  = str-nameToString (H.Symbol _ str) = str------------------------------------------------------------------------------------isOperator :: H.Name l -> Bool-isOperator = fromMaybe False-    . (fmap (not . isAlpha) . listToMaybe)-    . nameToString---------------------------------------------------------------------------------- indent :: Int -> String -> String indent len = (indentPrefix len ++) @@ -68,8 +69,16 @@   ---------------------------------------------------------------------------------infoPoints :: H.SrcSpanInfo -> [((Int, Int), (Int, Int))]-infoPoints = H.srcInfoPoints >>> map (H.srcSpanStart &&& H.srcSpanEnd)+infoPoints :: [S.Located pass] -> [((Int, Int), (Int, Int))]+infoPoints = fmap (helper . S.getLoc)+  where+    helper :: S.SrcSpan -> ((Int, Int), (Int, Int))+    helper (S.RealSrcSpan s) = do+               let+                start = S.realSrcSpanStart s+                end = S.realSrcSpanEnd s+               ((S.srcLocLine start, S.srcLocCol start), (S.srcLocLine end, S.srcLocCol end))+    helper _                   = ((-1,-1), (-1,-1))   --------------------------------------------------------------------------------@@ -117,7 +126,7 @@        -> Lines     -- ^ Resulting lines noWrap leading _ind = noWrap' leading   where-    noWrap' ss [] = [ss]+    noWrap' ss []         = [ss]     noWrap' ss (str:strs) = noWrap' (ss ++ " " ++ str) strs  @@ -181,7 +190,78 @@ withInit _ [x]      = [x] withInit f (x : xs) = f x : withInit f xs + -------------------------------------------------------------------------------- withTail :: (a -> a) -> [a] -> [a] withTail _ []       = [] withTail f (x : xs) = x : map f xs++++--------------------------------------------------------------------------------+-- | Utility for traversing through a list and knowing when you're at the+-- first and last element.+flagEnds :: [a] -> [(a, Bool, Bool)]+flagEnds = \case+    [] -> []+    [x] -> [(x, True, True)]+    x : y : zs -> (x, True, False) : go (y : zs)+  where+    go (x : y : zs) = (x, False, False) : go (y : zs)+    go [x]          = [(x, False, True)]+    go []           = []+++--------------------------------------------------------------------------------+traceOutputtable :: Outputable.Outputable a => String -> a -> b -> b+traceOutputtable title x =+    trace (title ++ ": " ++ (Outputable.showSDocUnsafe $ Outputable.ppr x))+++--------------------------------------------------------------------------------+traceOutputtableM :: (Outputable.Outputable a, Monad m) => String -> a -> m ()+traceOutputtableM title x = traceOutputtable title x $ pure ()+++--------------------------------------------------------------------------------+-- take the (Maybe) RealSrcSpan out of the SrcSpan+toRealSrcSpan :: S.SrcSpan -> Maybe S.RealSrcSpan+toRealSrcSpan (S.RealSrcSpan s) = Just s+toRealSrcSpan _                 = Nothing+++--------------------------------------------------------------------------------+-- Utility: grab the body out of guarded RHSs if it's a single unguarded one.+unguardedRhsBody :: Hs.GRHSs Hs.GhcPs a -> Maybe a+unguardedRhsBody (Hs.GRHSs _ [grhs] _)+    | Hs.GRHS _ [] body <- S.unLoc grhs = Just body+unguardedRhsBody _ = Nothing+++-- Utility: grab the body out of guarded RHSs+rhsBody :: Hs.GRHSs Hs.GhcPs a -> Maybe a+rhsBody (Hs.GRHSs _ [grhs] _)+    | Hs.GRHS _ _ body <- S.unLoc grhs = Just body+rhsBody _ = Nothing+++--------------------------------------------------------------------------------+-- get guards in a guarded rhs of a Match+getGuards :: Hs.Match Hs.GhcPs (Hs.LHsExpr Hs.GhcPs) -> [Hs.GuardLStmt Hs.GhcPs]+getGuards (Hs.Match _ _ _ grhss) =+  let+    lgrhs = getLocGRHS grhss -- []+    grhs  = map S.unLoc lgrhs+  in+    concatMap getGuardLStmts grhs+getGuards (Hs.XMatch x) = Hs.noExtCon x+++getLocGRHS :: Hs.GRHSs Hs.GhcPs (Hs.LHsExpr Hs.GhcPs) -> [Hs.LGRHS Hs.GhcPs (Hs.LHsExpr Hs.GhcPs)]+getLocGRHS (Hs.GRHSs _ guardeds _) = guardeds+getLocGRHS (Hs.XGRHSs x)           = Hs.noExtCon x+++getGuardLStmts :: Hs.GRHS Hs.GhcPs (Hs.LHsExpr Hs.GhcPs) -> [Hs.GuardLStmt Hs.GhcPs]+getGuardLStmts (Hs.GRHS _ guards _) = guards+getGuardLStmts (Hs.XGRHS x)         = Hs.noExtCon x
src/Main.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -------------------------------------------------------------------------------- module Main     ( main@@ -5,15 +6,18 @@   ---------------------------------------------------------------------------------import           Control.Monad            (forM_, unless)+import           Control.Monad            (forM_, unless, when) import qualified Data.ByteString.Char8    as BC8-import           Data.Monoid              ((<>)) import           Data.Version             (showVersion) import qualified Options.Applicative      as OA import           System.Exit              (exitFailure) import qualified System.IO                as IO import qualified System.IO.Strict         as IO.Strict +--------------------------------------------------------------------------------+#if __GLASGOW_HASKELL__ < 808+import           Data.Monoid              ((<>))+#endif  -------------------------------------------------------------------------------- import           Language.Haskell.Stylish@@ -112,7 +116,10 @@             forM_ steps $ \s -> verbose' $ "Enabled " ++ stepName s ++ " step"             verbose' $ "Extra language extensions: " ++                 show (configLanguageExtensions conf)-            mapM_ (file sa conf) $ files' filesR+            res <- foldMap (file sa conf) (files' filesR)++            verbose' $ "Exit code behavior: " ++ show (configExitCode conf)+            when (configExitCode conf == ErrorOnFormatExitBehavior && res == DidFormat) exitFailure   where     verbose' = makeVerbose (saVerbose sa)     files' x = case (saRecursive sa, null x) of@@ -120,16 +127,33 @@       (_,True)    -> [Nothing]  -- Involving IO.stdin.       (_,False)   -> map Just x -- Process available files. +data FormattingResult+  = DidFormat+  | NoChange+  deriving (Eq) +instance Semigroup FormattingResult where+  _ <> DidFormat = DidFormat+  DidFormat <> _ = DidFormat+  _ <> _ = NoChange++instance Monoid FormattingResult where+  mempty = NoChange+ -------------------------------------------------------------------------------- -- | Processes a single file, or stdin if no filepath is given-file :: StylishArgs -> Config -> Maybe FilePath -> IO ()+file :: StylishArgs -> Config -> Maybe FilePath -> IO FormattingResult file sa conf mfp = do     contents <- maybe getContents readUTF8File mfp-    let result = runSteps (configLanguageExtensions conf)-            mfp (configSteps conf) $ lines contents+    let+      inputLines =+        lines contents+      result =+        runSteps (configLanguageExtensions conf) mfp (configSteps conf) inputLines     case result of-        Right ok  -> write contents $ unlines ok+        Right ok  -> do+            write contents (unlines ok)+            pure $ if ok /= inputLines then DidFormat else NoChange         Left  err -> do             IO.hPutStrLn IO.stderr err             exitFailure
stylish-haskell.cabal view
@@ -1,5 +1,5 @@ Name:          stylish-haskell-Version:       0.11.0.3+Version:       0.12.0.0 Synopsis:      Haskell code prettifier Homepage:      https://github.com/jaspervdj/stylish-haskell License:       BSD3@@ -30,8 +30,12 @@    Exposed-modules:     Language.Haskell.Stylish+    Language.Haskell.Stylish.GHC+    Language.Haskell.Stylish.Module+    Language.Haskell.Stylish.Printer     Language.Haskell.Stylish.Step.Data     Language.Haskell.Stylish.Step.Imports+    Language.Haskell.Stylish.Step.ModuleHeader     Language.Haskell.Stylish.Step.LanguagePragmas     Language.Haskell.Stylish.Step.SimpleAlign     Language.Haskell.Stylish.Step.Squash@@ -46,6 +50,7 @@     Language.Haskell.Stylish.Config.Cabal     Language.Haskell.Stylish.Config.Internal     Language.Haskell.Stylish.Editor+    Language.Haskell.Stylish.Ordering     Language.Haskell.Stylish.Parse     Language.Haskell.Stylish.Step     Language.Haskell.Stylish.Util@@ -61,14 +66,17 @@     directory        >= 1.2.3  && < 1.4,     filepath         >= 1.1    && < 1.5,     file-embed       >= 0.0.10 && < 0.1,-    haskell-src-exts >= 1.18   && < 1.24,+    ghc-lib-parser   >= 8.10   && < 8.12,     mtl              >= 2.0    && < 2.3,-    semigroups       >= 0.18   && < 0.20,     syb              >= 0.3    && < 0.8,     text             >= 1.2    && < 1.3,     HsYAML-aeson     >=0.2.0   && < 0.3,     HsYAML           >=0.2.0   && < 0.3 +  if impl(ghc < 8.0)+    Build-depends:+      semigroups     >= 0.18   && < 0.20+ Executable stylish-haskell   Ghc-options:      -Wall   Hs-source-dirs:   src@@ -77,7 +85,7 @@    Build-depends:     stylish-haskell,-    strict               >= 0.3  && < 0.4,+    strict               >= 0.3  && < 0.5,     optparse-applicative >= 0.12 && < 0.16,     -- Copied from regular dependencies...     aeson            >= 0.6    && < 1.6,@@ -88,7 +96,7 @@     directory        >= 1.2.3  && < 1.4,     filepath         >= 1.1    && < 1.5,     file-embed       >= 0.0.10 && < 0.1,-    haskell-src-exts >= 1.18   && < 1.24,+    ghc-lib-parser   >= 8.10   && < 8.12,     mtl              >= 2.0    && < 2.3,     syb              >= 0.3    && < 0.8,     HsYAML-aeson     >=0.2.0   && < 0.3,@@ -110,13 +118,20 @@     Language.Haskell.Stylish.Config.Internal     Language.Haskell.Stylish.Config.Tests     Language.Haskell.Stylish.Editor+    Language.Haskell.Stylish.GHC+    Language.Haskell.Stylish.Ordering+    Language.Haskell.Stylish.Module     Language.Haskell.Stylish.Parse     Language.Haskell.Stylish.Parse.Tests+    Language.Haskell.Stylish.Printer     Language.Haskell.Stylish.Step     Language.Haskell.Stylish.Step.Imports     Language.Haskell.Stylish.Step.Imports.Tests+    Language.Haskell.Stylish.Step.Imports.FelixTests     Language.Haskell.Stylish.Step.Data     Language.Haskell.Stylish.Step.Data.Tests+    Language.Haskell.Stylish.Step.ModuleHeader+    Language.Haskell.Stylish.Step.ModuleHeader.Tests     Language.Haskell.Stylish.Step.LanguagePragmas     Language.Haskell.Stylish.Step.LanguagePragmas.Tests     Language.Haskell.Stylish.Step.SimpleAlign@@ -144,12 +159,12 @@     aeson            >= 0.6    && < 1.6,     base             >= 4.8    && < 5,     bytestring       >= 0.9    && < 0.11,-    Cabal            >= 2.4    && < 3.1,+    Cabal            >= 2.4    && < 3.3,     containers       >= 0.3    && < 0.7,     directory        >= 1.2.3  && < 1.4,     filepath         >= 1.1    && < 1.5,     file-embed       >= 0.0.10 && < 0.1,-    haskell-src-exts >= 1.18   && < 1.24,+    ghc-lib-parser   >= 8.10   && < 8.12,     mtl              >= 2.0    && < 2.3,     syb              >= 0.3    && < 0.8,     text             >= 1.2    && < 1.3,
tests/Language/Haskell/Stylish/Config/Tests.hs view
@@ -153,6 +153,7 @@   , "      first_field: \"indent 2\""   , "      field_comment: 2"   , "      deriving: 4"+  , "      via: \"indent 2\""   , "columns: 110"   , "language_extensions:"   , "  - TemplateHaskell"
tests/Language/Haskell/Stylish/Parse/Tests.hs view
@@ -6,7 +6,8 @@ -------------------------------------------------------------------------------- import           Test.Framework                 (Test, testGroup) import           Test.Framework.Providers.HUnit (testCase)-import           Test.HUnit                     (Assertion, assert)+import           Test.HUnit                     (Assertion, assertFailure)+import           GHC.Stack                      (HasCallStack, withFrozenCallStack)   --------------------------------------------------------------------------------@@ -33,18 +34,18 @@  -------------------------------------------------------------------------------- testShebangExt :: Assertion-testShebangExt = assert $ isRight $ parseModule [] Nothing input-                         where-                           input = unlines-                               [ "#!env runghc"-                               , "{-# LANGUAGE CPP #-}"-                               , "#define foo bar \\"-                               , "             qux"-                               ]+testShebangExt = returnsRight $ parseModule [] Nothing input+  where+    input = unlines+      [ "#!env runghc"+      , "{-# LANGUAGE CPP #-}"+      , "#define foo bar \\"+      , "             qux"+      ]  -------------------------------------------------------------------------------- testBom :: Assertion-testBom = assert $ isRight $ parseModule [] Nothing input+testBom = returnsRight $ parseModule [] Nothing input   where     input = unlines         [ '\xfeff' : "foo :: Int"@@ -54,13 +55,13 @@  -------------------------------------------------------------------------------- testExtraExtensions :: Assertion-testExtraExtensions = assert $ isRight $+testExtraExtensions = returnsRight $     parseModule ["TemplateHaskell"] Nothing "$(foo)"   -------------------------------------------------------------------------------- testMultilineCpp :: Assertion-testMultilineCpp = assert $ isRight $ parseModule [] Nothing $ unlines+testMultilineCpp = returnsRight $ parseModule [] Nothing $ unlines     [ "{-# LANGUAGE CPP #-}"     , "#define foo bar \\"     , "             qux"@@ -69,7 +70,7 @@  -------------------------------------------------------------------------------- testHaskell2010 :: Assertion-testHaskell2010 = assert $ isRight $ parseModule [] Nothing $ unlines+testHaskell2010 = returnsRight $ parseModule [] Nothing $ unlines     [ "{-# LANGUAGE Haskell2010 #-}"     , "module X where"     , "foo x | Just y <- x = y"@@ -78,7 +79,7 @@  -------------------------------------------------------------------------------- testShebang :: Assertion-testShebang = assert $ isRight $ parseModule [] Nothing $ unlines+testShebang = returnsRight $ parseModule [] Nothing $ unlines     [ "#!runhaskell"     , "module Main where"     , "main = return ()"@@ -87,7 +88,7 @@ --------------------------------------------------------------------------------  testShebangDouble :: Assertion-testShebangDouble = assert $ isRight $ parseModule [] Nothing $ unlines+testShebangDouble = returnsRight $ parseModule [] Nothing $ unlines     [ "#!nix-shell"     , "#!nix-shell -i runhaskell -p haskellPackages.ghc"     , "module Main where"@@ -100,7 +101,7 @@ -- enabled for parsing, even when the pragma is absent.  testGADTs :: Assertion-testGADTs = assert $ isRight $ parseModule [] Nothing $ unlines+testGADTs = returnsRight $ parseModule [] Nothing $ unlines   [ "module Main where"   , "data SafeList a b where"   , "  Nil :: SafeList a Empty"@@ -108,36 +109,35 @@   ]  testKindSignatures :: Assertion-testKindSignatures = assert $ isRight $ parseModule [] Nothing $ unlines+testKindSignatures = returnsRight $ parseModule [] Nothing $ unlines   [ "module Main where"   , "data D :: * -> * -> * where"   , "  D :: a -> b -> D a b"   ]  testStandaloneDeriving :: Assertion-testStandaloneDeriving = assert $ isRight $ parseModule [] Nothing $ unlines+testStandaloneDeriving = returnsRight $ parseModule [] Nothing $ unlines   [ "module Main where"   , "deriving instance Show MyType"   ]  testUnicodeSyntax :: Assertion-testUnicodeSyntax = assert $ isRight $ parseModule [] Nothing $ unlines+testUnicodeSyntax = returnsRight $ parseModule [] Nothing $ unlines   [ "module Main where"   , "monadic ∷ (Monad m) ⇒ m a → m a"   , "monadic = id"   ]  testXmlSyntaxRegression :: Assertion-testXmlSyntaxRegression = assert $ isRight $ parseModule [] Nothing $ unlines+testXmlSyntaxRegression = returnsRight $ parseModule [] Nothing $ unlines   [ "smaller a b = a <b"   ]  testMagicHashRegression :: Assertion-testMagicHashRegression = assert $ isRight $ parseModule [] Nothing $ unlines+testMagicHashRegression = returnsRight $ parseModule [] Nothing $ unlines   [ "xs = \"foo\"#|1#|'a'#|bar#|Nil"   ]  ---------------------------------------------------------------------------------isRight :: Either a b -> Bool-isRight (Right _) = True-isRight _         = False+returnsRight :: HasCallStack => Show a => Either a b -> Assertion+returnsRight action = withFrozenCallStack $ either (assertFailure . show) mempty action
tests/Language/Haskell/Stylish/Step/Data/Tests.hs view
@@ -35,6 +35,36 @@     , testCase "case 22" case22     , testCase "case 23" case23     , testCase "case 24" case24+    , testCase "case 25" case25+    , testCase "case 26" case26+    , testCase "case 27" case27+    , testCase "case 28" case28+    , testCase "case 29" case29+    , testCase "case 30" case30+    , testCase "case 31" case31+    , testCase "case 32" case32+    , testCase "case 33" case33+    , testCase "case 34" case34+    , testCase "case 35" case35+    , testCase "case 36" case36+    , testCase "case 37" case37+    , testCase "case 38" case38+    , testCase "case 39" case39+    , testCase "case 40" case40+    , testCase "case 41" case41+    , testCase "case 42" case42+    , testCase "case 43" case43+    , testCase "case 44" case44+    , testCase "case 45" case45+    , testCase "case 46" case46+    , testCase "case 47" case47+    , testCase "case 48" case48+    , testCase "case 49" case49+    , testCase "case 50" case50+    , testCase "case 51" case51+    , testCase "case 52" case52+    , testCase "case 53" case53+    , testCase "case 54" case54     ]  case00 :: Assertion@@ -165,7 +195,7 @@     expected = input  case08 :: Assertion-case08 = input @=? testStep (step sameSameStyle) input+case08 = expected @=? testStep (step sameSameStyle) input   where     input = unlines       [ "module Herp where"@@ -173,6 +203,11 @@       , "data Phantom a ="       , "  Phantom"       ]+    expected = unlines+      [ "module Herp where"+      , ""+      , "data Phantom a = Phantom"+      ]  case09 :: Assertion case09 = expected @=? testStep (step indentIndentStyle4) input@@ -333,7 +368,8 @@       , ""       , "data Foo"       , "  = Foo"-      , "      { a :: Int -- ^ comment"+      , "      { a :: Int"+      , "        -- ^ comment"       , "      }"       ] @@ -520,17 +556,661 @@        , "  deriving (ToJSON)"        ] +case25 :: Assertion+case25 = expected @=? testStep (step indentIndentStyle { cBreakSingleConstructors = False }) input+  where+    input = unlines+       [ "data Foo a"+       , "  = Foo { a :: Int,"+       , "          a2 :: String"+       , "          -- ^ some haddock"+       , "        }"+       , "  deriving (Eq, Show)"+       , "  deriving (ToJSON)"+       ]++    expected = unlines+       [ "data Foo a = Foo"+       , "  { a :: Int"+       , "  , a2 :: String"+       , "    -- ^ some haddock"+       , "  }"+       , "  deriving (Eq, Show)"+       , "  deriving (ToJSON)"+       ]++case26 :: Assertion+case26 = expected @=? testStep (step indentIndentStyle) input+  where+    input = unlines+      [ "module Herp where"+      , ""+      , "data Foo = Foo { a :: Int } deriving (FromJSON) via Bla Foo"+      ]++    expected = unlines+       [ "module Herp where"+       , ""+       , "data Foo"+       , "  = Foo"+       , "      { a :: Int"+       , "      }"+       , "  deriving (FromJSON) via Bla Foo"+       ]++case27 :: Assertion+case27 = expected @=? testStep (step sameIndentStyle { cBreakEnums = True }) input+  where+    input = unlines+      [ "module Herp where"+      , ""+      , "data Foo = Foo | Bar | Baz deriving (Eq, Show)"+      ]++    expected = unlines+      [ "module Herp where"+      , ""+      , "data Foo"+      , "  = Foo"+      , "  | Bar"+      , "  | Baz"+      , "  deriving (Eq, Show)"+      ]++case28 :: Assertion+case28 = expected @=? testStep (step sameIndentStyle { cBreakEnums = True }) input+  where+    input = unlines+      [ "module Some.Types where"+      , ""+      , "newtype BankCode = BankCode {"+      , "    unBankCode :: Text"+      , "  }"+      , "  deriving stock (Generic, Eq, Show)"+      , "  deriving anyclass (Newtype)"+      , ""+      , "newtype CheckDigit = CheckDigit { unCheckDigit :: Text }"+      , "  deriving stock (Generic, Eq, Show)"+      , "  deriving anyclass (Newtype)"+      , ""+      , "newtype WrappedInt = WrappedInt Int"+      , "  deriving stock (Generic, Eq, Show)"+      , "  deriving anyclass (Newtype)"+      , ""+      , "data MandateStatus"+      , "  = Approved"+      , "  | Failed"+      , "  | UserCanceled"+      , "  | Inactive"+      , "  deriving stock (Generic, Show, Eq, Enum, Bounded)"+      , "  deriving (ToJSON, FromJSON) via SnakeCaseCapsEnumEncoding MandateStatus"+      ]++    expected = unlines+      [ "module Some.Types where"+      , ""+      , "newtype BankCode = BankCode { unBankCode :: Text }"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving anyclass (Newtype)"+      , ""+      , "newtype CheckDigit = CheckDigit { unCheckDigit :: Text }"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving anyclass (Newtype)"+      , ""+      , "newtype WrappedInt = WrappedInt Int"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving anyclass (Newtype)"+      , ""+      , "data MandateStatus"+      , "  = Approved"+      , "  | Failed"+      , "  | UserCanceled"+      , "  | Inactive"+      , "  deriving stock (Bounded, Enum, Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON) via SnakeCaseCapsEnumEncoding MandateStatus"+      ]++case29 :: Assertion+case29 = expected @=? testStep (step sameIndentStyle) input+  where+    input = unlines+      [ "module Some.Types where"+      , ""+      , "data NonEmpty a"+      , "  = a :| [a]"+      ]++    expected = unlines+      [ "module Some.Types where"+      , ""+      , "data NonEmpty a = a :| [a]"+      ]++case30 :: Assertion+case30 = expected @=? testStep (step sameIndentStyle { cBreakEnums = True }) input+  where+    expected = input+    input = unlines+      [ "data ReasonCode"+      , "  = MissingTenantId"+      , "  -- Transaction errors:"+      , "  | TransactionDoesNotExist"+      , "  | TransactionAlreadyExists"+      , "  -- Engine errors:"+      , "  | EnginePersistenceError"+      , "  | EngineValidationError"+      , "  -- | Transaction was created in Info mode"+      , "  | RegisteredByNetworkEngine"+      , "  -- | Transaction was created in Routing mode"+      , "  | SentToNetworkEngine"+      , "  -- Network connection reasons:"+      , "  | SentToNetworkConnection"+      , "  | ReceivedByNetworkConnection"+      , "  | ValidatedByNetworkConnection"+      ]+++case31 :: Assertion+case31 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True }) input+  where+    expected = input+    input = unlines+      [ "data ConfiguredLogger"+      , "  -- | Logs to file"+      , "  = LogTo FilePath"+      , "  -- | Logs to stdout"+      , "  | LogToConsole"+      , "  -- | No logging, discards all messages"+      , "  | NoLogging"+      , "  deriving stock (Generic, Show)"+      ]++case32 :: Assertion+case32 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True }) input+  where+    expected = input+    input = unlines+      [ "data RejectionReason"+      , "  -- InvalidState"+      , "  = CancellationFailed"+      , "  | TotalAmountConfirmationInvalid"+      , "  -- InvalidApiUsage"+      , "  | AccessTokenNotActive"+      , "  | VersionNotFound"+      , "  -- ValidationFailed"+      , "  | BankAccountExists"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON) via SnakeCaseLowercaseEnumEncoding RejectionReason"+      ]++case33 :: Assertion+case33 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True, cBreakSingleConstructors = False }) input+  where+    input = unlines+      [ "module Some.Types where"+      , ""+      , "newtype NonEmpty a = NonEmpty { unNonEmpty :: a }"+      ]++    expected = unlines+      [ "module Some.Types where"+      , ""+      , "newtype NonEmpty a"+      , "  = NonEmpty { unNonEmpty :: a }"+      ]++case34 :: Assertion+case34 = expected @=? testStep (step indentIndentStyle { cVia = Indent 2 }) input+  where+    input = unlines+      [ "module Some.Types where"+      , ""+      , "newtype NonEmpty a = NonEmpty { unNonEmpty :: a }"+      , "     deriving (ToJSON, FromJSON) via Something Magic (NonEmpty a)"+      ]++    expected = unlines+      [ "module Some.Types where"+      , ""+      , "newtype NonEmpty a"+      , "  = NonEmpty { unNonEmpty :: a }"+      , "  deriving (FromJSON, ToJSON)"+      , "    via Something Magic (NonEmpty a)"+      ]++case35 :: Assertion+case35 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True, cBreakSingleConstructors = False }) input+  where+    input = unlines+      [ "module Some.Types where"+      , ""+      , "data Foo = Foo"+      , "  { _transfer :: MonetaryAmount"+      , "      -> TransactionId"+      , "      -> m (Either CreditTransferError TransactionId)"+      , "  }"+      ]++    expected = unlines+      [ "module Some.Types where"+      , ""+      , "data Foo = Foo"+      , "  { _transfer :: MonetaryAmount -> TransactionId -> m (Either CreditTransferError TransactionId)"+      , "  }"+      ]++case36 :: Assertion+case36 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True, cBreakSingleConstructors = False }) input+  where+    input = unlines+      [ "module Some.Types where"+      , ""+      , "data Foo = Foo"+      , "  { _transfer :: (a -> b)"+      , "      -> TransactionId"+      , "      -> m (Either CreditTransferError TransactionId)"+      , "  }"+      ]++    expected = unlines+      [ "module Some.Types where"+      , ""+      , "data Foo = Foo"+      , "  { _transfer :: (a -> b) -> TransactionId -> m (Either CreditTransferError TransactionId)"+      , "  }"+      ]++case37 :: Assertion+case37 = expected @=? testStep (step indentIndentStyle { cVia = Indent 2 }) input+  where+    input = unlines+      [ "module Some.Types where"+      , ""+      , "newtype UndoFlowData"+      , "  = UndoFlowData { flowDataDetails :: FlowDataDetails }"+      , "  deriving stock (Generic, Eq, Show)"+      , "  deriving (ToJSON, FromJSON)"+      , "    via AddConstTextFields '[\"type0\" := \"undo\","+      , "                             \"type1\" := \"undo\","+      , "                     \"reversal_indicator\" := \"Undo\"] FlowDataDetails"+      ]++    expected = unlines+      [ "module Some.Types where"+      , ""+      , "newtype UndoFlowData"+      , "  = UndoFlowData { flowDataDetails :: FlowDataDetails }"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON)"+      , "    via AddConstTextFields '[\"type0\" := \"undo\", \"type1\" := \"undo\", \"reversal_indicator\" := \"Undo\"] FlowDataDetails"+      ]++case38 :: Assertion+case38 = expected @=? testStep (step indentIndentStyle { cVia = Indent 2 }) input+  where+    input = unlines+      [ "data Flat = Flat"+      , "  { foo :: Int"+      , "  , bar :: Text"+      , "  , baz :: Double"+      , "  , qux :: Bool"+      , "  }"+      , "  deriving stock (Generic, Show, Eq)"+      , "  deriving (FromJSON, ToJSON)"+      , "    via GenericEncoded"+      , "      '[ FieldLabelModifier :="+      , "        '[ \"foo\" ==> \"nestFoo#foo\""+      , "         , \"bar\" ==> \"nestBar#bar\""+      , "         , \"baz\" ==> \"nestFoo#baz\""+      , "         ]"+      , "       ]"+      , "      Flat"+      ]++    expected = unlines+      [ "data Flat"+      , "  = Flat"+      , "      { foo :: Int"+      , "      , bar :: Text"+      , "      , baz :: Double"+      , "      , qux :: Bool"+      , "      }"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON)"+      , "    via GenericEncoded '[FieldLabelModifier := '[\"foo\" ==> \"nestFoo#foo\", \"bar\" ==> \"nestBar#bar\", \"baz\" ==> \"nestFoo#baz\"]] Flat"+      ]++case39 :: Assertion+case39 = expected @=? testStep (step indentIndentStyle { cVia = Indent 2 }) input+  where+    input = unlines+      [ "data CreditTransfer = CreditTransfer"+      , "  { nestedCreditorInfo :: CreditorInfo"+      , "  }"+      , "  deriving stock (Show, Eq, Generic)"+      , "  deriving (ToJSON, FromJSON) via"+      , "    ( UntaggedEncoded NordeaCreditTransfer"+      , "    & AddConstTextFields"+      , "      '[ \"request_type\" ':= \"credit_transfer\""+      , "       , \"provider\" ':= \"nordea\""+      , "       ]"+      , "    & FlattenFields '[\"nested_creditor_info\"]"+      , "    & RenameKeys"+      , "        '[ \"nested_creditor_info.creditor_agent_bic\" ==> \"creditor_agent_bic\""+      , "         , \"nested_creditor_info.creditor_iban\" ==> \"creditor_iban\""+      , "         , \"nested_creditor_info.creditor_name\" ==> \"creditor_name\""+      , "         , \"nested_creditor_info.creditor_account\" ==> \"creditor_account\""+      , "         ]"+      , "    )"+      ]++    expected = unlines+      [ "data CreditTransfer"+      , "  = CreditTransfer"+      , "      { nestedCreditorInfo :: CreditorInfo"+      , "      }"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON)"+      , "    via (UntaggedEncoded NordeaCreditTransfer & AddConstTextFields '[\"request_type\" ':= \"credit_transfer\", \"provider\" ':= \"nordea\"] & FlattenFields '[\"nested_creditor_info\"] & RenameKeys '[\"nested_creditor_info.creditor_agent_bic\" ==> \"creditor_agent_bic\", \"nested_creditor_info.creditor_iban\" ==> \"creditor_iban\", \"nested_creditor_info.creditor_name\" ==> \"creditor_name\", \"nested_creditor_info.creditor_account\" ==> \"creditor_account\"])"+      ]++case40 :: Assertion+case40 = expected @=? testStep (step indentIndentStyle { cBreakSingleConstructors = False }) input+  where+    input = unlines+      [ "module X where"+      , ""+      , "data a :==> b    ="+      , "                  Arr a b"+      ]++    expected = unlines+      [ "module X where"+      , ""+      , "data a :==> b = Arr a b"+      ]++case41 :: Assertion+case41 = expected @=? testStep (step indentIndentStyle) input+  where+    input = expected++    expected = unlines+      [ "module X where"+      , ""+      , "data Callback"+      , "  -- | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor"+      , "  --   incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis"+      , "  --   nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."+      , "  --   Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore"+      , "  --   eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,"+      , "  --   sunt in culpa qui officia deserunt mollit anim id est laborum."+      , "  = KafkaTopic"+      , "      { callbackTopic :: CallbackTopic"+      , "        -- ^ Name of topic to send updates to"+      , "      , callbackFormat :: CallbackFormat"+      , "        -- ^ The format used to send these updates"+      , "      }"+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON) via IdiomaticWithDescription CallbackDesc Callback"+      , "  deriving (HasGen) via Generically Callback"+      , "  deriving (FromField) via JsonField Callback"+      ]++case42 :: Assertion+case42 = expected @=? testStep (step indentIndentStyle) input+  where+    input = expected++    expected = unlines+      [ "module X where"+      , ""+      , "data SignupError"+      , "  = IdempotencyConflict"+      , "  | ValidationError Text -- TODO: might be a sumtype of possible error codes"+      , "  deriving stock (Eq, Generic, Show)"+      ]++case43 :: Assertion+case43 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True, cBreakSingleConstructors = False }) input+  where+    input = expected++    expected = unlines+      [ "module X where"+      , ""+      , "data CallbackResult"+      , "  -- | Callback successfully sent"+      , "  = Success"+      , "  -- | Kafka error received"+      , "  | KafkaIssue KafkaError"+      , "  deriving (Eq, Show)"+      ]++-- This test showcases a difficult to solve issue. If the comment is in a+-- deriving clause, it's very hard to guess the correct position of the entire+-- block. E.g. the deriving clause itself has the wrong position. However, if+-- we look at all deriving clauses we know where they start and end.+--+-- This means that we've needed to make the decision to put all inline comments+-- before the deriving clause itself+case44 :: Assertion+case44 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True, cBreakSingleConstructors = False, cVia = Indent 2 }) input+  where+    input = unlines+      [ "module X where"+      , ""+      , " data CreditTransfer = CreditTransfer"+      , "   { amount :: Amount -- ^ 1 <= amount <= 999_999_999_999"+      , "   , date :: Day"+      , "   , accountNumber :: Account"+      , "   }"+      , "   deriving stock (Eq, Generic, Show)"+      , "   deriving (FromJSON, ToJSON) via"+      , "     AddConstTextFields"+      , "       '[\"notification_type\" ':= \"credit_transaction\""+      , "         -- Note that the bcio name has \"transaction\""+      , "         -- rather than \"transfer\""+      , "        ]"+      , "        (UntaggedEncoded CreditTransfer)"+      ]+    expected = unlines+      [ "module X where"+      , ""+      , "data CreditTransfer = CreditTransfer"+      , "  { amount :: Amount"+      , "    -- ^ 1 <= amount <= 999_999_999_999"+      , "  , date :: Day"+      , "  , accountNumber :: Account"+      , "  }"+      , "  -- Note that the bcio name has \"transaction\""+      , "  -- rather than \"transfer\""+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON)"+      , "    via AddConstTextFields '[\"notification_type\" ':= \"credit_transaction\"] (UntaggedEncoded CreditTransfer)"+      ]++case45 :: Assertion+case45 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True, cBreakSingleConstructors = False, cVia = Indent 2 }) input+  where+    input = expected+    expected = unlines+      [ "module X where"+      , ""+      , "data CreditTransfer = CreditTransfer"+      , "  { amount :: Amount"+      , "    -- ^ 1 <= amount <= 999_999_999_999"+      , "  , date :: Day"+      , "  , accountNumber :: Account"+      , "  }"+      , "  -- Note that the bcio name has \"transaction\""+      , "  -- rather than \"transfer\""+      , "  deriving stock (Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON)"+      , "    via AddConstTextFields '[\"notification_type\" ':= \"credit_transaction\"] (UntaggedEncoded CreditTransfer)"+      ]++case46 :: Assertion+case46 = expected @=? testStep (step indentIndentStyle { cBreakEnums = True, cBreakSingleConstructors = False, cVia = Indent 2 }) input+  where+    input = expected+    expected = unlines+      [ "module X where"+      , ""+      , "-- | A format detailing which encoding to use for the settlement events"+      , "data CallbackFormat"+      , "  -- | The Avro schema is to be used"+      , "  = AvroEngineEvent"+      , "  deriving (Bounded, Enum, Eq, Generic, Show)"+      , "  deriving (FromJSON, ToJSON)"+      , "    via TypeTaggedWithDescription FormatDesc CallbackFormat"+      , "  deriving (HasGen)"+      , "    via EnumBounded CallbackFormat"+      ]++case47 :: Assertion+case47 = expected @=? testStep (step indentIndentStyle) input+  where+    input = expected+    expected = unlines+      [ "module X where"+      , ""+      , "-- | A GADT example"+      , "data T a where"+      , "  D1 :: Int -> T String"+      , "  D2 :: T Bool"+      , "  D3 :: (a, a) -> T [a]"+      ]++case48 :: Assertion+case48 = expected @=? testStep (step indentIndentStyle) input+  where+    input = expected+    expected = unlines+      [ "module X where"+      , ""+      , "-- | A GADT example"+      , "data T a where"+      , "  D1 :: Int -> T String"+      , "  D2 :: T Bool"+      , "  D3 :: forall a. (Eq a, Bounded a) => (a, a) -> T [a]"+      ]++case49 :: Assertion+case49 = expected @=? testStep (step indentIndentStyle) input+  where+    input = expected+    expected = unlines+      [ "module X where"+      , ""+      , "-- | A GADT example"+      , "data T a where"+      , "  D1 :: Int -> T String"+      , "  D2 :: T Bool"+      , "  D3 :: forall a. (Eq a) => (a, a) -> T [a]"+      ]++case50 :: Assertion+case50 = expected @=? testStep (step indentIndentStyle { cCurriedContext = True }) input+  where+    input = expected+    expected = unlines+      [ "module X where"+      , ""+      , "-- | A GADT example"+      , "data T a where"+      , "  D1 :: Int -> T String"+      , "  D2 :: T Bool"+      , "  D3 :: forall a. Eq a => (a, a) -> T [a]"+      ]++case51 :: Assertion+case51 = expected @=? testStep (step indentIndentStyle { cCurriedContext = True }) input+  where+    input = unlines+      [ "module X where"+      , ""+      , "-- | A GADT example"+      , "data T a where"+      , "  D1 :: Int -> T String"+      , "  D2 :: T Bool"+      , "  D3 :: forall a. (Eq a) => (a, a) -> T [a]"+      ]+    expected = unlines+      [ "module X where"+      , ""+      , "-- | A GADT example"+      , "data T a where"+      , "  D1 :: Int -> T String"+      , "  D2 :: T Bool"+      , "  D3 :: forall a. Eq a => (a, a) -> T [a]"+      ]++case52 :: Assertion+case52 = expected @=? testStep (step indentIndentStyle { cBreakSingleConstructors = False, cCurriedContext = True }) input+  where+    input = unlines+      [ "module X where"+      , ""+      , "data Foo = Foo"+      , "  { foo :: forall a b. (Eq a, Bounded b) => a -> b -> [(a, b)]"+      , "  }"+      ]+    expected = unlines+      [ "module X where"+      , ""+      , "data Foo = Foo"+      , "  { foo :: forall a b. Eq a => Bounded b => a -> b -> [(a, b)]"+      , "  }"+      ]++case53 :: Assertion+case53 = expected @=? testStep (step indentIndentStyle { cMaxColumns = MaxColumns 80 }) input+  where+    input = unlines+      [ "newtype Foo m a"+      , "  = Foo (m a)"+      , "  deriving newtype (Functor, Applicative, Monad, MonadError, MonadCatch, Foldable, Monoid)"+      ]+    expected = unlines+      [ "newtype Foo m a"+      , "  = Foo (m a)"+      , "  deriving newtype"+      , "    ( Applicative"+      , "    , Foldable"+      , "    , Functor"+      , "    , Monad"+      , "    , MonadCatch"+      , "    , MonadError"+      , "    , Monoid"+      , "    )"+      ]++case54 :: Assertion+case54 = expected @=? testStep (step indentIndentStyle { cMaxColumns = MaxColumns 80 }) input+  where+    input = unlines+      [ "newtype Foo m a"+      , "  = Foo (m a)"+      , "  deriving newtype (Functor, Applicative, Monad)"+      ]+    expected = unlines+      [ "newtype Foo m a"+      , "  = Foo (m a)"+      , "  deriving newtype (Applicative, Functor, Monad)"+      ]+ sameSameStyle :: Config-sameSameStyle = Config SameLine SameLine 2 2+sameSameStyle = Config SameLine SameLine 2 2 False True SameLine False NoMaxColumns  sameIndentStyle :: Config-sameIndentStyle = Config SameLine (Indent 2) 2 2+sameIndentStyle = Config SameLine (Indent 2) 2 2 False True SameLine False NoMaxColumns  indentSameStyle :: Config-indentSameStyle = Config (Indent 2) SameLine 2 2+indentSameStyle = Config (Indent 2) SameLine 2 2 False True SameLine False NoMaxColumns  indentIndentStyle :: Config-indentIndentStyle = Config (Indent 2) (Indent 2) 2 2+indentIndentStyle = Config (Indent 2) (Indent 2) 2 2 False True SameLine False NoMaxColumns  indentIndentStyle4 :: Config-indentIndentStyle4 = Config (Indent 4) (Indent 4) 4 4+indentIndentStyle4 = Config (Indent 4) (Indent 4) 4 4 False True SameLine False NoMaxColumns
+ tests/Language/Haskell/Stylish/Step/Imports/FelixTests.hs view
@@ -0,0 +1,382 @@+-- | Tests contributed by Felix Mulder as part of+-- <https://github.com/jaspervdj/stylish-haskell/pull/293>.+module Language.Haskell.Stylish.Step.Imports.FelixTests+  ( tests+  ) where++--------------------------------------------------------------------------------+import           Test.Framework                        (Test, testGroup)+import           Test.Framework.Providers.HUnit        (testCase)+import           Test.HUnit                            (Assertion)+import           GHC.Stack                             (HasCallStack, withFrozenCallStack)+import           Prelude                               hiding (lines)++--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.Module+import           Language.Haskell.Stylish.Step.Imports+import           Language.Haskell.Stylish.Tests.Util   (testStep', (@=??))++++--------------------------------------------------------------------------------+tests :: Test+tests = testGroup "Language.Haskell.Stylish.Step.ImportsGHC"+  [ testCase "Hello world" ex0+  , testCase "Sorted simple" ex1+  , testCase "Sorted import lists" ex2+  , testCase "Sorted import lists and import decls" ex3+  , testCase "Import constructor all" ex4+  , testCase "Import constructor specific" ex5+  , testCase "Import constructor specific sorted" ex6+  , testCase "Imports step does not change rest of file" ex7+  , testCase "Imports respect groups" ex8+  , testCase "Imports respects whitespace between groups" ex9+  , testCase "Doesn't add extra space after 'hiding'" ex10+  , testCase "Should be able to format symbolic imports" ex11+  , testCase "Able to merge equivalent imports" ex12+  , testCase "Obeys max columns setting" ex13+  , testCase "Obeys max columns setting with two in each" ex14+  , testCase "Respects multiple groups" ex15+  , testCase "Doesn't delete nullary imports" ex16+  ]++--------------------------------------------------------------------------------+ex0 :: Assertion+ex0 = input `assertFormatted` output+  where+    input =+      [ "import B"+      , "import A"+      ]+    output =+      [ "import           A"+      , "import           B"+      ]++ex1 :: Assertion+ex1 = input `assertFormatted` output+  where+    input =+      [ "import B"+      , "import A"+      , "import C"+      , "import qualified A"+      , "import qualified B as X"+      ]+    output =+      [ "import           A"+      , "import qualified A"+      , "import           B"+      , "import qualified B as X"+      , "import           C"+      ]++ex2 :: Assertion+ex2 = input `assertFormatted` output+  where+    input =+      [ "import B"+      , "import A (X)"+      , "import C"+      , "import qualified A as Y (Y)"+      ]+    output =+      [ "import           A (X)"+      , "import qualified A as Y (Y)"+      , "import           B"+      , "import           C"+      ]++ex3 :: Assertion+ex3 = input `assertFormatted` output+  where+    input =+      [ "import B"+      , "import A (X, Z, Y)"+      , "import C"+      , "import qualified A as A0 (b, Y, a)"+      , "import qualified D as D0 (Y, b, a)"+      , "import qualified E as E0 (b, a, Y)"+      ]+    output =+      [ "import           A (X, Y, Z)"+      , "import qualified A as A0 (Y, a, b)"+      , "import           B"+      , "import           C"+      , "import qualified D as D0 (Y, a, b)"+      , "import qualified E as E0 (Y, a, b)"+      ]++ex4 :: Assertion+ex4 = input `assertFormatted` output+  where+    input =+      [ "import A (X, Z(..), Y)"+      ]+    output =+      [ "import           A (X, Y, Z (..))"+      ]++ex5 :: Assertion+ex5 = input `assertFormatted` output+  where+    input =+      [ "import A (X, Z(Z), Y)"+      ]+    output =+      [ "import           A (X, Y, Z (Z))"+      ]++ex6 :: Assertion+ex6 = input `assertFormatted` output+  where+    input =+      [ "import A (X, Z(X, Z, Y), Y)"+      ]+    output =+      [ "import           A (X, Y, Z (X, Y, Z))"+      ]++ex7 :: Assertion+ex7 = input `assertFormatted` output+  where+    input =+      [ "module Foo (tests) where"+      , "import B"+      , "import A (X, Z, Y)"+      , "import C"+      , "import qualified A as A0 (b, Y, a)"+      , "import qualified D as D0 (Y, b, a)"+      , "import qualified E as E0 (b, a, Y)"+      , "-- hello"+      , "foo :: Int"+      , "foo = 1"+      ]+    output =+      [ "module Foo (tests) where"+      , "import           A (X, Y, Z)"+      , "import qualified A as A0 (Y, a, b)"+      , "import           B"+      , "import           C"+      , "import qualified D as D0 (Y, a, b)"+      , "import qualified E as E0 (Y, a, b)"+      , "-- hello"+      , "foo :: Int"+      , "foo = 1"+      ]++ex8 :: Assertion+ex8 = input `assertFormatted` output+  where+    input =+      [ "import B"+      , "-- Group divisor"+      , "import A (X)"+      , "import C"+      , "import qualified  A as Y (Y)"+      ]+    output =+      [ "import           B"+      , "-- Group divisor"+      , "import           A (X)"+      , "import qualified A as Y (Y)"+      , "import           C"+      ]++ex9 :: Assertion+ex9 = input `assertFormatted` output+  where+    input =+      [ "--------"+      , "import B"+      , ""+      , "-- Group divisor"+      , "import A (X)"+      , "import C"+      , "import qualified A as Y (Y)"+      ]+    output =+      [ "--------"+      , "import           B"+      , ""+      , "-- Group divisor"+      , "import           A (X)"+      , "import qualified A as Y (Y)"+      , "import           C"+      ]++ex10 :: Assertion+ex10 = input `assertFormatted` output+  where+    input =+      [ "import B         hiding      (X)"+      , "import A  hiding (X)"+      ]+    output =+      [ "import           A hiding (X)"+      , "import           B hiding (X)"+      ]++ex11 :: Assertion+ex11 = input `assertFormatted` output+  where+    input =+      [ "import Data.Aeson ((.=))"+      , "import A  hiding (X)"+      ]+    output =+      [ "import           A          hiding (X)"+      , "import           Data.Aeson ((.=))"+      ]++ex12 :: Assertion+ex12 = input `assertFormatted` output+  where+    input =+      [ "import Data.Aeson ((.=))"+      , "import Data.Aeson ((.=))"+      , "import A  hiding (X)"+      ]+    output =+      [ "import           A          hiding (X)"+      , "import           Data.Aeson ((.=))"+      ]++ex13 :: Assertion+ex13 = input `assertFormattedCols` output+  where+    assertFormattedCols =+      assertFormatted' (Just 10)+    input =+      [ "import Foo (A, B, C, D)"+      , "import A  hiding (X)"+      ]+    output =+      [ "import           A   hiding (X)"+      , "import           Foo (A)"+      , "import           Foo (B)"+      , "import           Foo (C)"+      , "import           Foo (D)"+      ]++ex14 :: Assertion+ex14 = input `assertFormattedCols` output+  where+    assertFormattedCols =+      assertFormatted' (Just 27)+    input =+      [ "import Foo (A, B, C, D)"+      , "import A  hiding (X)"+      ]+    output =+      [ "import           A   hiding (X)"+      , "import           Foo (A, B)"+      , "import           Foo (C, D)"+      ]++ex15 :: Assertion+ex15 = input `assertFormattedCols` output+  where+    assertFormattedCols =+      assertFormatted' (Just 100)+    input =+      [ "module Custom.Prelude"+      , "  ( LazyByteString"+      , "  , UUID"+      , "  , decodeUtf8Lenient"+      , "  , error"+      , "  , headMay"+      , "  , module X"+      , "  , nextRandomUUID"+      , "  , onChars"+      , "  , proxyOf"+      , "  , show"+      , "  , showStr"+      , "  , toLazyByteString"+      , "  , toStrictByteString"+      , "  , type (~>)"+      , "  , uuidToText"+      , "  ) where"+      , ""+      , "--------------------------------------------------------------------------------"+      , "import Prelude as X hiding ((!!), appendFile, error, foldl, head, putStrLn, readFile, show, tail, take, unlines, unwords, words, writeFile)"+      , "import qualified Prelude"+      , ""+      , "--------------------------------------------------------------------------------"+      , "import Control.Lens as X ((%~), (&), (.~), (?~), (^.), (^?), _Left, _Right, iat, over, preview, sans, set, to, view)"+      , "import Control.Lens.Extras as X (is)"+      , ""+      , "--------------------------------------------------------------------------------"+      , "import Control.Applicative as X ((<|>))"+      , "import Control.Monad as X ((<=<), (>=>), guard, unless, when)"+      , "import Control.Monad.Except as X (ExceptT (..), MonadError (..), liftEither, runExceptT, withExceptT)"+      , "import Control.Monad.IO.Unlift as X"+      , "import Control.Monad.Reader as X (MonadReader (..), ReaderT (..), asks)"+      , "import Control.Monad.Trans.Class as X (MonadTrans (lift))"+      , "--------------------------------------------------------------------------------"+      ]+    output =+      [ "module Custom.Prelude"+      , "  ( LazyByteString"+      , "  , UUID"+      , "  , decodeUtf8Lenient"+      , "  , error"+      , "  , headMay"+      , "  , module X"+      , "  , nextRandomUUID"+      , "  , onChars"+      , "  , proxyOf"+      , "  , show"+      , "  , showStr"+      , "  , toLazyByteString"+      , "  , toStrictByteString"+      , "  , type (~>)"+      , "  , uuidToText"+      , "  ) where"+      , ""+      , "--------------------------------------------------------------------------------"+      , "import           Prelude                   as X hiding (appendFile, error, foldl, head, putStrLn, readFile, show, tail, take, unlines, unwords, words, writeFile, (!!))"+      , "import qualified Prelude"+      , ""+      , "--------------------------------------------------------------------------------"+      , "import           Control.Lens              as X (_Left, _Right, iat, over, preview, sans, set, to)"+      , "import           Control.Lens              as X (view, (%~), (&), (.~), (?~), (^.), (^?))"+      , "import           Control.Lens.Extras       as X (is)"+      , ""+      , "--------------------------------------------------------------------------------"+      , "import           Control.Applicative       as X ((<|>))"+      , "import           Control.Monad             as X (guard, unless, when, (<=<), (>=>))"+      , "import           Control.Monad.Except      as X (ExceptT (..), MonadError (..), liftEither)"+      , "import           Control.Monad.Except      as X (runExceptT, withExceptT)"+      , "import           Control.Monad.IO.Unlift   as X"+      , "import           Control.Monad.Reader      as X (MonadReader (..), ReaderT (..), asks)"+      , "import           Control.Monad.Trans.Class as X (MonadTrans (lift))"+      , "--------------------------------------------------------------------------------"+      ]++ex16 :: Assertion+ex16 = input `assertFormatted` output+  where+    input =+      [ "module Foo where"+      , ""+      , "import           B ()"+      , "import           A ()"+      ]+    output =+      [ "module Foo where"+      , ""+      , "import           A ()"+      , "import           B ()"+      ]++assertFormatted :: HasCallStack => Lines -> Lines -> Assertion+assertFormatted = withFrozenCallStack $ assertFormatted' Nothing++assertFormatted' :: HasCallStack => Maybe Int -> Lines -> Lines -> Assertion+assertFormatted' maxColumns input expected =+  withFrozenCallStack $ expected @=?? testStep' (step maxColumns felixOptions) input+  where+    felixOptions = defaultOptions+        { listAlign = Repeat+        }
tests/Language/Haskell/Stylish/Step/Imports/Tests.hs view
@@ -5,9 +5,9 @@   ---------------------------------------------------------------------------------import           Test.Framework                 (Test, testGroup)-import           Test.Framework.Providers.HUnit (testCase)-import           Test.HUnit                     (Assertion, (@=?))+import           Test.Framework                        (Test, testGroup)+import           Test.Framework.Providers.HUnit        (testCase)+import           Test.HUnit                            (Assertion, (@=?))   --------------------------------------------------------------------------------@@ -15,7 +15,6 @@ import           Language.Haskell.Stylish.Tests.Util  - -------------------------------------------------------------------------------- fromImportAlign :: ImportAlign -> Options fromImportAlign align = defaultOptions { importAlign = align }@@ -63,8 +62,8 @@   ---------------------------------------------------------------------------------input :: String-input = unlines+input :: Snippet+input = Snippet     [ "module Herp where"     , ""     , "import qualified Data.Map  as M"@@ -83,9 +82,9 @@  -------------------------------------------------------------------------------- case01 :: Assertion-case01 = expected @=? testStep (step (Just 80) $ fromImportAlign Global) input+case01 = expected @=? testSnippet (step (Just 80) $ fromImportAlign Global) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"@@ -105,9 +104,9 @@  -------------------------------------------------------------------------------- case02 :: Assertion-case02 = expected @=? testStep (step (Just 80) $ fromImportAlign Group) input+case02 = expected @=? testSnippet (step (Just 80) $ fromImportAlign Group) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"@@ -126,9 +125,9 @@  -------------------------------------------------------------------------------- case03 :: Assertion-case03 = expected @=? testStep (step (Just 80) $ fromImportAlign None) input+case03 = expected @=? testSnippet (step (Just 80) $ fromImportAlign None) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import Control.Monad"@@ -147,13 +146,13 @@  -------------------------------------------------------------------------------- case04 :: Assertion-case04 = expected @=? testStep (step (Just 80) $ fromImportAlign Global) input'+case04 = expected @=? testSnippet (step (Just 80) $ fromImportAlign Global) input'   where-    input' =+    input' = Snippet $ pure $         "import Data.Aeson.Types (object, typeMismatch, FromJSON(..)," ++         "ToJSON(..), Value(..), parseEither, (.!=), (.:), (.:?), (.=))" -    expected = unlines+    expected = Snippet         [ "import           Data.Aeson.Types (FromJSON (..), ToJSON (..), Value (..),"         , "                                   object, parseEither, typeMismatch, (.!=),"         , "                                   (.:), (.:?), (.=))"@@ -162,17 +161,18 @@  -------------------------------------------------------------------------------- case05 :: Assertion-case05 = input' @=? testStep (step (Just 80) $ fromImportAlign Group) input'+case05 = input' @=? testSnippet (step (Just 80) $ fromImportAlign Group) input'   where-    input' = "import Distribution.PackageDescription.Configuration " ++-        "(finalizePackageDescription)\n"+    -- Putting this on a different line shouldn't really help.+    input' = Snippet ["import Distribution.PackageDescription.Configuration " +++        "(finalizePackageDescription)"]   -------------------------------------------------------------------------------- case06 :: Assertion-case06 = input' @=? testStep (step (Just 80) $ fromImportAlign File) input'+case06 = input' @=? testStep' (step (Just 80) $ fromImportAlign File) input'   where-    input' = unlines+    input' =         [ "import Bar.Qux"         , "import Foo.Bar"         ]@@ -180,15 +180,16 @@  -------------------------------------------------------------------------------- case07 :: Assertion-case07 = expected @=? testStep (step (Just 80) $ fromImportAlign File) input'+case07 =+    expected @=? testSnippet (step (Just 80) $ fromImportAlign File) input'   where-    input' = unlines+    input' = Snippet         [ "import Bar.Qux"         , ""         , "import qualified Foo.Bar"         ] -    expected = unlines+    expected = Snippet         [ "import           Bar.Qux"         , ""         , "import qualified Foo.Bar"@@ -197,10 +198,13 @@  -------------------------------------------------------------------------------- case08 :: Assertion-case08 = expected-    @=? testStep (step (Just 80) $ Options Global WithAlias True Inline Inherit (LPConstant 4) True False) input+case08 =+  let+    options = Options Global WithAlias True Inline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"@@ -220,10 +224,13 @@  -------------------------------------------------------------------------------- case08b :: Assertion-case08b = expected-    @=? testStep (step (Just 80) $ Options Global WithModuleName True Inline Inherit (LPConstant 4) True False) input+case08b =+  let+    options = Options Global WithModuleName True Inline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input   where-    expected = unlines+    expected = Snippet         ["module Herp where"         , ""         , "import           Control.Monad"@@ -242,10 +249,13 @@  -------------------------------------------------------------------------------- case09 :: Assertion-case09 = expected-    @=? testStep (step (Just 80) $ Options Global WithAlias True Multiline Inherit (LPConstant 4) True False) input+case09 =+  let+    options = Options Global WithAlias True Multiline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"@@ -276,10 +286,13 @@  -------------------------------------------------------------------------------- case10 :: Assertion-case10 = expected-    @=? testStep (step (Just 40) $ Options Group WithAlias True Multiline Inherit (LPConstant 4) True False) input+case10 =+  let+    options = Options Group WithAlias True Multiline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 40) options) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"@@ -313,12 +326,16 @@         ]  + -------------------------------------------------------------------------------- case11 :: Assertion-case11 = expected-    @=? testStep (step (Just 80) $ Options Group NewLine True Inline Inherit (LPConstant 4) True False) input+case11 =+  let+    options = Options Group NewLine True Inline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"@@ -342,10 +359,13 @@   case11b :: Assertion-case11b = expected-    @=? testStep (step (Just 80) $ Options Group WithModuleName True Inline Inherit (LPConstant 4) True False) input+case11b =+  let+    options = Options Group WithModuleName True Inline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"@@ -364,14 +384,17 @@  -------------------------------------------------------------------------------- case12 :: Assertion-case12 = expected-    @=? testStep (step (Just 80) $ Options Group NewLine True Inline Inherit (LPConstant 2) True False) input'+case12 =+  let+    options = Options Group NewLine True Inline Inherit (LPConstant 2) True False+  in+    expected @=? testSnippet (step (Just 80) options) input'   where-    input' = unlines+    input' = Snippet         [ "import Data.List (map)"         ] -    expected = unlines+    expected = Snippet         [ "import Data.List"         , "  (map)"         ]@@ -379,27 +402,31 @@  -------------------------------------------------------------------------------- case12b :: Assertion-case12b = expected-    @=? testStep (step (Just 80) $ Options Group WithModuleName True Inline Inherit (LPConstant 2) True False) input'+case12b =+  let+    options = Options Group WithModuleName True Inline Inherit (LPConstant 2) True False+  in+    expected @=? testStep' (step (Just 80) options) input'   where-    input' = unlines-        [ "import Data.List (map)"-        ]+    input' =  ["import Data.List (map)"]      expected = input'   -------------------------------------------------------------------------------- case13 :: Assertion-case13 = expected-    @=? testStep (step (Just 80) $ Options None WithAlias True InlineWithBreak Inherit (LPConstant 4) True False) input'+case13 =+  let+    options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input'   where-    input' = unlines+    input' = Snippet         [ "import qualified Data.List as List (concat, foldl, foldr, head, init,"         , "    last, length, map, null, reverse, tail, (++))"         ] -    expected = unlines+    expected = Snippet         [ "import qualified Data.List as List"         , "    (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail,"         , "    (++))"@@ -408,15 +435,18 @@  -------------------------------------------------------------------------------- case13b :: Assertion-case13b = expected-    @=? testStep (step (Just 80) $ Options None WithModuleName True InlineWithBreak Inherit (LPConstant 4) True False) input'+case13b =+  let+    options = Options None WithModuleName True InlineWithBreak Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input'   where-    input' = unlines+    input' = Snippet         [ "import qualified Data.List as List (concat, foldl, foldr, head, init,"         , "    last, length, map, null, reverse, tail, (++))"         ] -    expected = unlines+    expected = Snippet         [ "import qualified Data.List as List"         , "    (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail,"         , "    (++))"@@ -425,21 +455,26 @@  -------------------------------------------------------------------------------- case14 :: Assertion-case14 = expected-    @=? testStep-      (step (Just 80) $ Options None WithAlias True InlineWithBreak Inherit (LPConstant 10) True False) expected+case14 =+  let+    options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 10) True False+  in+    expected @=? testSnippet (step (Just 80) options) expected   where-    expected = unlines+    expected = Snippet         [ "import qualified Data.List as List (concat, map, null, reverse, tail, (++))"         ]   -------------------------------------------------------------------------------- case15 :: Assertion-case15 = expected-    @=? testStep (step (Just 80) $ Options None AfterAlias True Multiline Inherit (LPConstant 4) True False) input'+case15 =+  let+    options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input'   where-    expected = unlines+    expected = Snippet         [ "import Data.Acid (AcidState)"         , "import qualified Data.Acid as Acid"         , "    ( closeAcidState"@@ -451,7 +486,7 @@         , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (bar, foo)"         ] -    input' = unlines+    input' = Snippet         [ "import Data.Acid (AcidState)"         , "import qualified Data.Acid as Acid (closeAcidState, createCheckpoint, openLocalStateFrom)"         , "import Data.Default.Class (Default (def))"@@ -462,10 +497,13 @@  -------------------------------------------------------------------------------- case16 :: Assertion-case16 = expected-    @=? testStep (step (Just 80) $ Options None AfterAlias True Multiline Inherit (LPConstant 4) False False) input'+case16 =+  let+    options = Options None AfterAlias True Multiline Inherit (LPConstant 4) False False+  in+    expected @=? testSnippet (step (Just 80) options) input'   where-    expected = unlines+    expected = Snippet         [ "import Data.Acid (AcidState)"         , "import Data.Default.Class (Default(def))"         , ""@@ -474,7 +512,7 @@         , "import Data.Foo (Foo(Bar, Foo), Goo(Goo))"         ] -    input' = unlines+    input' = Snippet         [ "import Data.Acid (AcidState)"         , "import Data.Default.Class (Default(def))"         , ""@@ -486,16 +524,19 @@  -------------------------------------------------------------------------------- case17 :: Assertion-case17 = expected-    @=? testStep (step (Just 80) $ Options None AfterAlias True Multiline Inherit (LPConstant 4) True False) input'+case17 =+  let+    options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 80) options) input'   where-    expected = unlines+    expected = Snippet         [ "import Control.Applicative (Applicative (pure, (<*>)))"         , ""         , "import Data.Identity (Identity (Identity, runIdentity))"         ] -    input' = unlines+    input' = Snippet         [ "import Control.Applicative (Applicative ((<*>),pure))"         , ""         , "import Data.Identity (Identity (runIdentity,Identity))"@@ -504,10 +545,13 @@  -------------------------------------------------------------------------------- case18 :: Assertion-case18 = expected @=? testStep-    (step (Just 40) $ Options None AfterAlias True InlineToMultiline Inherit (LPConstant 4) True False) input'+case18 =+  let+    options = Options None AfterAlias True InlineToMultiline Inherit (LPConstant 4) True False+  in+    expected @=? testSnippet (step (Just 40) options) input'   where-    expected = unlines+    expected = Snippet            ----------------------------------------         [ "import Data.Foo as Foo (Bar, Baz, Foo)"         , ""@@ -521,7 +565,7 @@         , "    )"         ] -    input' = unlines+    input' = Snippet         [ "import Data.Foo as Foo (Bar, Baz, Foo)"         , ""         , "import Data.Identity (Identity (Identity, runIdentity))"@@ -532,10 +576,13 @@  -------------------------------------------------------------------------------- case19 :: Assertion-case19 = expected @=? testStep-    (step (Just 40) $ Options Global NewLine True InlineWithBreak RightAfter (LPConstant 17) True False) case19input+case19 =+  let+    options = Options Global NewLine True InlineWithBreak RightAfter (LPConstant 17) True False+  in+    expected @=? testSnippet (step (Just 40) options) case19input   where-    expected = unlines+    expected = Snippet            ----------------------------------------         [ "import           Prelude ()"         , "import           Prelude.Compat hiding"@@ -548,14 +595,16 @@   case19b :: Assertion-case19b = expected @=? testStep-    (step (Just 40) $ Options File NewLine True InlineWithBreak RightAfter (LPConstant 17) True False) case19input+case19b =+  let+    options = Options File NewLine True InlineWithBreak RightAfter (LPConstant 17) True False+  in+    expected @=? testSnippet (step (Just 40) options) case19input   where-    expected = unlines+    expected = Snippet            ----------------------------------------         [ "import Prelude ()"-        , "import Prelude.Compat hiding"-        , "                 (foldMap)"+        , "import Prelude.Compat hiding (foldMap)"         , ""         , "import Data.List"         , "                 (foldl', intercalate,"@@ -564,14 +613,16 @@   case19c :: Assertion-case19c = expected @=? testStep-    (step (Just 40) $ Options File NewLine True InlineWithBreak RightAfter LPModuleName True False) case19input+case19c =+  let+    options = Options File NewLine True InlineWithBreak RightAfter LPModuleName True False+  in+    expected @=? testSnippet (step (Just 40) options) case19input   where-    expected = unlines+    expected = Snippet            ----------------------------------------         [ "import Prelude ()"-        , "import Prelude.Compat hiding"-        , "       (foldMap)"+        , "import Prelude.Compat hiding (foldMap)"         , ""         , "import Data.List"         , "       (foldl', intercalate,"@@ -580,10 +631,13 @@   case19d :: Assertion-case19d = expected @=? testStep-    (step (Just 40) $ Options Global NewLine True InlineWithBreak RightAfter LPModuleName True False) case19input+case19d =+  let+    options = Options Global NewLine True InlineWithBreak RightAfter LPModuleName True False+  in+    expected @=? testSnippet (step (Just 40) options) case19input   where-    expected = unlines+    expected = Snippet            ----------------------------------------         [ "import           Prelude ()"         , "import           Prelude.Compat hiding"@@ -595,27 +649,27 @@         ]  -case19input :: String-case19input = unlines-        [ "import Prelude.Compat hiding (foldMap)"-        , "import Prelude ()"-        , ""-        , "import Data.List (foldl', intercalate, intersperse)"-        ]+case19input :: Snippet+case19input = Snippet+    [ "import Prelude.Compat hiding (foldMap)"+    , "import Prelude ()"+    , ""+    , "import Data.List (foldl', intercalate, intersperse)"+    ]   -------------------------------------------------------------------------------- case20 :: Assertion case20 = expected-    @=? testStep (step (Just 80) defaultOptions) input'+    @=? testSnippet (step (Just 80) defaultOptions) input'   where-    expected = unlines-        [ "import {-# SOURCE #-} Data.ByteString as BS"-        , "import qualified Data.Map        as Map"-        , "import           Data.Set        (empty)"+    expected = Snippet+        [ "import {-# SOURCE #-}           Data.ByteString as BS"+        , "import                qualified Data.Map        as Map"+        , "import                          Data.Set        (empty)"         , "import {-# SOURCE #-} qualified Data.Text       as T"         ]-    input' = unlines+    input' = Snippet         [ "import {-# SOURCE #-}    Data.ByteString as BS"         , "import {-# SOURCE #-} qualified Data.Text as T"         , "import qualified   Data.Map as Map"@@ -626,9 +680,9 @@ -------------------------------------------------------------------------------- case21 :: Assertion case21 = expected-    @=? testStep (step (Just 80) defaultOptions) input'+    @=? testSnippet (step (Just 80) defaultOptions) input'   where-    expected = unlines+    expected = Snippet         [ "{-# LANGUAGE ExplicitNamespaces #-}"         , "import           X1 (A, B, C)"         , "import           X2 (A, B, C)"@@ -640,7 +694,7 @@         , "import           X8 (type (+), (+))"         , "import           X9 hiding (x, y, z)"         ]-    input' = unlines+    input' = Snippet         [ "{-# LANGUAGE ExplicitNamespaces #-}"         , "import X1 (A, B, A, C, A, B, A)"         , "import X2 (C(), B(), A())"@@ -657,9 +711,9 @@ -------------------------------------------------------------------------------- case22 :: Assertion case22 = expected-    @=? testStep (step (Just 80) defaultOptions) input'+    @=? testSnippet (step (Just 80) defaultOptions) input'   where-    expected = unlines+    expected = Snippet         [ "{-# LANGUAGE PackageImports #-}"         , "import           A"         , "import           \"blah\" A"@@ -668,7 +722,7 @@         , "import           \"foo\" B  (shortName, someLongName, someLongerName,"         , "                           theLongestNameYet)"         ]-    input' = unlines+    input' = Snippet         [ "{-# LANGUAGE PackageImports #-}"         , "import A"         , "import \"foo\" A"@@ -683,10 +737,14 @@  -------------------------------------------------------------------------------- case23 :: Assertion-case23 = expected-    @=? testStep (step (Just 40) $ Options None AfterAlias False Inline Inherit (LPConstant 4) True True) input'+case23 =+  let+    options = Options None AfterAlias False Inline Inherit (LPConstant 4) True True+  in+    expected @=? testSnippet (step (Just 40) options) input'   where-    expected = unlines+    expected = Snippet+           ----------------------------------------         [ "import Data.Acid ( AcidState )"         , "import Data.Default.Class ( Default (def) )"         , ""@@ -696,7 +754,7 @@         , "                            Goo )"         ] -    input' = unlines+    input' = Snippet         [ "import Data.Acid (AcidState)"         , "import Data.Default.Class (Default(def))"         , ""@@ -708,10 +766,14 @@  -------------------------------------------------------------------------------- case23b :: Assertion-case23b = expected-    @=? testStep (step (Just 40) $ Options None WithModuleName False Inline Inherit (LPConstant 4) True True) input'+case23b =+  let+    options = Options None WithModuleName False Inline Inherit (LPConstant 4) True True+  in+    expected @=? testSnippet (step (Just 40) options) input'   where-    expected = unlines+    expected = Snippet+           ----------------------------------------         [ "import Data.Acid ( AcidState )"         , "import Data.Default.Class"         , "           ( Default (def) )"@@ -722,7 +784,7 @@         , "           Goo )"         ] -    input' = unlines+    input' = Snippet         [ "import Data.Acid (AcidState)"         , "import Data.Default.Class (Default(def))"         , ""@@ -734,10 +796,14 @@  -------------------------------------------------------------------------------- case24 :: Assertion-case24 = expected-    @=? testStep (step (Just 40) $ Options None AfterAlias False InlineWithBreak Inherit (LPConstant 4) True True) input'+case24 =+  let+    options = Options None AfterAlias False InlineWithBreak Inherit (LPConstant 4) True True+  in+    expected @=? testSnippet (step (Just 40) options) input'   where-    expected = unlines+    expected = Snippet+           ----------------------------------------         [ "import Data.Acid ( AcidState )"         , "import Data.Default.Class"         , "    ( Default (def) )"@@ -747,7 +813,7 @@         , "    GooReallyLong )"         ] -    input' = unlines+    input' = Snippet         [ "import Data.Acid (AcidState)"         , "import Data.Default.Class (Default(def))"         , ""@@ -758,10 +824,13 @@  -------------------------------------------------------------------------------- case25 :: Assertion-case25 = expected-    @=? testStep (step (Just 80) $ Options Group AfterAlias False Multiline Inherit (LPConstant 4) False False) input'+case25 =+  let+    options = Options Group AfterAlias False Multiline Inherit (LPConstant 4) False False+  in+    expected @=? testSnippet (step (Just 80) options) input'   where-    expected = unlines+    expected = Snippet         [ "import Data.Acid (AcidState)"         , "import Data.Default.Class (Default(def))"         , ""@@ -770,7 +839,7 @@         , ""         , "import Data.Foo (Foo(Bar, Foo), Goo(Goo))"         ]-    input' = unlines+    input' = Snippet         [ "import Data.Acid (AcidState)"         , "import Data.Default.Class (Default(def))"         , ""@@ -784,22 +853,18 @@ -------------------------------------------------------------------------------- case26 :: Assertion case26 = expected-    @=? testStep (step (Just 80) options ) input'+    @=? testSnippet (step (Just 80) options ) input'   where     options = defaultOptions { listAlign = NewLine, longListAlign = Multiline }-    input' = unlines-        [ "import Data.List"-        ]-    expected = unlines-        [ "import           Data.List"-        ]+    input' = Snippet ["import Data.List"]+    expected = Snippet ["import           Data.List"]   -------------------------------------------------------------------------------- case27 :: Assertion-case27 = expected @=? testStep (step Nothing $ fromImportAlign Global) input+case27 = expected @=? testSnippet (step Nothing $ fromImportAlign Global) input   where-    expected = unlines+    expected = Snippet         [ "module Herp where"         , ""         , "import           Control.Monad"
tests/Language/Haskell/Stylish/Step/LanguagePragmas/Tests.hs view
@@ -1,4 +1,5 @@ --------------------------------------------------------------------------------+{-# LANGUAGE OverloadedLists #-} module Language.Haskell.Stylish.Step.LanguagePragmas.Tests     ( tests     ) where@@ -7,7 +8,7 @@ -------------------------------------------------------------------------------- import Test.Framework                 (Test, testGroup) import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit                     (Assertion, (@=?))+import Test.HUnit                     (Assertion)   --------------------------------------------------------------------------------@@ -30,6 +31,7 @@     , testCase "case 10" case10     , testCase "case 11" case11     , testCase "case 12" case12+    , testCase "case 13" case13     ]  lANG :: String@@ -37,202 +39,191 @@  -------------------------------------------------------------------------------- case01 :: Assertion-case01 = expected @=? testStep (step (Just 80) Vertical True False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE ViewPatterns #-}"-        , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"-        , "{-# LANGUAGE ScopedTypeVariables #-}"-        , "module Main where"-        ]+case01 = assertSnippet+    (step (Just 80) Vertical True False lANG)+    [ "{-# LANGUAGE ViewPatterns #-}"+    , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"+    , "{-# LANGUAGE ScopedTypeVariables #-}"+    , "module Main where"+    ] -    expected = unlines-        [ "{-# LANGUAGE ScopedTypeVariables #-}"-        , "{-# LANGUAGE TemplateHaskell     #-}"-        , "{-# LANGUAGE ViewPatterns        #-}"-        , "module Main where"-        ]+    [ "{-# LANGUAGE ScopedTypeVariables #-}"+    , "{-# LANGUAGE TemplateHaskell     #-}"+    , "{-# LANGUAGE ViewPatterns        #-}"+    , "module Main where"+    ]   -------------------------------------------------------------------------------- case02 :: Assertion-case02 = expected @=? testStep (step (Just 80) Vertical True True lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE BangPatterns #-}"-        , "{-# LANGUAGE ViewPatterns #-}"-        , "increment ((+ 1) -> x) = x"-        ]+case02 = assertSnippet+    (step (Just 80) Vertical True True lANG)+    [ "{-# LANGUAGE BangPatterns #-}"+    , "{-# LANGUAGE ViewPatterns #-}"+    , "increment ((+ 1) -> x) = x"+    ] -    expected = unlines-        [ "{-# LANGUAGE ViewPatterns #-}"-        , "increment ((+ 1) -> x) = x"-        ]+    [ "{-# LANGUAGE ViewPatterns #-}"+    , "increment ((+ 1) -> x) = x"+    ]   -------------------------------------------------------------------------------- case03 :: Assertion-case03 = expected @=? testStep (step (Just 80) Vertical True True lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE BangPatterns #-}"-        , "{-# LANGUAGE ViewPatterns #-}"-        , "increment x = case x of !_ -> x + 1"-        ]+case03 = assertSnippet+    (step (Just 80) Vertical True True lANG)+    [ "{-# LANGUAGE BangPatterns #-}"+    , "{-# LANGUAGE ViewPatterns #-}"+    , "increment x = case x of !_ -> x + 1"+    ] -    expected = unlines-        [ "{-# LANGUAGE BangPatterns #-}"-        , "increment x = case x of !_ -> x + 1"-        ]+    [ "{-# LANGUAGE BangPatterns #-}"+    , "increment x = case x of !_ -> x + 1"+    ]   -------------------------------------------------------------------------------- case04 :: Assertion-case04 = expected @=? testStep (step (Just 80) Compact True False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable,"-        , "    TemplateHaskell #-}"-        , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"-        ]+case04 = assertSnippet+    (step (Just 80) Compact True False lANG)+    [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable,"+    , "    TemplateHaskell #-}"+    , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"+    ] -    expected = unlines-        [ "{-# LANGUAGE DeriveDataTypeable, StandaloneDeriving, " ++-            "TemplateHaskell,"-        , "             TypeOperators, ViewPatterns #-}"-        ]+    [ "{-# LANGUAGE DeriveDataTypeable, StandaloneDeriving, " +++        "TemplateHaskell,"+    , "             TypeOperators, ViewPatterns #-}"+    ]   -------------------------------------------------------------------------------- case05 :: Assertion-case05 = expected @=? testStep (step (Just 80) Vertical True False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE CPP #-}"-        , ""-        , "#if __GLASGOW_HASKELL__ >= 702"-        , "{-# LANGUAGE Trustworthy #-}"-        , "#endif"-        ]+case05 = assertSnippet+    (step (Just 80) Vertical True False lANG)+    [ "{-# LANGUAGE CPP #-}"+    , ""+    , "#if __GLASGOW_HASKELL__ >= 702"+    , "{-# LANGUAGE Trustworthy #-}"+    , "#endif"+    ] -    expected = unlines-        [ "{-# LANGUAGE CPP         #-}"-        , ""-        , "#if __GLASGOW_HASKELL__ >= 702"-        , "{-# LANGUAGE Trustworthy #-}"-        , "#endif"-        ]+    [ "{-# LANGUAGE CPP         #-}"+    , ""+    , "#if __GLASGOW_HASKELL__ >= 702"+    , "{-# LANGUAGE Trustworthy #-}"+    , "#endif"+    ]   -------------------------------------------------------------------------------- case06 :: Assertion-case06 = expected @=? testStep (step (Just 80) CompactLine True False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable,"-        , "    TemplateHaskell #-}"-        , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"-        ]-    expected = unlines-        [ "{-# LANGUAGE DeriveDataTypeable, StandaloneDeriving, " ++-          "TemplateHaskell #-}"-        , "{-# LANGUAGE TypeOperators, ViewPatterns                             #-}"-        ]+case06 = assertSnippet+    (step (Just 80) CompactLine True False lANG)+    [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable,"+    , "    TemplateHaskell #-}"+    , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"+    ]+    [ "{-# LANGUAGE DeriveDataTypeable, StandaloneDeriving, " +++      "TemplateHaskell #-}"+    , "{-# LANGUAGE TypeOperators, ViewPatterns                             #-}"+    ]  -------------------------------------------------------------------------------- case07 :: Assertion-case07 = expected @=? testStep (step (Just 80) Vertical False False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE ViewPatterns #-}"-        , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"-        , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}"-        , "module Main where"-        ]+case07 = assertSnippet+    (step (Just 80) Vertical False False lANG)+    [ "{-# LANGUAGE ViewPatterns #-}"+    , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"+    , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}"+    , "module Main where"+    ] -    expected = unlines-        [ "{-# LANGUAGE NoImplicitPrelude #-}"-        , "{-# LANGUAGE ScopedTypeVariables #-}"-        , "{-# LANGUAGE TemplateHaskell #-}"-        , "{-# LANGUAGE ViewPatterns #-}"-        , "module Main where"-        ]+    [ "{-# LANGUAGE NoImplicitPrelude #-}"+    , "{-# LANGUAGE ScopedTypeVariables #-}"+    , "{-# LANGUAGE TemplateHaskell #-}"+    , "{-# LANGUAGE ViewPatterns #-}"+    , "module Main where"+    ]   -------------------------------------------------------------------------------- case08 :: Assertion-case08 = expected @=? testStep (step (Just 80) CompactLine False False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable,"-        , "    TemplateHaskell #-}"-        , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"-        ]-    expected = unlines-        [ "{-# LANGUAGE DeriveDataTypeable, StandaloneDeriving, " ++-          "TemplateHaskell #-}"-        , "{-# LANGUAGE TypeOperators, ViewPatterns #-}"-        ]+case08 = assertSnippet+    (step (Just 80) CompactLine False False lANG)+    [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable,"+    , "    TemplateHaskell #-}"+    , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"+    ]+    [ "{-# LANGUAGE DeriveDataTypeable, StandaloneDeriving, " +++      "TemplateHaskell #-}"+    , "{-# LANGUAGE TypeOperators, ViewPatterns #-}"+    ]   -------------------------------------------------------------------------------- case09 :: Assertion-case09 = expected @=? testStep (step (Just 80) Compact True False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE DefaultSignatures, FlexibleInstances, LambdaCase, " ++-          "TypeApplications"-        , "             #-}"-        ]-    expected = unlines-        [ "{-# LANGUAGE DefaultSignatures, FlexibleInstances, LambdaCase,"-        , "             TypeApplications #-}"-        ]+case09 = assertSnippet+    (step (Just 80) Compact True False lANG)+    [ "{-# LANGUAGE DefaultSignatures, FlexibleInstances, LambdaCase, " +++      "TypeApplications"+    , "             #-}"+    ]+    [ "{-# LANGUAGE DefaultSignatures, FlexibleInstances, LambdaCase,"+    , "             TypeApplications #-}"+    ]  -------------------------------------------------------------------------------- case10 :: Assertion-case10 = expected @=? testStep (step (Just 80) Compact True False lANG) input-  where-    input = unlines-        [ "{-# LANGUAGE NondecreasingIndentation, ScopedTypeVariables,"-        , "             TypeApplications #-}"-        ]-    expected = unlines-        [ "{-# LANGUAGE NondecreasingIndentation, ScopedTypeVariables, " ++-          "TypeApplications #-}"-        ]+case10 = assertSnippet+    (step (Just 80) Compact True False lANG)+    [ "{-# LANGUAGE NondecreasingIndentation, ScopedTypeVariables,"+    , "             TypeApplications #-}"+    ]+    [ "{-# LANGUAGE NondecreasingIndentation, ScopedTypeVariables, " +++      "TypeApplications #-}"+    ]  -------------------------------------------------------------------------------- case11 :: Assertion-case11 = expected @=? testStep (step (Just 80) Vertical False False "language") input-  where-    input = unlines-        [ "{-# LANGUAGE ViewPatterns #-}"-        , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"-        , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}"-        , "module Main where"-        ]+case11 = assertSnippet+    (step (Just 80) Vertical False False "language")+    [ "{-# LANGUAGE ViewPatterns #-}"+    , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"+    , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}"+    , "module Main where"+    ] -    expected = unlines-        [ "{-# language NoImplicitPrelude #-}"-        , "{-# language ScopedTypeVariables #-}"-        , "{-# language TemplateHaskell #-}"-        , "{-# language ViewPatterns #-}"-        , "module Main where"-        ]+    [ "{-# language NoImplicitPrelude #-}"+    , "{-# language ScopedTypeVariables #-}"+    , "{-# language TemplateHaskell #-}"+    , "{-# language ViewPatterns #-}"+    , "module Main where"+    ] + -------------------------------------------------------------------------------- case12 :: Assertion-case12 = expected @=? testStep (step Nothing Compact False False "language") input-  where-    input = unlines-        [ "{-# LANGUAGE ViewPatterns, OverloadedStrings #-}"-        , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"-        , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}"-        , "module Main where"-        ]+case12 = assertSnippet+    (step Nothing Compact False False "language")+    [ "{-# LANGUAGE ViewPatterns, OverloadedStrings #-}"+    , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}"+    , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}"+    , "module Main where"+    ] -    expected = unlines-        [ "{-# language NoImplicitPrelude, OverloadedStrings, ScopedTypeVariables, TemplateHaskell, ViewPatterns #-}"-        , "module Main where"+    [ "{-# language NoImplicitPrelude, OverloadedStrings, ScopedTypeVariables, TemplateHaskell, ViewPatterns #-}"+    , "module Main where"+    ]+++--------------------------------------------------------------------------------+case13 :: Assertion+case13 = assertSnippet+    (step Nothing Vertical True True lANG) input input+  where+    input =+        [ "{-# LANGUAGE BangPatterns  #-}"+        , "{-# LANGUAGE DeriveFunctor #-}"+        , "main = let !x = 1 + 1 in print x"         ]
+ tests/Language/Haskell/Stylish/Step/ModuleHeader/Tests.hs view
@@ -0,0 +1,301 @@+{-# LANGUAGE OverloadedLists #-}+module Language.Haskell.Stylish.Step.ModuleHeader.Tests+  ( tests+  ) where++--------------------------------------------------------------------------------+import           Prelude                                    hiding (lines)+import           Test.Framework                             (Test, testGroup)+import           Test.Framework.Providers.HUnit             (testCase)+import           Test.HUnit                                 (Assertion)++--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.Step.ModuleHeader+import           Language.Haskell.Stylish.Tests.Util+++--------------------------------------------------------------------------------+tests :: Test+tests = testGroup "Language.Haskell.Stylish.Printer.ModuleHeader"+    [ testCase "Hello world" ex0+    , testCase "Empty exports list" ex1+    , testCase "Single exported variable" ex2+    , testCase "Multiple exported variables" ex3+    , testCase "Only reformats module header" ex4+    , testCase "Leaving pragmas in place" ex5+    , testCase "Leaving pragmas in place variant" ex6+    , testCase "Leaving comments in place" ex7+    , testCase "Exports all" ex8+    , testCase "Exports module" ex9+    , testCase "Exports symbol" ex10+    , testCase "Respects groups" ex11+    , testCase "'where' not repeated in case it isn't part of exports" ex12+    , testCase "Indents absent export list with 2 spaces" ex13+    , testCase "Indents with 2 spaces" ex14+    , testCase "Group doc with 2 spaces" ex15+    , testCase "Does not sort" ex16+    ]++--------------------------------------------------------------------------------+ex0 :: Assertion+ex0 = assertSnippet (step defaultConfig)+    [ "module Foo where"+    ]+    [ "module Foo"+    , "    where"+    ]++ex1 :: Assertion+ex1 = assertSnippet (step defaultConfig)+    [ "module Foo () where"+    ]+    [ "module Foo"+    , "    ("+    , "    ) where"+    ]++ex2 :: Assertion+ex2 = assertSnippet (step defaultConfig)+    [ "module Foo (tests) where"+    ]+    [ "module Foo"+    , "    ( tests"+    , "    ) where"+    ]++ex3 :: Assertion+ex3 = assertSnippet (step defaultConfig)+    [ "module Foo (t1, t2, t3) where"+    ]+    [ "module Foo"+    , "    ( t1"+    , "    , t2"+    , "    , t3"+    , "    ) where"+    ]++ex4 :: Assertion+ex4 = assertSnippet (step defaultConfig)+    [ "module Foo ("+    , "  t1,"+    , "  t3,"+    , "  t2"+    , ") where"+    , ""+    , ""+    , "-- | Docstring"+    , "foo :: Int"+    , "foo = 1"+    ]+    [ "module Foo"+    , "    ( t1"+    , "    , t2"+    , "    , t3"+    , "    ) where"+    , ""+    , ""+    , "-- | Docstring"+    , "foo :: Int"+    , "foo = 1"+    ]++ex5 :: Assertion+ex5 = assertSnippet (step defaultConfig)+    [ "{-# LANGUAGE DerivingVia #-}"+    , "-- | This module docs"+    , "module Foo ("+    , "  t1,"+    , "  t3,"+    , "  t2"+    , ") where"+    ]+    [ "{-# LANGUAGE DerivingVia #-}"+    , "-- | This module docs"+    , "module Foo"+    , "    ( t1"+    , "    , t2"+    , "    , t3"+    , "    ) where"+    ]++ex6 :: Assertion+ex6 = assertSnippet (step defaultConfig)+    [ "-- | This module docs"+    , "{-# LANGUAGE DerivingVia #-}"+    , "module Foo ("+    , "    t1,"+    , "    t3,"+    , "    t2"+    , ") where"+    ]+    [ "-- | This module docs"+    , "{-# LANGUAGE DerivingVia #-}"+    , "module Foo"+    , "    ( t1"+    , "    , t2"+    , "    , t3"+    , "    ) where"+    ]++ex7 :: Assertion+ex7 = assertSnippet (step defaultConfig)+    [ "module Foo -- Foo"+    , "("+    , " -- * t1 something"+    , "  t3,"+    , "  t1,"+    , " -- * t2 something"+    , "  t2"+    , ") where -- x"+    , "-- y"+    ]+    [ "module Foo -- Foo"+    , "    ( -- * t1 something"+    , "      t1"+    , "    , t3"+    , "      -- * t2 something"+    , "    , t2"+    , "    ) where -- x"+    , "-- y"+    ]+++ex8 :: Assertion+ex8 = assertSnippet (step defaultConfig)+    [ "module Foo ("+    , " -- * t1 something"+    , "  t3,"+    , "  A(..),"+    , " -- * t2 something"+    , "  t2,"+    , "  t1"+    , ") where -- x"+    , "-- y"+    ]+    [ "module Foo"+    , "    ( -- * t1 something"+    , "      A (..)"+    , "    , t3"+    , "      -- * t2 something"+    , "    , t1"+    , "    , t2"+    , "    ) where -- x"+    , "-- y"+    ]++ex9 :: Assertion+ex9 = assertSnippet (step defaultConfig)+    [ "module Foo ("+    , " -- * t1 something"+    , "  module A,"+    , "  t3,"+    , " -- * t2 something"+    , "  t2"+    , ") where -- x"+    , "-- y"+    ]+    [ "module Foo"+    , "    ( -- * t1 something"+    , "      module A"+    , "    , t3"+    , "      -- * t2 something"+    , "    , t2"+    , "    ) where -- x"+    , "-- y"+    ]++ex10 :: Assertion+ex10 = assertSnippet (step defaultConfig)+    [ "module Foo ("+    , "  (<&>)"+    , ") where -- x"+    , "-- y"+    ]+    [ "module Foo"+    , "    ( (<&>)"+    , "    ) where -- x"+    , "-- y"+    ]++ex11 :: Assertion+ex11 = assertSnippet (step defaultConfig)+    [ "module Foo ("+    , "  -- group 1"+    , " g1_1,"+    , " g1_0,"+    , "  -- group 2"+    , " g0_1,"+    , " g0_0"+    , ") where"+    ]+    [ "module Foo"+    , "    ( -- group 1"+    , "      g1_0"+    , "    , g1_1"+    , "      -- group 2"+    , "    , g0_0"+    , "    , g0_1"+    , "    ) where"+    ]++ex12 :: Assertion+ex12 = assertSnippet (step defaultConfig)+    [ "module Foo"+    , "  where"+    , "-- hmm"+    ]+    [ "module Foo"+    , "    where"+    , "-- hmm"+    ]++ex13 :: Assertion+ex13 = assertSnippet (step defaultConfig {indent = 2})+    [ "module Foo where"+    ]+    [ "module Foo"+    , "  where"+    ]++ex14 :: Assertion+ex14 = assertSnippet (step defaultConfig {indent = 2})+    [ "module Foo"+    , "  ( yes"+    , "  , no"+    , "  ) where"+    ]+    [ "module Foo"+    , "  ( no"+    , "  , yes"+    , "  ) where"+    ]++ex15 :: Assertion+ex15 = assertSnippet (step defaultConfig {indent = 2})+    [ "module Foo -- Foo"+    , "("+    , " -- * t1 something"+    , "  t3,"+    , "  t1,"+    , " -- * t2 something"+    , "  t2"+    , ") where"+    ]+    [ "module Foo -- Foo"+    , "  ( -- * t1 something"+    , "    t1"+    , "  , t3"+    , "    -- * t2 something"+    , "  , t2"+    , "  ) where"+    ]++ex16 :: Assertion+ex16 = assertSnippet (step defaultConfig {sort = False}) input input+  where+    input =+      [ "module Foo"+      , "    ( yes"+      , "    , no"+      , "    ) where"+      ]
tests/Language/Haskell/Stylish/Step/SimpleAlign/Tests.hs view
@@ -1,4 +1,5 @@ --------------------------------------------------------------------------------+{-# LANGUAGE OverloadedLists #-} module Language.Haskell.Stylish.Step.SimpleAlign.Tests     ( tests     ) where@@ -7,7 +8,7 @@ -------------------------------------------------------------------------------- import           Test.Framework                            (Test, testGroup) import           Test.Framework.Providers.HUnit            (testCase)-import           Test.HUnit                                (Assertion, (@=?))+import           Test.HUnit                                (Assertion)   --------------------------------------------------------------------------------@@ -27,81 +28,68 @@     , testCase "case 07" case07     , testCase "case 08" case08     , testCase "case 09" case09+    , testCase "case 10" case10+    , testCase "case 11" case11+    , testCase "case 12" case12     ]   -------------------------------------------------------------------------------- case01 :: Assertion-case01 = expected @=? testStep (step (Just 80) defaultConfig) input-  where-    input = unlines-        [ "eitherToMaybe e = case e of"-        , "    Left _ -> Nothing"-        , "    Right x -> Just x"-        ]--    expected = unlines-        [ "eitherToMaybe e = case e of"-        , "    Left _  -> Nothing"-        , "    Right x -> Just x"-        ]+case01 = assertSnippet (step (Just 80) defaultConfig)+    [ "eitherToMaybe e = case e of"+    , "    Left _ -> Nothing"+    , "    Right x -> Just x"+    ]+    [ "eitherToMaybe e = case e of"+    , "    Left _  -> Nothing"+    , "    Right x -> Just x"+    ]   -------------------------------------------------------------------------------- case02 :: Assertion-case02 = expected @=? testStep (step (Just 80) defaultConfig) input-  where-    input = unlines-        [ "eitherToMaybe (Left _) = Nothing"-        , "eitherToMaybe (Right x) = Just x"-        ]--    expected = unlines-        [ "eitherToMaybe (Left _)  = Nothing"-        , "eitherToMaybe (Right x) = Just x"-        ]+case02 = assertSnippet (step (Just 80) defaultConfig)+    [ "eitherToMaybe (Left _) = Nothing"+    , "eitherToMaybe (Right x) = Just x"+    ]+    [ "eitherToMaybe (Left _)  = Nothing"+    , "eitherToMaybe (Right x) = Just x"+    ]   -------------------------------------------------------------------------------- case03 :: Assertion-case03 = expected @=? testStep (step (Just 80) defaultConfig) input-  where-    input = unlines-        [ "heady def [] = def"-        , "heady _ (x : _) = x"-        ]--    expected = unlines-        [ "heady def []    = def"-        , "heady _ (x : _) = x"-        ]+case03 = assertSnippet (step (Just 80) defaultConfig)+    [ "heady def [] = def"+    , "heady _ (x : _) = x"+    ]+    [ "heady def []    = def"+    , "heady _ (x : _) = x"+    ]   -------------------------------------------------------------------------------- case04 :: Assertion-case04 = expected @=? testStep (step (Just 80) defaultConfig) input-  where-    input = unlines-        [ "data Foo = Foo"-        , "    { foo :: Int"-        , "    , barqux :: String"-        , "    } deriving (Show)"-        ]--    expected = unlines-        [ "data Foo = Foo"-        , "    { foo    :: Int"-        , "    , barqux :: String"-        , "    } deriving (Show)"-        ]+case04 = assertSnippet (step (Just 80) defaultConfig)+    [ "data Foo = Foo"+    , "    { foo :: Int"+    , "    , barqux :: String"+    , "    } deriving (Show)"+    ]+    [ "data Foo = Foo"+    , "    { foo    :: Int"+    , "    , barqux :: String"+    , "    } deriving (Show)"+    ]   -------------------------------------------------------------------------------- case05 :: Assertion-case05 = input @=? testStep (step (Just 80) defaultConfig) input+case05 = assertSnippet (step (Just 80) defaultConfig) input input   where     -- Don't attempt to align this since a field spans multiple lines-    input = unlines+    input =         [ "data Foo = Foo"         , "    { foo :: Int"         , "    , barqux"@@ -112,78 +100,102 @@  -------------------------------------------------------------------------------- case06 :: Assertion-case06 =+case06 = assertSnippet     -- 22 max columns is /just/ enough to align this stuff.-    expected @=? testStep (step (Just 22) defaultConfig) input-  where-    input = unlines-        [ "data Foo = Foo"-        , "    { foo :: String"-        , "    , barqux :: Int"-        , "    }"-        ]--    expected = unlines-        [ "data Foo = Foo"-        , "    { foo    :: String"-        , "    , barqux :: Int"-        , "    }"-        ]+    (step (Just 22) defaultConfig)+    [ "data Foo = Foo"+    , "    { foo :: String"+    , "    , barqux :: Int"+    , "    }"+    ]+    [ "data Foo = Foo"+    , "    { foo    :: String"+    , "    , barqux :: Int"+    , "    }"+    ]   -------------------------------------------------------------------------------- case07 :: Assertion-case07 =+case07 = assertSnippet     -- 21 max columns is /just NOT/ enough to align this stuff.-    expected @=? testStep (step (Just 21) defaultConfig) input-  where-    input = unlines-        [ "data Foo = Foo"-        , "    { foo :: String"-        , "    , barqux :: Int"-        , "    }"-        ]--    expected = unlines-        [ "data Foo = Foo"-        , "    { foo :: String"-        , "    , barqux :: Int"-        , "    }"-        ]+    (step (Just 21) defaultConfig)+    [ "data Foo = Foo"+    , "    { foo :: String"+    , "    , barqux :: Int"+    , "    }"+    ]+    [ "data Foo = Foo"+    , "    { foo :: String"+    , "    , barqux :: Int"+    , "    }"+    ]   -------------------------------------------------------------------------------- case08 :: Assertion-case08 = expected @=? testStep (step (Just 80) defaultConfig) input-  where-    input = unlines-        [ "canDrink mbAge = case mbAge of"-        , "    Just age | age > 18 -> True"-        , "    _ -> False"-        ]--    expected = unlines-        [ "canDrink mbAge = case mbAge of"-        , "    Just age | age > 18 -> True"-        , "    _                   -> False"-        ]+case08 = assertSnippet (step (Just 80) defaultConfig)+    [ "canDrink mbAge = case mbAge of"+    , "    Just age | age > 18 -> True"+    , "    _ -> False"+    ]+    [ "canDrink mbAge = case mbAge of"+    , "    Just age | age > 18 -> True"+    , "    _                   -> False"+    ]   -------------------------------------------------------------------------------- case09 :: Assertion-case09 =-    expected @=? testStep (step Nothing defaultConfig) input-  where-    input = unlines-        [ "data Foo = Foo"-        , "    { foo :: String"-        , "    , barqux :: Int"-        , "    }"-        ]+case09 = assertSnippet (step Nothing defaultConfig)+    [ "data Foo = Foo"+    , "    { foo :: String"+    , "    , barqux :: Int"+    , "    }"+    ]+    [ "data Foo = Foo"+    , "    { foo    :: String"+    , "    , barqux :: Int"+    , "    }"+    ] -    expected = unlines-        [ "data Foo = Foo"-        , "    { foo    :: String"-        , "    , barqux :: Int"-        , "    }"++--------------------------------------------------------------------------------+case10 :: Assertion+case10 = assertSnippet (step Nothing defaultConfig)+    [ "padQual  = case align' of"+    , "  Global -> True"+    , "  File    -> fileAlign"+    , "  Group    -> anyQual"+    ]+    [ "padQual  = case align' of"+    , "  Global -> True"+    , "  File   -> fileAlign"+    , "  Group  -> anyQual"+    ]+++--------------------------------------------------------------------------------+case11 :: Assertion+case11 = assertSnippet (step Nothing defaultConfig)+    [ "data Foo = Foo"+    , "    { foo  :: String"+    , "    , barqux  :: !Int"+    , "    }"+    ]+    [ "data Foo = Foo"+    , "    { foo    :: String"+    , "    , barqux :: !Int"+    , "    }"+    ]+++--------------------------------------------------------------------------------+case12 :: Assertion+case12 = assertSnippet (step Nothing defaultConfig {cCases = False}) input input+  where+    input =+        [ "case x of"+        , "  Just y -> 1"+        , "  Nothing -> 2"         ]
tests/Language/Haskell/Stylish/Tests.hs view
@@ -49,6 +49,7 @@         , "      first_field: \"indent 2\""         , "      field_comment: 2"         , "      deriving: 2"+        , "      via: \"indent 2\""         ]      actual <- format (Just $ ConfigPath "test-config.yaml") Nothing input@@ -73,6 +74,7 @@         , "      first_field: \"same_line\""         , "      field_comment: 2"         , "      deriving: 2"+        , "      via: \"indent 2\""         ]      actual <- format (Just $ ConfigPath "test-config.yaml") Nothing input@@ -98,10 +100,8 @@     fileLocation = "directory/File.hs"     input = "module Herp"     result = Left $-      "Language.Haskell.Stylish.Parse.parseModule: could not parse " <>-      fileLocation <>-      ": ParseFailed (SrcLoc \"<unknown>.hs\" 2 1) \"Parse error: EOF\""-+      fileLocation <> ": RealSrcSpan SrcSpanPoint \"directory/File.hs\" 2 1:" +      <> " parse error (possibly incorrect indentation or mismatched brackets)\n"  -------------------------------------------------------------------------------- -- | When providing current dir including folders and files.
tests/Language/Haskell/Stylish/Tests/Util.hs view
@@ -1,11 +1,21 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TypeFamilies   #-} module Language.Haskell.Stylish.Tests.Util     ( testStep+    , testStep'+    , Snippet (..)+    , testSnippet+    , assertSnippet     , withTestDirTree+    , (@=??)     ) where   -------------------------------------------------------------------------------- import           Control.Exception              (bracket, try)+import           Control.Monad.Writer           (execWriter, tell)+import           Data.List                      (intercalate)+import           GHC.Exts                       (IsList (..)) import           System.Directory               (createDirectory,                                                  getCurrentDirectory,                                                  getTemporaryDirectory,@@ -14,6 +24,8 @@ import           System.FilePath                ((</>)) import           System.IO.Error                (isAlreadyExistsError) import           System.Random                  (randomIO)+import           Test.HUnit                     (Assertion, assertFailure,+                                                 (@=?))   --------------------------------------------------------------------------------@@ -23,14 +35,45 @@  -------------------------------------------------------------------------------- testStep :: Step -> String -> String-testStep step str = case parseModule [] Nothing str of-    Left err      -> error err-    Right module' -> unlines $ stepFilter step ls module'+testStep s str = case s of+  Step _ step ->+    case parseModule [] Nothing str of+      Left err      -> error err+      Right module' -> unlines $ step ls module'   where     ls = lines str   --------------------------------------------------------------------------------+testStep' :: Step -> Lines -> Lines+testStep' s ls = lines $ testStep s (unlines ls)+++--------------------------------------------------------------------------------+-- | 'Lines' that show as a normal string.+newtype Snippet = Snippet {unSnippet :: Lines} deriving (Eq)++-- Prefix with one newline since so HUnit will use a newline after `got: ` or+-- `expected: `.+instance Show Snippet where show = unlines . ("" :) . unSnippet++instance IsList Snippet where+    type Item Snippet = String+    fromList = Snippet+    toList   = unSnippet+++--------------------------------------------------------------------------------+testSnippet :: Step -> Snippet -> Snippet+testSnippet s = Snippet . lines . testStep s . unlines . unSnippet+++--------------------------------------------------------------------------------+assertSnippet :: Step -> Snippet -> Snippet -> Assertion+assertSnippet step input expected = expected @=? testSnippet step input+++-------------------------------------------------------------------------------- -- | Create a temporary directory with a randomised name built from the template -- provided createTempDirectory :: String -> IO FilePath@@ -59,3 +102,15 @@         setCurrentDirectory current *>         removeDirectoryRecursive temp)     (\(_, temp) -> setCurrentDirectory temp *> action)++(@=??) :: Lines -> Lines -> Assertion+expected @=?? actual =+  if expected == actual then pure ()+  else assertFailure $ intercalate "\n" $ execWriter do+    tell ["Expected:"]+    printLines expected+    tell ["Got:"]+    printLines actual+  where+    printLines =+      mapM_ \line -> tell ["  " <> line]
tests/TestSuite.hs view
@@ -13,6 +13,8 @@ import qualified Language.Haskell.Stylish.Parse.Tests import qualified Language.Haskell.Stylish.Step.Data.Tests import qualified Language.Haskell.Stylish.Step.Imports.Tests+import qualified Language.Haskell.Stylish.Step.Imports.FelixTests+import qualified Language.Haskell.Stylish.Step.ModuleHeader.Tests import qualified Language.Haskell.Stylish.Step.LanguagePragmas.Tests import qualified Language.Haskell.Stylish.Step.SimpleAlign.Tests import qualified Language.Haskell.Stylish.Step.Squash.Tests@@ -29,7 +31,9 @@     , Language.Haskell.Stylish.Config.Tests.tests     , Language.Haskell.Stylish.Step.Data.Tests.tests     , Language.Haskell.Stylish.Step.Imports.Tests.tests+    , Language.Haskell.Stylish.Step.Imports.FelixTests.tests     , Language.Haskell.Stylish.Step.LanguagePragmas.Tests.tests+    , Language.Haskell.Stylish.Step.ModuleHeader.Tests.tests     , Language.Haskell.Stylish.Step.SimpleAlign.Tests.tests     , Language.Haskell.Stylish.Step.Squash.Tests.tests     , Language.Haskell.Stylish.Step.Tabs.Tests.tests