fourmolu 0.16.2.0 → 0.17.0.0
raw patch · 67 files changed
+1701/−152 lines, 67 filesdep ~DiffPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: Diff
API changes (from Hackage documentation)
+ Ormolu: [cfgLocalModules] :: Config region -> !Set ModuleName
+ Ormolu: [ciModules] :: CabalInfo -> ![ModuleName]
+ Ormolu: [poImportGrouping] :: PrinterOpts f -> f ImportGrouping
+ Ormolu: [poSortConstraints] :: PrinterOpts f -> f Bool
+ Ormolu: [poSortDerivedClasses] :: PrinterOpts f -> f Bool
+ Ormolu: [poSortDerivingClauses] :: PrinterOpts f -> f Bool
+ Ormolu: [poTrailingSectionOperators] :: PrinterOpts f -> f Bool
+ Ormolu.Config: ImportGroup :: !Maybe String -> !NonEmpty ImportGroupRule -> ImportGroup
+ Ormolu.Config: ImportGroupByQualified :: ImportGrouping
+ Ormolu.Config: ImportGroupByScope :: ImportGrouping
+ Ormolu.Config: ImportGroupByScopeThenQualified :: ImportGrouping
+ Ormolu.Config: ImportGroupCustom :: NonEmpty ImportGroup -> ImportGrouping
+ Ormolu.Config: ImportGroupLegacy :: ImportGrouping
+ Ormolu.Config: ImportGroupPreserve :: ImportGrouping
+ Ormolu.Config: ImportGroupRule :: !ImportModuleMatcher -> !QualifiedImportMatcher -> !ImportRulePriority -> ImportGroupRule
+ Ormolu.Config: ImportGroupSingle :: ImportGrouping
+ Ormolu.Config: ImportRulePriority :: Word8 -> ImportRulePriority
+ Ormolu.Config: MatchAllModules :: ImportModuleMatcher
+ Ormolu.Config: MatchBothQualifiedAndUnqualified :: QualifiedImportMatcher
+ Ormolu.Config: MatchGlob :: !Glob -> ImportModuleMatcher
+ Ormolu.Config: MatchLocalModules :: ImportModuleMatcher
+ Ormolu.Config: MatchQualifiedOnly :: QualifiedImportMatcher
+ Ormolu.Config: MatchUnqualifiedOnly :: QualifiedImportMatcher
+ Ormolu.Config: [cfgLocalModules] :: Config region -> !Set ModuleName
+ Ormolu.Config: [igName] :: ImportGroup -> !Maybe String
+ Ormolu.Config: [igRules] :: ImportGroup -> !NonEmpty ImportGroupRule
+ Ormolu.Config: [igrModuleMatcher] :: ImportGroupRule -> !ImportModuleMatcher
+ Ormolu.Config: [igrPriority] :: ImportGroupRule -> !ImportRulePriority
+ Ormolu.Config: [igrQualifiedMatcher] :: ImportGroupRule -> !QualifiedImportMatcher
+ Ormolu.Config: [poImportGrouping] :: PrinterOpts f -> f ImportGrouping
+ Ormolu.Config: [poSortConstraints] :: PrinterOpts f -> f Bool
+ Ormolu.Config: [poSortDerivedClasses] :: PrinterOpts f -> f Bool
+ Ormolu.Config: [poSortDerivingClauses] :: PrinterOpts f -> f Bool
+ Ormolu.Config: [poTrailingSectionOperators] :: PrinterOpts f -> f Bool
+ Ormolu.Config: data ImportGroup
+ Ormolu.Config: data ImportGroupRule
+ Ormolu.Config: data ImportGrouping
+ Ormolu.Config: data ImportModuleMatcher
+ Ormolu.Config: data QualifiedImportMatcher
+ Ormolu.Config: defaultImportRulePriority :: ImportRulePriority
+ Ormolu.Config: matchAllRulePriority :: ImportRulePriority
+ Ormolu.Config: matchLocalRulePriority :: ImportRulePriority
+ Ormolu.Config: newtype ImportRulePriority
+ Ormolu.Imports.Grouping: Import :: ModuleName -> Bool -> Import
+ Ormolu.Imports.Grouping: [importName] :: Import -> ModuleName
+ Ormolu.Imports.Grouping: [importQualified] :: Import -> Bool
+ Ormolu.Imports.Grouping: data Import
+ Ormolu.Imports.Grouping: groupImports :: forall x. ImportGrouping -> Set ModuleName -> (x -> Import) -> [x] -> [[x]]
+ Ormolu.Imports.Grouping: prepareExistingGroups :: ImportGrouping -> Bool -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]
+ Ormolu.Parser.Result: [prLocalModules] :: ParseResult -> Set ModuleName
+ Ormolu.Printer: [poImportGrouping] :: PrinterOpts f -> f ImportGrouping
+ Ormolu.Printer: [poSortConstraints] :: PrinterOpts f -> f Bool
+ Ormolu.Printer: [poSortDerivedClasses] :: PrinterOpts f -> f Bool
+ Ormolu.Printer: [poSortDerivingClauses] :: PrinterOpts f -> f Bool
+ Ormolu.Printer: [poTrailingSectionOperators] :: PrinterOpts f -> f Bool
+ Ormolu.Printer.Combinators: getLocalModules :: R (Set ModuleName)
+ Ormolu.Printer.Combinators: multilineCommentNewline :: R ()
+ Ormolu.Printer.Combinators: txtStripIndent :: Text -> R ()
+ Ormolu.Printer.Internal: getLocalModules :: R (Set ModuleName)
+ Ormolu.Printer.Internal: multilineCommentNewline :: R ()
+ Ormolu.Printer.Internal: txtStripIndent :: Text -> R ()
+ Ormolu.Printer.Meat.Declaration.Data: instance GHC.Classes.Eq Ormolu.Printer.Meat.Declaration.Data.DerivingClauseSortKey
+ Ormolu.Printer.Meat.Declaration.Data: instance GHC.Classes.Ord Ormolu.Printer.Meat.Declaration.Data.DerivingClauseSortKey
+ Ormolu.Utils.Cabal: [ciModules] :: CabalInfo -> ![ModuleName]
+ Ormolu.Utils.Glob: data Glob
+ Ormolu.Utils.Glob: instance GHC.Classes.Eq Ormolu.Utils.Glob.Glob
+ Ormolu.Utils.Glob: instance GHC.Classes.Eq Ormolu.Utils.Glob.GlobPart
+ Ormolu.Utils.Glob: instance GHC.Show.Show Ormolu.Utils.Glob.Glob
+ Ormolu.Utils.Glob: instance GHC.Show.Show Ormolu.Utils.Glob.GlobPart
+ Ormolu.Utils.Glob: matchesGlob :: String -> Glob -> Bool
+ Ormolu.Utils.Glob: mkGlob :: String -> Glob
- Ormolu: CabalInfo :: !PackageName -> ![DynOption] -> !Set PackageName -> !FilePath -> CabalInfo
+ Ormolu: CabalInfo :: !PackageName -> ![DynOption] -> !Set PackageName -> !FilePath -> ![ModuleName] -> CabalInfo
- Ormolu: Config :: ![DynOption] -> !FixityOverrides -> !ModuleReexports -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> !PrinterOptsTotal -> Config region
+ Ormolu: Config :: ![DynOption] -> !FixityOverrides -> !ModuleReexports -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> !PrinterOptsTotal -> !Set ModuleName -> Config region
- Ormolu: PrinterOpts :: f Int -> f ColumnLimit -> f FunctionArrowsStyle -> f CommaStyle -> f ImportExportStyle -> f Bool -> f Bool -> f Int -> f HaddockPrintStyle -> f HaddockPrintStyleModule -> f LetStyle -> f InStyle -> f SingleConstraintParens -> f SingleDerivingParens -> f Unicode -> f Bool -> PrinterOpts f
+ Ormolu: PrinterOpts :: f Int -> f ColumnLimit -> f FunctionArrowsStyle -> f CommaStyle -> f ImportExportStyle -> f ImportGrouping -> f Bool -> f Bool -> f Int -> f HaddockPrintStyle -> f HaddockPrintStyleModule -> f LetStyle -> f InStyle -> f SingleConstraintParens -> f SingleDerivingParens -> f Bool -> f Bool -> f Bool -> f Bool -> f Unicode -> f Bool -> PrinterOpts f
- Ormolu.Config: Config :: ![DynOption] -> !FixityOverrides -> !ModuleReexports -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> !PrinterOptsTotal -> Config region
+ Ormolu.Config: Config :: ![DynOption] -> !FixityOverrides -> !ModuleReexports -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> !PrinterOptsTotal -> !Set ModuleName -> Config region
- Ormolu.Config: PrinterOpts :: f Int -> f ColumnLimit -> f FunctionArrowsStyle -> f CommaStyle -> f ImportExportStyle -> f Bool -> f Bool -> f Int -> f HaddockPrintStyle -> f HaddockPrintStyleModule -> f LetStyle -> f InStyle -> f SingleConstraintParens -> f SingleDerivingParens -> f Unicode -> f Bool -> PrinterOpts f
+ Ormolu.Config: PrinterOpts :: f Int -> f ColumnLimit -> f FunctionArrowsStyle -> f CommaStyle -> f ImportExportStyle -> f ImportGrouping -> f Bool -> f Bool -> f Int -> f HaddockPrintStyle -> f HaddockPrintStyleModule -> f LetStyle -> f InStyle -> f SingleConstraintParens -> f SingleDerivingParens -> f Bool -> f Bool -> f Bool -> f Bool -> f Unicode -> f Bool -> PrinterOpts f
- Ormolu.Imports: normalizeImports :: Bool -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]
+ Ormolu.Imports: normalizeImports :: Bool -> Set ModuleName -> ImportGrouping -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]
- Ormolu.Parser.Result: ParseResult :: HsModule GhcPs -> SourceType -> Maybe LComment -> [([LComment], Pragma)] -> CommentStream -> EnumSet Extension -> ModuleFixityMap -> Int -> ParseResult
+ Ormolu.Parser.Result: ParseResult :: HsModule GhcPs -> SourceType -> Maybe LComment -> [([LComment], Pragma)] -> CommentStream -> EnumSet Extension -> ModuleFixityMap -> Int -> Set ModuleName -> ParseResult
- Ormolu.Printer: PrinterOpts :: f Int -> f ColumnLimit -> f FunctionArrowsStyle -> f CommaStyle -> f ImportExportStyle -> f Bool -> f Bool -> f Int -> f HaddockPrintStyle -> f HaddockPrintStyleModule -> f LetStyle -> f InStyle -> f SingleConstraintParens -> f SingleDerivingParens -> f Unicode -> f Bool -> PrinterOpts f
+ Ormolu.Printer: PrinterOpts :: f Int -> f ColumnLimit -> f FunctionArrowsStyle -> f CommaStyle -> f ImportExportStyle -> f ImportGrouping -> f Bool -> f Bool -> f Int -> f HaddockPrintStyle -> f HaddockPrintStyleModule -> f LetStyle -> f InStyle -> f SingleConstraintParens -> f SingleDerivingParens -> f Bool -> f Bool -> f Bool -> f Bool -> f Unicode -> f Bool -> PrinterOpts f
- Ormolu.Printer.Combinators: runR :: R () -> SpanStream -> CommentStream -> PrinterOptsTotal -> SourceType -> EnumSet Extension -> ModuleFixityMap -> Bool -> Text
+ Ormolu.Printer.Combinators: runR :: R () -> SpanStream -> CommentStream -> PrinterOptsTotal -> Set ModuleName -> SourceType -> EnumSet Extension -> ModuleFixityMap -> Bool -> Text
- Ormolu.Printer.Internal: runR :: R () -> SpanStream -> CommentStream -> PrinterOptsTotal -> SourceType -> EnumSet Extension -> ModuleFixityMap -> Bool -> Text
+ Ormolu.Printer.Internal: runR :: R () -> SpanStream -> CommentStream -> PrinterOptsTotal -> Set ModuleName -> SourceType -> EnumSet Extension -> ModuleFixityMap -> Bool -> Text
- Ormolu.Utils.Cabal: CabalInfo :: !PackageName -> ![DynOption] -> !Set PackageName -> !FilePath -> CabalInfo
+ Ormolu.Utils.Cabal: CabalInfo :: !PackageName -> ![DynOption] -> !Set PackageName -> !FilePath -> ![ModuleName] -> CabalInfo
Files
- CHANGELOG.md +19/−0
- README.md +15/−3
- app/Main.hs +6/−0
- data/examples/declaration/data/infix-haddocks-four-out.hs +4/−3
- data/examples/declaration/data/operators-four-out.hs +9/−6
- data/examples/declaration/data/single-constructor-with-haddock-four-out.hs +3/−2
- data/examples/declaration/splice/bracket-declaration-out.hs +4/−4
- data/examples/declaration/splice/untyped-splice-out.hs +1/−1
- data/examples/declaration/value/function/infix/esqueleto-1-out.hs +1/−1
- data/examples/declaration/value/function/let-multi-line-out.hs +1/−1
- data/examples/declaration/value/function/operators-6-out.hs +2/−2
- data/examples/declaration/value/function/pattern/famous-cardano-pattern-out.hs +2/−2
- data/examples/declaration/value/function/pattern/splice-pattern-out.hs +2/−2
- data/examples/declaration/value/function/required-type-arguments-out.hs +1/−1
- data/examples/fourmolu/multiline-haddocks-four-out.hs +6/−4
- data/examples/import/docstrings-after-exports-four-out.hs +4/−3
- data/fourmolu/import-export/output-ImportExportDiffFriendly.hs +7/−5
- data/fourmolu/import-export/output-ImportExportLeading.hs +7/−5
- data/fourmolu/import-export/output-ImportExportTrailing.hs +7/−5
- data/fourmolu/import-grouping/input.hs +13/−0
- data/fourmolu/import-grouping/output-ImportGroupByQualified.hs +12/−0
- data/fourmolu/import-grouping/output-ImportGroupByScope.hs +12/−0
- data/fourmolu/import-grouping/output-ImportGroupByScopeThenQualified.hs +14/−0
- data/fourmolu/import-grouping/output-ImportGroupPreserve.hs +13/−0
- data/fourmolu/import-grouping/output-ImportGroupSingle.hs +11/−0
- data/fourmolu/import-grouping/output-custom.hs +14/−0
- data/fourmolu/single-constraint-parens/input.hs +3/−0
- data/fourmolu/single-constraint-parens/output-ConstraintAlways.hs +2/−0
- data/fourmolu/single-constraint-parens/output-ConstraintAuto.hs +2/−0
- data/fourmolu/single-constraint-parens/output-ConstraintNever.hs +2/−0
- data/fourmolu/sort-constraints/input.hs +43/−0
- data/fourmolu/sort-constraints/output-False.hs +45/−0
- data/fourmolu/sort-constraints/output-True.hs +45/−0
- data/fourmolu/sort-derived-classes/input.hs +18/−0
- data/fourmolu/sort-derived-classes/output-False.hs +19/−0
- data/fourmolu/sort-derived-classes/output-True.hs +19/−0
- data/fourmolu/sort-deriving-clauses/input.hs +12/−0
- data/fourmolu/sort-deriving-clauses/output-False.hs +12/−0
- data/fourmolu/sort-deriving-clauses/output-True.hs +13/−0
- data/fourmolu/trailing-section-operators/input.hs +18/−0
- data/fourmolu/trailing-section-operators/output-False.hs +22/−0
- data/fourmolu/trailing-section-operators/output-True.hs +21/−0
- fourmolu.cabal +9/−3
- fourmolu.yaml +6/−0
- src/Ormolu.hs +4/−1
- src/Ormolu/Config.hs +15/−2
- src/Ormolu/Config/Gen.hs +112/−0
- src/Ormolu/Config/Types.hs +109/−0
- src/Ormolu/Diff/ParseResult.hs +55/−11
- src/Ormolu/Imports.hs +18/−13
- src/Ormolu/Imports/Grouping.hs +167/−0
- src/Ormolu/Parser.hs +3/−1
- src/Ormolu/Parser/Result.hs +6/−2
- src/Ormolu/Printer.hs +1/−0
- src/Ormolu/Printer/Combinators.hs +3/−0
- src/Ormolu/Printer/Comments.hs +5/−5
- src/Ormolu/Printer/Internal.hs +70/−2
- src/Ormolu/Printer/Meat/Common.hs +6/−32
- src/Ormolu/Printer/Meat/Declaration/Data.hs +24/−3
- src/Ormolu/Printer/Meat/Declaration/OpTree.hs +8/−4
- src/Ormolu/Printer/Meat/Module.hs +4/−2
- src/Ormolu/Printer/Meat/Type.hs +5/−1
- src/Ormolu/Utils/Cabal.hs +42/−3
- src/Ormolu/Utils/Glob.hs +50/−0
- tests/Ormolu/Config/PrinterOptsSpec.hs +135/−16
- tests/Ormolu/ConfigSpec.hs +308/−1
- tests/Ormolu/Utils/GlobSpec.hs +50/−0
CHANGELOG.md view
@@ -1,3 +1,22 @@+## Fourmolu 0.17.0.0++* Add new `import-grouping` option to group imports with grouping rules specified in configuration ([#403](https://github.com/fourmolu/fourmolu/pull/403))++* Add new `sort-constraints` option to sort constraints alphabetically ([#433](https://github.com/fourmolu/fourmolu/pull/433))++* Add new `sort-derived-classes` option to sort classes in deriving clauses ([#434](https://github.com/fourmolu/fourmolu/pull/434))++* Add new `sort-derived-clauses` option to sort classes deriving clauses ([#434](https://github.com/fourmolu/fourmolu/pull/434))++* Add new `trailing-section-operators` option to disable trailing "section" operators (those that are `infixr 0`, such as `$`) ([#444](https://github.com/fourmolu/fourmolu/pull/444))++* Fix issue where `single-constraint-parens: never` would drop parentheses around implicit parameters ([#446](https://github.com/fourmolu/fourmolu/issues/446))++* Fix indentation for parenthesized expressions that start off the indentation column ([#428](https://github.com/fourmolu/fourmolu/issues/428))++* Allow multiline comments in indented contexts ([#65](https://github.com/fourmolu/fourmolu/issues/65))++ ## Fourmolu 0.16.2.0 ### Upstream changes:
README.md view
@@ -18,6 +18,7 @@ * [Troubleshooting](#troubleshooting) * [Operators are being formatted weirdly!](#operators-are-being-formatted-weirdly) * [Limitations](#limitations)+* [Breaking changes policy](#breaking-changes-policy) * [Contributing](#contributing) * [License](#license) @@ -244,7 +245,7 @@ * If this is a custom operator, see the instructions in the [Language extensions, dependencies, and fixities](#language-extensions-dependencies-and-fixities) section to- specify the correct fixities in a `.ormolu` file.+ specify the correct fixities in a `fourmolu.yaml` file. * If this is a third-party operator (e.g. from `base` or some other package from Hackage), Ormolu probably doesn't recognize that the operator is the@@ -256,7 +257,7 @@ * You might have `-XNoImplicitPrelude` turned on If any of these are true, make sure to specify the reexports correctly in- a `.ormolu` file.+ a `fourmolu.yaml` file. You can see how Ormolu decides the fixity of operators if you use `--debug`. @@ -268,7 +269,18 @@ declarations. See the [CPP](https://github.com/tweag/ormolu/blob/master/DESIGN.md#cpp) section in the design notes for a discussion of the dangers. * Various minor idempotence issues, most of them are related to comments or column limits.-* Fourmolu is in a fairly early stage of development. The implementation should be as stable as Ormolu, as it only makes minimal changes, and is extensively tested. But the default configuration style may change in some minor ways in the near future, as we make more options available. It will always be possible to replicate the old default behaviour with a suitable `fourmolu.yaml`.++## Breaking changes policy++Fourmolu is still in a relatively early stage of development, but it is in wide enough use that stability is a desirable property. Fourmolu aims to uphold the following principles:++1. It will always be possible to replicate Ormolu's formatting with a suitable `fourmolu.yaml`++1. Breaking changes will be avoided where possible, but may still occur in the following circumstances:++ * Fourmolu inherits a breaking change from Ormolu+ * The change reverts a prior breaking change, which caused a regression+ * Other exceptional situations, on a case-by-case basis ## Contributing
app/Main.hs view
@@ -474,6 +474,12 @@ ] ) <*> pure defaultPrinterOpts -- unused; overwritten in resolveConfig+ <*> (fmap Set.fromList . many . strOption . mconcat)+ [ long "local-modules",+ short 'm',+ metavar "LOCAL_MODULES",+ help "Modules Fourmolu should consider as local by the current Cabal package"+ ] sourceTypeParser :: Parser (Maybe SourceType) sourceTypeParser =
data/examples/declaration/data/infix-haddocks-four-out.hs view
@@ -23,9 +23,10 @@ right data DocPartial- = Left -- ^ left docs- -- on multiple- -- lines+ = Left {- ^ left docs+ on multiple+ lines+ -} :*: Right | -- | op
data/examples/declaration/data/operators-four-out.hs view
@@ -1,12 +1,15 @@ data ErrorMessage' s = -- | Show the text as is. Text s- | -- | Pretty print the type.- -- @ShowType :: k -> ErrorMessage@+ | {- | Pretty print the type.+ @ShowType :: k -> ErrorMessage@+ -} forall t. ShowType t- | -- | Put two pieces of error message next- -- to each other.+ | {- | Put two pieces of error message next+ to each other.+ -} ErrorMessage' s :<>: ErrorMessage' s- | -- | Stack two pieces of error message on top- -- of each other.+ | {- | Stack two pieces of error message on top+ of each other.+ -} ErrorMessage' s :$$: ErrorMessage' s
data/examples/declaration/data/single-constructor-with-haddock-four-out.hs view
@@ -1,6 +1,7 @@ data FutureSumType- = -- | This is what the documentation for this type looks like.- -- It is pretty awkward that the @--@s aren't aligned.+ = {- | This is what the documentation for this type looks like.+ It is pretty awkward that the @--@s aren't aligned.+ -} SingleConstructor { someRecordField :: Bool , someOtherRecordField :: Int
data/examples/declaration/splice/bracket-declaration-out.hs view
@@ -19,10 +19,10 @@ $(singletons [d|data T = T deriving (Eq, Ord, Enum, Bounded, Show)|]) $( singletons- [d|- data T = T- deriving (Eq, Ord, Enum, Bounded, Show)- |]+ [d|+ data T = T+ deriving (Eq, Ord, Enum, Bounded, Show)+ |] ) foo = [d| type X = * |]
data/examples/declaration/splice/untyped-splice-out.hs view
@@ -4,7 +4,7 @@ x = $( foo- bar+ bar ) x = $foo
data/examples/declaration/value/function/infix/esqueleto-1-out.hs view
@@ -5,5 +5,5 @@ E.table `E.innerJoin` E.table `E.on` ( \(a :& b) ->- a E.^. AField E.==. b E.^. BField+ a E.^. AField E.==. b E.^. BField )
data/examples/declaration/value/function/let-multi-line-out.hs view
@@ -14,7 +14,7 @@ inlineComment :: Int -> Int inlineComment = let {- join -} go = case () of- () -> undefined+ () -> undefined in go implicitParams :: (HasCallStack) => Int
data/examples/declaration/value/function/operators-6-out.hs view
@@ -3,9 +3,9 @@ type PermuteRef = "a" :> ( "b" :> "c" :> End- :<|> "c" :> "b" :> End+ :<|> "c" :> "b" :> End ) :<|> "b" :> ( "a" :> "c" :> End- :<|> "c" :> "a" :> End+ :<|> "c" :> "a" :> End )
data/examples/declaration/value/function/pattern/famous-cardano-pattern-out.hs view
@@ -4,5 +4,5 @@ :<|> restartNodeR ) :<|> ( getUtxoR- :<|> getConfirmedProposalsR- ) = client nodeV1Api+ :<|> getConfirmedProposalsR+ ) = client nodeV1Api
data/examples/declaration/value/function/pattern/splice-pattern-out.hs view
@@ -6,8 +6,8 @@ multiline = case () of $( x- + y+ + y ) -> () $( y- "something"+ "something" ) -> ()
data/examples/declaration/value/function/required-type-arguments-out.hs view
@@ -16,6 +16,6 @@ foo = f ( type ( Maybe- Int+ Int ) )
data/examples/fourmolu/multiline-haddocks-four-out.hs view
@@ -1,12 +1,14 @@ -- https://github.com/fourmolu/fourmolu/pull/64 module Foo (- -- | Multiline comment- -- in header+ {- | Multiline comment+ in header+ -} foo, ) where class Foo a where- -- | Multiline haddock- -- in class+ {- | Multiline haddock+ in class+ -} multiline :: a
data/examples/import/docstrings-after-exports-four-out.hs view
@@ -5,7 +5,8 @@ SinceType (..), -- ^ @since 4.0 SinceClass (..), -- ^ @since 5.0 Multi (..),- -- ^ since 6.0- -- multi- -- line+ {- ^ since 6.0+ multi+ line+ -} ) where
data/fourmolu/import-export/output-ImportExportDiffFriendly.hs view
@@ -3,8 +3,9 @@ -- * Something bar,- -- | A multiline- -- comment here+ {- | A multiline+ comment here+ -} baz, -- * Another thing@@ -61,7 +62,8 @@ SinceType (..), -- ^ @since 4.0 SinceClass (..), -- ^ @since 5.0 Multi (..),- -- ^ since 6.0- -- multi- -- line+ {- ^ since 6.0+ multi+ line+ -} ) where
data/fourmolu/import-export/output-ImportExportLeading.hs view
@@ -3,8 +3,9 @@ -- * Something , bar- -- | A multiline- -- comment here+ {- | A multiline+ comment here+ -} , baz -- * Another thing@@ -61,7 +62,8 @@ , SinceType (..) -- ^ @since 4.0 , SinceClass (..) -- ^ @since 5.0 , Multi (..)- -- ^ since 6.0- -- multi- -- line+ {- ^ since 6.0+ multi+ line+ -} ) where
data/fourmolu/import-export/output-ImportExportTrailing.hs view
@@ -3,8 +3,9 @@ -- * Something bar,- -- | A multiline- -- comment here+ {- | A multiline+ comment here+ -} baz, -- * Another thing@@ -61,7 +62,8 @@ SinceType (..), -- ^ @since 4.0 SinceClass (..), -- ^ @since 5.0 Multi (..),- -- ^ since 6.0- -- multi- -- line+ {- ^ since 6.0+ multi+ line+ -} ) where
+ data/fourmolu/import-grouping/input.hs view
@@ -0,0 +1,13 @@+module Main where++import Data.Text (Text)+import Data.Maybe (maybe)++import qualified Data.Text+import Control.Monad (Monad (..))++import qualified System.IO as SIO+import SomeInternal.Module1 (anotherDefinition, someDefinition)+import qualified SomeInternal.Module2 as Mod2+import Text.Printf (printf)+import qualified SomeModule
+ data/fourmolu/import-grouping/output-ImportGroupByQualified.hs view
@@ -0,0 +1,12 @@+module Main where++import Control.Monad (Monad (..))+import Data.Maybe (maybe)+import Data.Text (Text)+import SomeInternal.Module1 (anotherDefinition, someDefinition)+import Text.Printf (printf)++import qualified Data.Text+import qualified SomeInternal.Module2 as Mod2+import qualified SomeModule+import qualified System.IO as SIO
+ data/fourmolu/import-grouping/output-ImportGroupByScope.hs view
@@ -0,0 +1,12 @@+module Main where++import Control.Monad (Monad (..))+import Data.Maybe (maybe)+import Data.Text (Text)+import qualified Data.Text+import qualified SomeModule+import qualified System.IO as SIO+import Text.Printf (printf)++import SomeInternal.Module1 (anotherDefinition, someDefinition)+import qualified SomeInternal.Module2 as Mod2
+ data/fourmolu/import-grouping/output-ImportGroupByScopeThenQualified.hs view
@@ -0,0 +1,14 @@+module Main where++import Control.Monad (Monad (..))+import Data.Maybe (maybe)+import Data.Text (Text)+import Text.Printf (printf)++import qualified Data.Text+import qualified SomeModule+import qualified System.IO as SIO++import SomeInternal.Module1 (anotherDefinition, someDefinition)++import qualified SomeInternal.Module2 as Mod2
+ data/fourmolu/import-grouping/output-ImportGroupPreserve.hs view
@@ -0,0 +1,13 @@+module Main where++import Data.Maybe (maybe)+import Data.Text (Text)++import Control.Monad (Monad (..))+import qualified Data.Text++import SomeInternal.Module1 (anotherDefinition, someDefinition)+import qualified SomeInternal.Module2 as Mod2+import qualified SomeModule+import qualified System.IO as SIO+import Text.Printf (printf)
+ data/fourmolu/import-grouping/output-ImportGroupSingle.hs view
@@ -0,0 +1,11 @@+module Main where++import Control.Monad (Monad (..))+import Data.Maybe (maybe)+import Data.Text (Text)+import qualified Data.Text+import SomeInternal.Module1 (anotherDefinition, someDefinition)+import qualified SomeInternal.Module2 as Mod2+import qualified SomeModule+import qualified System.IO as SIO+import Text.Printf (printf)
+ data/fourmolu/import-grouping/output-custom.hs view
@@ -0,0 +1,14 @@+module Main where++import Data.Text (Text)+import qualified Data.Text++import Control.Monad (Monad (..))+import Data.Maybe (maybe)+import Text.Printf (printf)++import qualified SomeInternal.Module2 as Mod2++import SomeInternal.Module1 (anotherDefinition, someDefinition)+import qualified SomeModule+import qualified System.IO as SIO
data/fourmolu/single-constraint-parens/input.hs view
@@ -9,3 +9,6 @@ -- https://github.com/fourmolu/fourmolu/issues/374 nestedConstraint :: ((c a, c b) => c (f a b)) => Is c a -> Is c b -> Is c (f a b)++-- https://github.com/fourmolu/fourmolu/issues/446+implicitParam :: (?a :: Int) => Int
data/fourmolu/single-constraint-parens/output-ConstraintAlways.hs view
@@ -7,3 +7,5 @@ quantifiedConstraint :: (forall a. (Show a) => Show (f a)) => f Int -- https://github.com/fourmolu/fourmolu/issues/374 nestedConstraint :: ((c a, c b) => c (f a b)) => Is c a -> Is c b -> Is c (f a b)+-- https://github.com/fourmolu/fourmolu/issues/446+implicitParam :: (?a :: Int) => Int
data/fourmolu/single-constraint-parens/output-ConstraintAuto.hs view
@@ -7,3 +7,5 @@ quantifiedConstraint :: (forall a. Show a => Show (f a)) => f Int -- https://github.com/fourmolu/fourmolu/issues/374 nestedConstraint :: ((c a, c b) => c (f a b)) => Is c a -> Is c b -> Is c (f a b)+-- https://github.com/fourmolu/fourmolu/issues/446+implicitParam :: (?a :: Int) => Int
data/fourmolu/single-constraint-parens/output-ConstraintNever.hs view
@@ -7,3 +7,5 @@ quantifiedConstraint :: (forall a. Show a => Show (f a)) => f Int -- https://github.com/fourmolu/fourmolu/issues/374 nestedConstraint :: ((c a, c b) => c (f a b)) => Is c a -> Is c b -> Is c (f a b)+-- https://github.com/fourmolu/fourmolu/issues/446+implicitParam :: (?a :: Int) => Int
+ data/fourmolu/sort-constraints/input.hs view
@@ -0,0 +1,43 @@+module Main where++f1 :: (Show a, Eq a) => a+f1 a = a++f2 ::+ (Show a+ , Eq a)+ => a+f2 a = a++-- We don't currently realise that the inner tuple type+-- is a constraint tuple.+f3 ::+ ( Generic a+ , (Show a, Num a)+ , Eq a)+ => a+f3 a = a++f4 ::+ (Show a, Num a)+ =>+ Eq a+ => a+f4 a = a++f5 ::+ (Show a+ -- an interloping comment+ , Eq a)+ => a+f5 a = a++class (Show a, Eq a) => Class1 a where++data (Generic a, (Show a, Eq a)) => A a++deriving instance (Show a, Eq a) => Class1 Int++-- We can't know this is a constraint tuple type rather than a normal+-- tuple type without type information+type MyConstraints a = (Show a, Eq a)
+ data/fourmolu/sort-constraints/output-False.hs view
@@ -0,0 +1,45 @@+module Main where++f1 :: (Show a, Eq a) => a+f1 a = a++f2 ::+ ( Show a+ , Eq a+ ) =>+ a+f2 a = a++-- We don't currently realise that the inner tuple type+-- is a constraint tuple.+f3 ::+ ( Generic a+ , (Show a, Num a)+ , Eq a+ ) =>+ a+f3 a = a++f4 ::+ (Show a, Num a) =>+ (Eq a) =>+ a+f4 a = a++f5 ::+ ( Show a+ , -- an interloping comment+ Eq a+ ) =>+ a+f5 a = a++class (Show a, Eq a) => Class1 a++data (Generic a, (Show a, Eq a)) => A a++deriving instance (Show a, Eq a) => Class1 Int++-- We can't know this is a constraint tuple type rather than a normal+-- tuple type without type information+type MyConstraints a = (Show a, Eq a)
+ data/fourmolu/sort-constraints/output-True.hs view
@@ -0,0 +1,45 @@+module Main where++f1 :: (Eq a, Show a) => a+f1 a = a++f2 ::+ ( Eq a+ , Show a+ ) =>+ a+f2 a = a++-- We don't currently realise that the inner tuple type+-- is a constraint tuple.+f3 ::+ ( (Show a, Num a)+ , Eq a+ , Generic a+ ) =>+ a+f3 a = a++f4 ::+ (Num a, Show a) =>+ (Eq a) =>+ a+f4 a = a++f5 ::+ ( -- an interloping comment+ Eq a+ , Show a+ ) =>+ a+f5 a = a++class (Eq a, Show a) => Class1 a++data ((Show a, Eq a), Generic a) => A a++deriving instance (Eq a, Show a) => Class1 Int++-- We can't know this is a constraint tuple type rather than a normal+-- tuple type without type information+type MyConstraints a = (Show a, Eq a)
+ data/fourmolu/sort-derived-classes/input.hs view
@@ -0,0 +1,18 @@+module Main where++data A+ deriving (Show, Eq, Ord)++newtype B = B Int+ deriving newtype (Num)+ deriving stock (Show, Eq, Generic)+ deriving (Semigroup, Monoid) via (Generically B)+ deriving stock (Ord, Generic)+ deriving (ToJSON)++data C+ deriving + (Show+ , Eq+ -- A comment that will end up in an odd place+ , Ord)
+ data/fourmolu/sort-derived-classes/output-False.hs view
@@ -0,0 +1,19 @@+module Main where++data A+ deriving (Show, Eq, Ord)++newtype B = B Int+ deriving newtype (Num)+ deriving stock (Show, Eq, Generic)+ deriving (Semigroup, Monoid) via (Generically B)+ deriving stock (Ord, Generic)+ deriving (ToJSON)++data C+ deriving+ ( Show+ , Eq+ , -- A comment that will end up in an odd place+ Ord+ )
+ data/fourmolu/sort-derived-classes/output-True.hs view
@@ -0,0 +1,19 @@+module Main where++data A+ deriving (Eq, Ord, Show)++newtype B = B Int+ deriving newtype (Num)+ deriving stock (Eq, Generic, Show)+ deriving (Monoid, Semigroup) via (Generically B)+ deriving stock (Generic, Ord)+ deriving (ToJSON)++data C+ deriving+ ( Eq+ , -- A comment that will end up in an odd place+ Ord+ , Show+ )
+ data/fourmolu/sort-deriving-clauses/input.hs view
@@ -0,0 +1,12 @@+module Main where++newtype A = A Int+ deriving newtype (Num)+ deriving stock (Show, Eq)+ deriving stock (Ord, Generic)+ deriving (ToJSON)++data B + -- A comment that will end up in an odd place+ deriving stock (Show)+ deriving (Eq)
+ data/fourmolu/sort-deriving-clauses/output-False.hs view
@@ -0,0 +1,12 @@+module Main where++newtype A = A Int+ deriving newtype (Num)+ deriving stock (Show, Eq)+ deriving stock (Ord, Generic)+ deriving (ToJSON)++data B+ -- A comment that will end up in an odd place+ deriving stock (Show)+ deriving (Eq)
+ data/fourmolu/sort-deriving-clauses/output-True.hs view
@@ -0,0 +1,13 @@+module Main where++newtype A = A Int+ deriving (ToJSON)+ deriving stock (Show, Eq)+ deriving stock (Ord, Generic)+ deriving newtype (Num)++data B+ -- A comment that will end up in an odd place++ deriving (Eq)+ deriving stock (Show)
+ data/fourmolu/trailing-section-operators/input.hs view
@@ -0,0 +1,18 @@+someShortFunction $ someShortFunction x++someShortFunction $ someShortFunction $ \x -> do+ putStrLn x++someLongFunction $ someLongFunction+ $ someLongFunction+ $ someLongFunction $ someLongFunction x++someLongFunction+ $ someLongFunction+ $ someLongFunction $ someLongFunction $ \x -> do+ putStrLn x++someLongFunction $ someLongFunction $ someLongFunction $ someLongFunction $ \x -> putStrLn x++someLongFunction $ someLongFunction $ someLongFunction $ someLongFunction $ \x -> do+ putStrLn x
+ data/fourmolu/trailing-section-operators/output-False.hs view
@@ -0,0 +1,22 @@+someShortFunction $ someShortFunction x++someShortFunction $ someShortFunction $ \x -> do+ putStrLn x++someLongFunction+ $ someLongFunction+ $ someLongFunction+ $ someLongFunction+ $ someLongFunction x++someLongFunction+ $ someLongFunction+ $ someLongFunction+ $ someLongFunction+ $ \x -> do+ putStrLn x++someLongFunction $ someLongFunction $ someLongFunction $ someLongFunction $ \x -> putStrLn x++someLongFunction $ someLongFunction $ someLongFunction $ someLongFunction $ \x -> do+ putStrLn x
+ data/fourmolu/trailing-section-operators/output-True.hs view
@@ -0,0 +1,21 @@+someShortFunction $ someShortFunction x++someShortFunction $ someShortFunction $ \x -> do+ putStrLn x++someLongFunction $+ someLongFunction $+ someLongFunction $+ someLongFunction $+ someLongFunction x++someLongFunction $+ someLongFunction $+ someLongFunction $+ someLongFunction $ \x -> do+ putStrLn x++someLongFunction $ someLongFunction $ someLongFunction $ someLongFunction $ \x -> putStrLn x++someLongFunction $ someLongFunction $ someLongFunction $ someLongFunction $ \x -> do+ putStrLn x
fourmolu.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: fourmolu-version: 0.16.2.0+version: 0.17.0.0 license: BSD-3-Clause license-file: LICENSE.md maintainer:@@ -52,6 +52,7 @@ Ormolu.Diff.Text Ormolu.Exception Ormolu.Imports+ Ormolu.Imports.Grouping Ormolu.Parser Ormolu.Parser.CommentStream Ormolu.Parser.Pragma@@ -96,17 +97,21 @@ Ormolu.Utils Ormolu.Utils.Cabal Ormolu.Utils.Fixity+ Ormolu.Utils.Glob Ormolu.Utils.IO Paths_fourmolu other-modules: Ormolu.Config.Gen+ Ormolu.Config.Types+ autogen-modules:+ Paths_fourmolu hs-source-dirs: src other-modules: GHC.DynFlags default-language: GHC2021 build-depends: Cabal-syntax >=3.12 && <3.13,- Diff >=0.4 && <1,+ Diff >=0.4 && <2, MemoTrie >=0.6 && <0.7, ansi-terminal >=0.10 && <1.2, array >=0.5 && <0.6,@@ -214,9 +219,10 @@ Ormolu.Integration.FixitySpec Ormolu.Integration.RegionSpec Ormolu.Integration.Utils+ Ormolu.Utils.GlobSpec build-depends: bytestring,- Diff >=0.3 && <1,+ Diff >=1 && <2, pretty >=1.0 && <2.0, process >=1.6 && <2.0, yaml,
fourmolu.yaml view
@@ -6,6 +6,7 @@ function-arrows: trailing comma-style: trailing import-export-style: trailing+import-grouping: legacy indent-wheres: true record-brace-space: true newlines-between-decls: 1@@ -15,7 +16,12 @@ in-style: right-align single-constraint-parens: always single-deriving-parens: always+sort-constraints: false+sort-derived-classes: false+sort-deriving-clauses: false+trailing-section-operators: true unicode: never respectful: false fixities: [] reexports: []+local-modules: []
src/Ormolu.hs view
@@ -221,7 +221,10 @@ ], cfgDependencies = Set.union (cfgDependencies rawConfig) depsFromCabal,- cfgSourceType = sourceType+ cfgSourceType = sourceType,+ cfgLocalModules =+ cfgLocalModules rawConfig+ <> maybe Set.empty (Set.fromList . CabalUtils.ciModules) mcabalInfo } where fixityOverrides = fromMaybe defaultFixityOverrides mfixityOverrides
src/Ormolu/Config.hs view
@@ -39,6 +39,15 @@ HaddockPrintStyle (..), HaddockPrintStyleModule (..), ImportExportStyle (..),+ ImportGrouping (..),+ ImportGroup (..),+ ImportGroupRule (..),+ ImportModuleMatcher (..),+ ImportRulePriority (..),+ matchAllRulePriority,+ matchLocalRulePriority,+ defaultImportRulePriority,+ QualifiedImportMatcher (..), LetStyle (..), InStyle (..), Unicode (..),@@ -65,10 +74,12 @@ import Data.Set (Set) import Data.Set qualified as Set import Data.String (fromString)+import Distribution.ModuleName (ModuleName) import Distribution.Types.PackageName (PackageName) import GHC.Generics (Generic) import GHC.Types.SrcLoc qualified as GHC import Ormolu.Config.Gen+import Ormolu.Config.Types import Ormolu.Fixity import Ormolu.Terminal (ColorMode (..)) import Ormolu.Utils.Fixity (parseFixityDeclarationStr, parseModuleReexportDeclarationStr)@@ -113,7 +124,8 @@ cfgColorMode :: !ColorMode, -- | Region selection cfgRegion :: !region,- cfgPrinterOpts :: !PrinterOptsTotal+ cfgPrinterOpts :: !PrinterOptsTotal,+ cfgLocalModules :: !(Set ModuleName) } deriving (Eq, Show, Functor, Generic) @@ -154,7 +166,8 @@ { regionStartLine = Nothing, regionEndLine = Nothing },- cfgPrinterOpts = defaultPrinterOpts+ cfgPrinterOpts = defaultPrinterOpts,+ cfgLocalModules = Set.empty } -- | Return all dependencies of the module. This includes both the declared
src/Ormolu/Config/Gen.hs view
@@ -19,6 +19,7 @@ , SingleConstraintParens (..) , ColumnLimit (..) , SingleDerivingParens (..)+ , ImportGrouping (..) , emptyPrinterOpts , defaultPrinterOpts , defaultPrinterOptsYaml@@ -32,9 +33,11 @@ import qualified Data.Aeson as Aeson import qualified Data.Aeson.Types as Aeson import Data.Functor.Identity (Identity)+import Data.List.NonEmpty (NonEmpty) import Data.Scientific (floatingOrInteger) import qualified Data.Text as Text import GHC.Generics (Generic)+import qualified Ormolu.Config.Types as CT import Text.Read (readEither, readMaybe) -- | Options controlling formatting output.@@ -50,6 +53,8 @@ poCommaStyle :: f CommaStyle , -- | Styling of import/export lists poImportExportStyle :: f ImportExportStyle+ , -- | Rules for grouping import declarations+ poImportGrouping :: f ImportGrouping , -- | Whether to full-indent or half-indent 'where' bindings past the preceding body poIndentWheres :: f Bool , -- | Whether to leave a space before an opening record brace@@ -68,6 +73,14 @@ poSingleConstraintParens :: f SingleConstraintParens , -- | Whether to put parentheses around a single deriving class poSingleDerivingParens :: f SingleDerivingParens+ , -- | Whether to sort constraints+ poSortConstraints :: f Bool+ , -- | Whether to sort derived classes+ poSortDerivedClasses :: f Bool+ , -- | Whether to sort deriving clauses+ poSortDerivingClauses :: f Bool+ , -- | Whether to place section operators (those that are infixr 0, such as $) in trailing position, continuing the expression indented below+ poTrailingSectionOperators :: f Bool , -- | Output Unicode syntax poUnicode :: f Unicode , -- | Give the programmer more choice on where to insert blank lines@@ -83,6 +96,7 @@ , poFunctionArrows = Nothing , poCommaStyle = Nothing , poImportExportStyle = Nothing+ , poImportGrouping = Nothing , poIndentWheres = Nothing , poRecordBraceSpace = Nothing , poNewlinesBetweenDecls = Nothing@@ -92,6 +106,10 @@ , poInStyle = Nothing , poSingleConstraintParens = Nothing , poSingleDerivingParens = Nothing+ , poSortConstraints = Nothing+ , poSortDerivedClasses = Nothing+ , poSortDerivingClauses = Nothing+ , poTrailingSectionOperators = Nothing , poUnicode = Nothing , poRespectful = Nothing }@@ -104,6 +122,7 @@ , poFunctionArrows = pure TrailingArrows , poCommaStyle = pure Leading , poImportExportStyle = pure ImportExportDiffFriendly+ , poImportGrouping = pure ImportGroupLegacy , poIndentWheres = pure False , poRecordBraceSpace = pure False , poNewlinesBetweenDecls = pure 1@@ -113,6 +132,10 @@ , poInStyle = pure InRightAlign , poSingleConstraintParens = pure ConstraintAlways , poSingleDerivingParens = pure DerivingAlways+ , poSortConstraints = pure False+ , poSortDerivedClasses = pure False+ , poSortDerivingClauses = pure False+ , poTrailingSectionOperators = pure True , poUnicode = pure UnicodeNever , poRespectful = pure True }@@ -132,6 +155,7 @@ , poFunctionArrows = maybe (poFunctionArrows p2) pure (poFunctionArrows p1) , poCommaStyle = maybe (poCommaStyle p2) pure (poCommaStyle p1) , poImportExportStyle = maybe (poImportExportStyle p2) pure (poImportExportStyle p1)+ , poImportGrouping = maybe (poImportGrouping p2) pure (poImportGrouping p1) , poIndentWheres = maybe (poIndentWheres p2) pure (poIndentWheres p1) , poRecordBraceSpace = maybe (poRecordBraceSpace p2) pure (poRecordBraceSpace p1) , poNewlinesBetweenDecls = maybe (poNewlinesBetweenDecls p2) pure (poNewlinesBetweenDecls p1)@@ -141,6 +165,10 @@ , poInStyle = maybe (poInStyle p2) pure (poInStyle p1) , poSingleConstraintParens = maybe (poSingleConstraintParens p2) pure (poSingleConstraintParens p1) , poSingleDerivingParens = maybe (poSingleDerivingParens p2) pure (poSingleDerivingParens p1)+ , poSortConstraints = maybe (poSortConstraints p2) pure (poSortConstraints p1)+ , poSortDerivedClasses = maybe (poSortDerivedClasses p2) pure (poSortDerivedClasses p1)+ , poSortDerivingClauses = maybe (poSortDerivingClauses p2) pure (poSortDerivingClauses p1)+ , poTrailingSectionOperators = maybe (poTrailingSectionOperators p2) pure (poTrailingSectionOperators p1) , poUnicode = maybe (poUnicode p2) pure (poUnicode p1) , poRespectful = maybe (poRespectful p2) pure (poRespectful p1) }@@ -172,6 +200,10 @@ "Styling of import/export lists (choices: \"leading\", \"trailing\", or \"diff-friendly\") (default: diff-friendly)" "OPTION" <*> f+ "import-grouping"+ "Rules for grouping import declarations (default: legacy)"+ "OPTION"+ <*> f "indent-wheres" "Whether to full-indent or half-indent 'where' bindings past the preceding body (default: false)" "BOOL"@@ -208,6 +240,22 @@ "Whether to put parentheses around a single deriving class (choices: \"auto\", \"always\", or \"never\") (default: always)" "OPTION" <*> f+ "sort-constraints"+ "Whether to sort constraints (default: false)"+ "BOOL"+ <*> f+ "sort-derived-classes"+ "Whether to sort derived classes (default: false)"+ "BOOL"+ <*> f+ "sort-deriving-clauses"+ "Whether to sort deriving clauses (default: false)"+ "BOOL"+ <*> f+ "trailing-section-operators"+ "Whether to place section operators (those that are infixr 0, such as $) in trailing position, continuing the expression indented below (default: true)"+ "BOOL"+ <*> f "unicode" "Output Unicode syntax (choices: \"detect\", \"always\", or \"never\") (default: never)" "OPTION"@@ -227,6 +275,7 @@ <*> f "function-arrows" <*> f "comma-style" <*> f "import-export-style"+ <*> f "import-grouping" <*> f "indent-wheres" <*> f "record-brace-space" <*> f "newlines-between-decls"@@ -236,6 +285,10 @@ <*> f "in-style" <*> f "single-constraint-parens" <*> f "single-deriving-parens"+ <*> f "sort-constraints"+ <*> f "sort-derived-classes"+ <*> f "sort-deriving-clauses"+ <*> f "trailing-section-operators" <*> f "unicode" <*> f "respectful" @@ -322,6 +375,16 @@ | DerivingNever deriving (Eq, Show, Enum, Bounded) +data ImportGrouping+ = ImportGroupLegacy+ | ImportGroupPreserve+ | ImportGroupSingle+ | ImportGroupByScope+ | ImportGroupByQualified+ | ImportGroupByScopeThenQualified+ | ImportGroupCustom (NonEmpty CT.ImportGroup)+ deriving (Eq, Show)+ instance Aeson.FromJSON CommaStyle where parseJSON = Aeson.withText "CommaStyle" $ \s ->@@ -525,6 +588,37 @@ , "Valid values are: \"auto\", \"always\", or \"never\"" ] +instance Aeson.FromJSON ImportGrouping where+ parseJSON =+ \case+ Aeson.String "legacy" -> pure ImportGroupLegacy+ Aeson.String "preserve" -> pure ImportGroupPreserve+ Aeson.String "single" -> pure ImportGroupSingle+ Aeson.String "by-qualified" -> pure ImportGroupByQualified+ Aeson.String "by-scope" -> pure ImportGroupByScope+ Aeson.String "by-scope-then-qualified" -> pure ImportGroupByScopeThenQualified+ arr@(Aeson.Array _) -> ImportGroupCustom <$> Aeson.parseJSON arr+ other ->+ fail . unlines $+ [ "unknown strategy value: " <> show other,+ "Valid values are: \"legacy\", \"preserve\", \"single\", \"by-qualified\", \"by-scope\", \"by-scope-then-qualified\" or a valid YAML configuration for import groups"+ ]++instance PrinterOptsFieldType ImportGrouping where+ parsePrinterOptType =+ \case+ "legacy" -> Right ImportGroupLegacy+ "preserve" -> Right ImportGroupPreserve+ "single" -> Right ImportGroupSingle+ "by-qualified" -> Right ImportGroupByQualified+ "by-scope" -> Right ImportGroupByScope+ "by-scope-then-qualified" -> Right ImportGroupByScopeThenQualified+ s ->+ Left . unlines $+ [ "unknown value: " <> show s+ , "Valid values are: \"legacy\", \"preserve\", \"single\", \"by-qualified\", \"by-scope\", \"by-scope-then-qualified\" or a valid YAML configuration for import groups (see fourmolu.yaml)"+ ]+ defaultPrinterOptsYaml :: String defaultPrinterOptsYaml = unlines@@ -543,6 +637,9 @@ , "# Styling of import/export lists (choices: leading, trailing, or diff-friendly)" , "import-export-style: diff-friendly" , ""+ , "# Rules for grouping import declarations"+ , "import-grouping: legacy"+ , "" , "# Whether to full-indent or half-indent 'where' bindings past the preceding body" , "indent-wheres: false" , ""@@ -570,6 +667,18 @@ , "# Whether to put parentheses around a single deriving class (choices: auto, always, or never)" , "single-deriving-parens: always" , ""+ , "# Whether to sort constraints"+ , "sort-constraints: false"+ , ""+ , "# Whether to sort derived classes"+ , "sort-derived-classes: false"+ , ""+ , "# Whether to sort deriving clauses"+ , "sort-deriving-clauses: false"+ , ""+ , "# Whether to place section operators (those that are infixr 0, such as $) in trailing position, continuing the expression indented below"+ , "trailing-section-operators: true"+ , "" , "# Output Unicode syntax (choices: detect, always, or never)" , "unicode: never" , ""@@ -581,4 +690,7 @@ , "" , "# Module reexports Fourmolu should know about" , "reexports: []"+ , ""+ , "# Modules defined by the current Cabal package for import grouping"+ , "local-modules: []" ]
+ src/Ormolu/Config/Types.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Ormolu.Config.Types+ ( ImportGroup (..),+ ImportGroupRule (..),+ ImportModuleMatcher (..),+ ImportRulePriority (..),+ matchAllRulePriority,+ matchLocalRulePriority,+ defaultImportRulePriority,+ QualifiedImportMatcher (..),+ )+where++import Control.Applicative (Alternative (..), asum)+import Data.Aeson ((.!=), (.:), (.:?))+import Data.Aeson qualified as Aeson+import Data.Aeson.Types qualified as Aeson+import Data.List.NonEmpty (NonEmpty)+import Data.Word (Word8)+import Ormolu.Utils.Glob (Glob, mkGlob)++data ImportGroup = ImportGroup+ { igName :: !(Maybe String),+ igRules :: !(NonEmpty ImportGroupRule)+ }+ deriving (Eq, Show)++instance Aeson.FromJSON ImportGroup where+ parseJSON = Aeson.withObject "ImportGroup" $ \o ->+ ImportGroup+ <$> Aeson.parseFieldMaybe o "name"+ <*> Aeson.parseField o "rules"++data ImportGroupRule = ImportGroupRule+ { igrModuleMatcher :: !ImportModuleMatcher,+ igrQualifiedMatcher :: !QualifiedImportMatcher,+ igrPriority :: !ImportRulePriority+ }+ deriving (Eq, Show)++instance Aeson.FromJSON ImportGroupRule where+ parseJSON = Aeson.withObject "rule" $ \o -> do+ let parseModuleMatcher = Aeson.parseJSON (Aeson.Object o)+ failUnknownModuleMatcher = Aeson.parseFail "Unknown or invalid module matcher"+ attemptParseModuleMatcher = parseModuleMatcher <|> failUnknownModuleMatcher+ igrModuleMatcher <- attemptParseModuleMatcher++ qualified <- o .:? "qualified"+ igrQualifiedMatcher <- case qualified of+ Just True -> pure MatchQualifiedOnly+ Just False -> pure MatchUnqualifiedOnly+ Nothing -> pure MatchBothQualifiedAndUnqualified++ let defaultPriority+ | MatchAllModules <- igrModuleMatcher = matchAllRulePriority+ | otherwise = defaultImportRulePriority+ igrPriority <- o .:? "priority" .!= defaultPriority++ pure ImportGroupRule {..}++newtype ImportRulePriority = ImportRulePriority Word8+ deriving (Eq, Ord, Show, Bounded)++instance Aeson.FromJSON ImportRulePriority where+ parseJSON = fmap ImportRulePriority . Aeson.parseJSON++matchAllRulePriority :: ImportRulePriority+matchAllRulePriority = ImportRulePriority 100++matchLocalRulePriority :: ImportRulePriority+matchLocalRulePriority = ImportRulePriority 60 -- Lower priority than "all" but higher than the default.++defaultImportRulePriority :: ImportRulePriority+defaultImportRulePriority = ImportRulePriority 50++data QualifiedImportMatcher+ = MatchQualifiedOnly+ | MatchUnqualifiedOnly+ | MatchBothQualifiedAndUnqualified+ deriving (Eq, Show)++data ImportModuleMatcher+ = MatchAllModules+ | MatchLocalModules+ | MatchGlob !Glob+ deriving (Eq, Show)++instance Aeson.FromJSON ImportModuleMatcher where+ parseJSON v =+ asum+ [ parseMatchModuleMatcher v,+ parseGlobModuleMatcher v+ ]+ where+ parseMatchModuleMatcher :: Aeson.Value -> Aeson.Parser ImportModuleMatcher+ parseMatchModuleMatcher = Aeson.withObject "ImportModuleMatcher" $ \o -> do+ c <- Aeson.parseField @String o "match"+ case c of+ "all" -> pure MatchAllModules+ "local-modules" -> pure MatchLocalModules+ other -> Aeson.parseFail $ "Unknown matcher: " <> other++ parseGlobModuleMatcher :: Aeson.Value -> Aeson.Parser ImportModuleMatcher+ parseGlobModuleMatcher = Aeson.withObject "ImportModuleMatcher" $ \o -> do+ rawGlob <- o .: "glob"+ let glob = mkGlob rawGlob+ pure (MatchGlob glob)
src/Ormolu/Diff/ParseResult.hs view
@@ -12,12 +12,16 @@ where import Data.ByteString (ByteString)+import Data.Char (isSpace) import Data.Foldable import Data.Function import Data.Generics+import Data.List (sortOn)+import Data.Text qualified as T import GHC.Hs import GHC.Types.SourceText import GHC.Types.SrcLoc+import Ormolu.Config.Gen (ImportGrouping (ImportGroupSingle)) import Ormolu.Imports (normalizeImports) import Ormolu.Parser.CommentStream import Ormolu.Parser.Result@@ -55,8 +59,8 @@ } = diffCommentStream cstream0 cstream1 <> diffHsModule- hs0 {hsmodImports = concat . normalizeImports False $ hsmodImports hs0}- hs1 {hsmodImports = concat . normalizeImports False $ hsmodImports hs1}+ hs0 {hsmodImports = concat . normalizeImports False mempty ImportGroupSingle $ hsmodImports hs0}+ hs1 {hsmodImports = concat . normalizeImports False mempty ImportGroupSingle $ hsmodImports hs1} diffCommentStream :: CommentStream -> CommentStream -> ParseResultDiff diffCommentStream (CommentStream cs) (CommentStream cs')@@ -98,7 +102,9 @@ `extQ` hsDocStringEq `extQ` importDeclQualifiedStyleEq `extQ` classDeclCtxEq- `extQ` derivedTyClsParensEq+ `extQ` derivedTyClsEq+ `extQ` qualTyCtxEq+ `extQ` dataDeclEq `extQ` considerEqual @EpAnnComments -- ~ XCGRHSs GhcPs `extQ` considerEqual @TokenLocation -- in LHs(Uni)Token `extQ` considerEqual @EpaLocation@@ -142,7 +148,7 @@ f x x' = x == x' hsDocStringEq :: HsDocString -> GenericQ ParseResultDiff- hsDocStringEq = considerEqualVia' ((==) `on` splitDocString True)+ hsDocStringEq = considerEqualVia' ((==) `on` (map (T.dropWhile isSpace) . splitDocString True)) forLocated :: (Data e0, Data e1) =>@@ -163,15 +169,53 @@ UnhelpfulSpan _ -> d appendSpan _ d = d + -- The order of contexts doesn't matter+ normalizeContext :: HsContext GhcPs -> HsContext GhcPs+ normalizeContext = sortOn showOutputable++ normalizeMContext :: Maybe (LHsContext GhcPs) -> Maybe (LHsContext GhcPs)+ normalizeMContext Nothing = Nothing+ normalizeMContext (Just (L _ [])) = Nothing+ normalizeMContext (Just (L ann ctx)) = Just (L ann $ normalizeContext ctx)++ qualTyCtxEq :: HsType GhcPs -> GenericQ ParseResultDiff+ qualTyCtxEq = considerEqualVia $ \lt rt -> genericQuery (normalizeQualTy lt) (normalizeQualTy rt)+ where+ normalizeQualTy :: HsType GhcPs -> HsType GhcPs+ normalizeQualTy (HsQualTy ann ctx body) = HsQualTy ann (fmap normalizeContext ctx) body+ normalizeQualTy ty = ty+ classDeclCtxEq :: TyClDecl GhcPs -> GenericQ ParseResultDiff- classDeclCtxEq ClassDecl {tcdCtxt = Just (L _ []), ..} tc' = genericQuery ClassDecl {tcdCtxt = Nothing, ..} tc'- classDeclCtxEq tc tc' = genericQuery tc tc'+ classDeclCtxEq = considerEqualVia $ \lc rc -> genericQuery (normalizeClassDecl lc) (normalizeClassDecl rc)+ where+ normalizeClassDecl ClassDecl {tcdCtxt, ..} = ClassDecl {tcdCtxt = normalizeMContext tcdCtxt, ..}+ normalizeClassDecl d = d - derivedTyClsParensEq :: DerivClauseTys GhcPs -> GenericQ ParseResultDiff- derivedTyClsParensEq = considerEqualVia $ curry $ \case- (DctSingle _ ty, DctMulti _ [ty']) -> genericQuery ty ty'- (DctMulti _ [ty], DctSingle _ ty') -> genericQuery ty ty'- (x, y) -> genericQuery x y+ dataDeclEq :: HsDataDefn GhcPs -> GenericQ ParseResultDiff+ dataDeclEq = considerEqualVia $ \dd dd' -> genericQuery (normalizeDataDecl dd) (normalizeDataDecl dd')++ normalizeDataDecl :: HsDataDefn GhcPs -> HsDataDefn GhcPs+ normalizeDataDecl HsDataDefn {dd_ctxt, dd_derivs, ..} =+ HsDataDefn+ { -- The order of classes in the context doesn't matter+ dd_ctxt = normalizeMContext dd_ctxt,+ -- The order of deriving clauses doesn't matter. Note: need to normalize before sorting, otherwise+ -- we'll get a different sort order!+ dd_derivs = sortOn showOutputable ((fmap . fmap) normalizeDerivingClause dd_derivs),+ ..+ }++ normalizeDerivingClause :: HsDerivingClause GhcPs -> HsDerivingClause GhcPs+ normalizeDerivingClause HsDerivingClause {deriv_clause_tys, ..} =+ HsDerivingClause {deriv_clause_tys = fmap normalizeDerivClauseTys deriv_clause_tys, ..}++ normalizeDerivClauseTys :: DerivClauseTys GhcPs -> DerivClauseTys GhcPs+ normalizeDerivClauseTys (DctSingle ann ty) = DctMulti ann [ty]+ -- The order of types in deriving clauses doesn't matter+ normalizeDerivClauseTys (DctMulti ann tys) = DctMulti ann (sortOn showOutputable tys)++ derivedTyClsEq :: DerivClauseTys GhcPs -> GenericQ ParseResultDiff+ derivedTyClsEq = considerEqualVia $ \lc rc -> genericQuery (normalizeDerivClauseTys lc) (normalizeDerivClauseTys rc) compareAnnKeywordId x y = let go = curry $ \case
src/Ormolu/Imports.hs view
@@ -13,11 +13,12 @@ import Data.Bifunctor import Data.Char (isAlphaNum)-import Data.Foldable (toList) import Data.Function (on) import Data.List (nubBy, sortBy, sortOn) import Data.Map.Strict (Map) import Data.Map.Strict qualified as M+import Data.Set (Set)+import Distribution.ModuleName qualified as Cabal import GHC.Data.FastString import GHC.Hs import GHC.Hs.ImpExp as GHC@@ -25,7 +26,9 @@ import GHC.Types.PkgQual import GHC.Types.SourceText import GHC.Types.SrcLoc-import Ormolu.Utils (groupBy', notImplemented, separatedByBlank, showOutputable)+import Ormolu.Config (ImportGrouping)+import Ormolu.Imports.Grouping (Import (..), groupImports, prepareExistingGroups)+import Ormolu.Utils (notImplemented, showOutputable) #if !MIN_VERSION_base(4,20,0) import Data.List (foldl') #endif@@ -35,18 +38,20 @@ -- Assumes input list is sorted by source location. Output list is not necessarily -- sorted by source location, so this function should be called at most once on a -- given input list.-normalizeImports :: Bool -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]-normalizeImports preserveGroups =- map- ( fmap snd- . M.toAscList- . M.fromListWith combineImports- . fmap (\x -> (importId x, g x))- )- . if preserveGroups- then map toList . groupBy' (\x y -> not $ separatedByBlank getLocA x y)- else pure+normalizeImports :: Bool -> Set Cabal.ModuleName -> ImportGrouping -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]+normalizeImports respectful localModules importGrouping =+ map (fmap snd)+ . concatMap+ ( groupImports importGrouping localModules toImport+ . M.toAscList+ . M.fromListWith combineImports+ . fmap (\x -> (importId x, g x))+ )+ . prepareExistingGroups importGrouping respectful where+ toImport :: (ImportId, x) -> Import+ toImport (ImportId {..}, _) = Import {importName = importIdName, importQualified}+ g :: LImportDecl GhcPs -> LImportDecl GhcPs g (L l ImportDecl {..}) = L
+ src/Ormolu/Imports/Grouping.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}++module Ormolu.Imports.Grouping+ ( Import (..),+ prepareExistingGroups,+ groupImports,+ )+where++import Data.Bifunctor (Bifunctor (..))+import Data.Foldable (toList)+import Data.Function (on)+import Data.List (groupBy, minimumBy, sortOn)+import Data.List.NonEmpty (NonEmpty)+import Data.List.NonEmpty qualified as NonEmpty+import Data.Set (Set)+import Data.Set qualified as Set+import Distribution.ModuleName qualified as Cabal+import GHC.Hs (GhcPs, getLocA)+import Language.Haskell.Syntax (LImportDecl, ModuleName, moduleNameString)+import Ormolu.Config (ImportGroup (..), ImportGroupRule (..), ImportGrouping (..), ImportModuleMatcher (..))+import Ormolu.Config qualified as Config+import Ormolu.Utils (ghcModuleNameToCabal, groupBy', separatedByBlank)+import Ormolu.Utils.Glob (matchesGlob)++newtype ImportGroups = ImportGroups (NonEmpty ImportGroup)++data Import = Import+ { importName :: ModuleName,+ importQualified :: Bool+ }++importGroupSingleStrategy :: ImportGroups+importGroupSingleStrategy =+ ImportGroups $+ pure+ ImportGroup+ { igName = Nothing,+ igRules = pure matchAllImportRule+ }++importGroupByQualifiedStrategy :: ImportGroups+importGroupByQualifiedStrategy =+ ImportGroups $+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules = pure $ withUnqualifiedOnly matchAllImportRule+ },+ ImportGroup+ { igName = Nothing,+ igRules = pure $ withQualifiedOnly matchAllImportRule+ }+ ]++importGroupByScopeStrategy :: ImportGroups+importGroupByScopeStrategy =+ ImportGroups $+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules = pure matchAllImportRule+ },+ ImportGroup+ { igName = Nothing,+ igRules = pure matchLocalModulesRule+ }+ ]++importGroupByScopeThenQualifiedStrategy :: ImportGroups+importGroupByScopeThenQualifiedStrategy =+ ImportGroups $+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules = pure $ withQualified matchModule+ }+ | matchModule <- [matchAllImportRule, matchLocalModulesRule],+ withQualified <- [withUnqualifiedOnly, withQualifiedOnly]+ ]++groupsFromConfig :: Config.ImportGrouping -> ImportGroups+groupsFromConfig =+ \case+ Config.ImportGroupLegacy -> importGroupSingleStrategy+ Config.ImportGroupPreserve -> importGroupSingleStrategy+ Config.ImportGroupSingle -> importGroupSingleStrategy+ Config.ImportGroupByQualified -> importGroupByQualifiedStrategy+ Config.ImportGroupByScope -> importGroupByScopeStrategy+ Config.ImportGroupByScopeThenQualified -> importGroupByScopeThenQualifiedStrategy+ Config.ImportGroupCustom igs -> ImportGroups igs++matchAllImportRule :: ImportGroupRule+matchAllImportRule =+ ImportGroupRule+ { igrModuleMatcher = MatchAllModules,+ igrQualifiedMatcher = Config.MatchBothQualifiedAndUnqualified,+ igrPriority = Config.matchAllRulePriority+ }++matchLocalModulesRule :: ImportGroupRule+matchLocalModulesRule =+ ImportGroupRule+ { igrModuleMatcher = MatchLocalModules,+ igrQualifiedMatcher = Config.MatchBothQualifiedAndUnqualified,+ igrPriority = Config.matchLocalRulePriority+ }++withQualifiedOnly :: ImportGroupRule -> ImportGroupRule+withQualifiedOnly ImportGroupRule {..} =+ ImportGroupRule+ { igrQualifiedMatcher = Config.MatchQualifiedOnly,+ ..+ }++withUnqualifiedOnly :: ImportGroupRule -> ImportGroupRule+withUnqualifiedOnly ImportGroupRule {..} =+ ImportGroupRule+ { igrQualifiedMatcher = Config.MatchUnqualifiedOnly,+ ..+ }++matchesRule :: Set Cabal.ModuleName -> Import -> ImportGroupRule -> Bool+matchesRule localMods Import {..} ImportGroupRule {..} = matchesModules && matchesQualified+ where+ matchesModules = case igrModuleMatcher of+ MatchAllModules -> True+ MatchLocalModules -> ghcModuleNameToCabal importName `Set.member` localMods+ MatchGlob gl -> moduleNameString importName `matchesGlob` gl+ matchesQualified = case igrQualifiedMatcher of+ Config.MatchQualifiedOnly -> importQualified+ Config.MatchUnqualifiedOnly -> not importQualified+ Config.MatchBothQualifiedAndUnqualified -> True++prepareExistingGroups :: ImportGrouping -> Bool -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]+prepareExistingGroups ig respectful =+ case ig of+ ImportGroupPreserve -> preserveGroups+ ImportGroupLegacy | respectful -> preserveGroups+ _ -> flattenGroups+ where+ preserveGroups = map toList . groupBy' (\x y -> not $ separatedByBlank getLocA x y)+ flattenGroups = pure++groupImports :: forall x. ImportGrouping -> Set Cabal.ModuleName -> (x -> Import) -> [x] -> [[x]]+groupImports ig localModules fToImport = regroup . fmap (breakTies . matchRules)+ where+ ImportGroups igs = groupsFromConfig ig++ indexedGroupRules :: [(Int, [ImportGroupRule])]+ indexedGroupRules = zip [0 ..] (toList . igRules <$> toList igs)++ matchRules :: x -> ([(Int, [ImportGroupRule])], x)+ matchRules x =+ let imp = fToImport x+ testRule (_, rules) = any (matchesRule localModules imp) rules+ in (filter testRule indexedGroupRules, x)++ breakTies :: ([(Int, [ImportGroupRule])], x) -> (Int, x)+ breakTies ([], x) =+ (maxBound, x) -- Any non-matched imports will be in the last group+ breakTies (matches, x) =+ (fst . minimumBy (compare `on` snd) $ second (minimum . fmap igrPriority) <$> matches, x)++ regroup :: [(Int, x)] -> [[x]]+ regroup = fmap (fmap snd) . groupBy ((==) `on` fst) . sortOn fst
src/Ormolu/Parser.hs view
@@ -168,7 +168,8 @@ prCommentStream = comments, prExtensions = GHC.extensionFlags dynFlags, prModuleFixityMap = modFixityMap,- prIndent = indent+ prIndent = indent,+ prLocalModules = cfgLocalModules } return r @@ -216,6 +217,7 @@ [x@(L _ (HsParTy _ inner))] | L _ HsForAllTy {} <- inner -> [x] | L _ HsQualTy {} <- inner -> [x]+ | L _ HsIParamTy {} <- inner -> [x] | ConstraintNever <- constraintParens -> [inner] | otherwise -> [x] [x@(L lx _)]
src/Ormolu/Parser/Result.hs view
@@ -5,9 +5,11 @@ ) where +import Data.Set (Set) import Data.Text (Text)+import Distribution.ModuleName (ModuleName) import GHC.Data.EnumSet (EnumSet)-import GHC.Hs+import GHC.Hs (GhcPs, HsModule) import GHC.LanguageExtensions.Type import Ormolu.Config (SourceType) import Ormolu.Fixity (ModuleFixityMap)@@ -34,5 +36,7 @@ -- | Fixity map for operators prModuleFixityMap :: ModuleFixityMap, -- | Indentation level, can be non-zero in case of region formatting- prIndent :: Int+ prIndent :: Int,+ -- | Local modules+ prLocalModules :: Set ModuleName }
src/Ormolu/Printer.hs view
@@ -40,6 +40,7 @@ (mkSpanStream prParsedSource) prCommentStream printerOpts+ prLocalModules prSourceType prExtensions prModuleFixityMap
src/Ormolu/Printer/Combinators.hs view
@@ -18,10 +18,12 @@ -- ** Basic txt,+ txtStripIndent, atom, space, newline, declNewline,+ multilineCommentNewline, inci, inciBy, inciIf,@@ -41,6 +43,7 @@ breakpoint, breakpoint', getPrinterOpt,+ getLocalModules, -- ** Formatting lists sep,
src/Ormolu/Printer/Comments.hs view
@@ -230,17 +230,17 @@ let startColumn = srcLocCol . realSrcSpanStart in startColumn espn > startColumn ref || ( abs (startColumn espn - startColumn l)- >= abs (startColumn ref - startColumn l)+ >= abs (startColumn ref - startColumn l) ) continuation = -- A comment is a continuation when it doesn't have non-whitespace -- lexemes in front of it and goes right after the previous comment. not (hasAtomsBefore comment) && ( case mlastMark of- Just (HaddockSpan _ _) -> False- Just (CommentSpan spn) ->- srcSpanEndLine spn + 1 == srcSpanStartLine l- _ -> False+ Just (HaddockSpan _ _) -> False+ Just (CommentSpan spn) ->+ srcSpanEndLine spn + 1 == srcSpanStartLine l+ _ -> False ) lastInEnclosing = case meSpn of
src/Ormolu/Printer/Internal.hs view
@@ -13,11 +13,13 @@ -- * Internal functions txt,+ txtStripIndent, interferingTxt, atom, space, newline, declNewline,+ multilineCommentNewline, askSourceType, askModuleFixityMap, askDebug,@@ -31,6 +33,7 @@ vlayout, getLayout, getPrinterOpt,+ getLocalModules, -- * Helpers for braces useBraces,@@ -65,6 +68,7 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Data.Bool (bool)+import Data.Char (isSpace) import Data.Choice (Choice) import Data.Choice qualified as Choice import Data.Coerce@@ -72,10 +76,12 @@ import Data.Functor.Identity (runIdentity) import Data.List (find) import Data.Maybe (listToMaybe)+import Data.Set (Set) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Builder+import Distribution.ModuleName (ModuleName) import GHC.Data.EnumSet (EnumSet) import GHC.Data.EnumSet qualified as EnumSet import GHC.LanguageExtensions.Type@@ -108,6 +114,7 @@ -- | Whether the last expression in the layout can use braces rcCanUseBraces :: Bool, rcPrinterOpts :: PrinterOptsTotal,+ rcLocalModules :: Set ModuleName, -- | Enabled extensions rcExtensions :: EnumSet Extension, -- | Whether the source is a signature or a regular module@@ -180,6 +187,7 @@ -- | Comment stream CommentStream -> PrinterOptsTotal ->+ Set ModuleName -> -- | Whether the source is a signature or a regular module SourceType -> -- | Enabled extensions@@ -189,7 +197,7 @@ -- | Resulting rendition Bool -> Text-runR (R m) sstream cstream printerOpts sourceType extensions moduleFixityMap debug =+runR (R m) sstream cstream printerOpts localModules sourceType extensions moduleFixityMap debug = TL.toStrict . toLazyText . scBuilder $ execState (runReaderT m rc) sc where rc =@@ -199,6 +207,7 @@ rcEnclosingSpans = [], rcCanUseBraces = False, rcPrinterOpts = printerOpts,+ rcLocalModules = localModules, rcExtensions = extensions, rcSourceType = sourceType, rcModuleFixityMap = moduleFixityMap,@@ -250,6 +259,15 @@ R () txt = spit SimpleText +-- | Same as 'txt', except strip leading whitespace equal to the current+-- indentation level. Useful for multiline comments, where the AST contains+-- the full leading whitespace.+txtStripIndent :: Text -> R ()+txtStripIndent s = do+ indent <- R (asks rcIndent)+ let (leadingSpaces, s') = T.span isSpace s+ txt $ T.drop indent leadingSpaces <> s'+ -- | Similar to 'txt' but the text inserted this way is assumed to break the -- “link” between the preceding atom and its pending comments. interferingTxt ::@@ -342,6 +360,23 @@ declNewline :: R () declNewline = newlineRawN =<< getPrinterOpt poNewlinesBetweenDecls +-- | Add a newline in a multiline comment.+--+-- Can't use 'newline' because it avoids adding multiple consecutive newlines.+-- Single-line comments don't have this issue because each line has at least "--".+multilineCommentNewline :: R ()+multilineCommentNewline =+ -- inlining part of rawNewline here because rawNewline still swallows newlines+ -- when called consecutively+ R . modify $ \sc ->+ sc+ { scBuilder = scBuilder sc <> "\n",+ scColumn = 0,+ scIndent = 0,+ scThisLineSpans = [],+ scRequestedDelimiter = AfterNewline+ }+ -- | Output a newline. First time 'newline' is used after some non-'newline' -- output it gets inserted immediately. Second use of 'newline' does not -- output anything but makes sure that the next non-white space output will@@ -422,7 +457,37 @@ where modRC rc = rc- { rcIndent = roundDownToNearest step (rcIndent rc) + step+ { rcIndent =+ -- Currently, in parenthesized expressions, we keep expressions aligned on+ -- the left, and increase indentation from that point. But this means that+ -- all subsequent indentation levels will be misaligned with the indentation,+ -- assuming the parentheses starts on the indentation column:+ --+ -- ( Maybe+ -- Int ->+ -- String+ -- )+ -- ^^^^ 'Int' is 4 spaces from indentation start+ -- ^^^^^^ but 6 spaces from start of column+ --+ -- To fix this, we mitigated the symptom by rounding down the indentation to+ -- the indentation first before incrementing.+ -- https://github.com/fourmolu/fourmolu/pull/37#discussion_r497904191+ --+ -- But this causes a regression with 2 space indentation, where a wrapped line+ -- isn't indented enough if the parentheses does NOT start on the indentation+ -- column.+ -- https://github.com/fourmolu/fourmolu/issues/428+ --+ -- Ideally, we'd rather indicate that the "current" indentation is shifted,+ -- while the "next" indentation should increase from the parentheses. However,+ -- that would probably be a lot of reworking the rcIndent machinery. Instead,+ -- we'll just restrict this patch to only apply on indentation > 2.+ let modifyIndent =+ if poIndentation (rcPrinterOpts rc) > 2+ then roundDownToNearest step+ else id+ in modifyIndent (rcIndent rc) + step } roundDownToNearest r n = (n `div` r) * r @@ -492,6 +557,9 @@ -- | Get a particular 'PrinterOpts' field from the environment. getPrinterOpt :: (forall f. PrinterOpts f -> f a) -> R a getPrinterOpt f = R $ asks $ runIdentity . f . rcPrinterOpts++getLocalModules :: R (Set ModuleName)+getLocalModules = R $ asks rcLocalModules ---------------------------------------------------------------------------- -- Special helpers for comment placement
src/Ormolu/Printer/Meat/Common.hs view
@@ -191,30 +191,11 @@ HaddockMultiLineCompact -> True let docStringLines = splitDocString shouldEscapeCommentBraces $ hsDocString str - mSrcSpan <- getSrcSpan l-- let useSingleLineComments =- or- [ poHStyle == HaddockSingleLine,- length docStringLines <= 1,- -- Use multiple single-line comments when the whole comment is indented- maybe False ((> 1) . srcSpanStartCol) mSrcSpan- ]-- let body sep' =- forM_ (zip docStringLines (True : repeat False)) $ \(x, isFirst) -> do- if isFirst- then do- -- prevent trailing space in multi-line comments- unless (not useSingleLineComments && T.null x) space- else do- sep'- unless (T.null x) (txt x)-- if useSingleLineComments+ if poHStyle == HaddockSingleLine || length docStringLines <= 1 then do txt $ "-- " <> haddockDelim- body $ newline >> txt "--" >> space+ space+ sep (newline >> txt "--" >> space) txt docStringLines else do txt . T.concat $ [ "{-",@@ -223,20 +204,13 @@ _ -> " ", haddockDelim ]- -- 'newline' doesn't allow multiple blank newlines, which changes the comment- -- if the user writes a comment with multiple newlines. So we have to do this- -- to force the printer to output a newline. The HaddockSingleLine branch- -- doesn't have this problem because each newline has at least "--".- --- -- 'newline' also takes indentation into account, but since multiline comments- -- are never used in an indented context (see useSingleLineComments), this is- -- safe- body $ txt "\n"+ space+ sep multilineCommentNewline txtStripIndent docStringLines newline txt "-}" when (Choice.isTrue needsNewline) newline- traverse_ (setSpanMark . HaddockSpan hstyle) mSrcSpan+ traverse_ (setSpanMark . HaddockSpan hstyle) =<< getSrcSpan l where haddockDelim = case hstyle of
src/Ormolu/Printer/Meat/Declaration/Data.hs view
@@ -16,6 +16,7 @@ import Control.Monad import Data.Choice (Choice, pattern Is, pattern Isn't, pattern With) import Data.Choice qualified as Choice+import Data.List (sortOn) import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE import Data.Maybe (isJust, isNothing, mapMaybe, maybeToList)@@ -133,8 +134,26 @@ else id sep s (sitcc' . located' (p_conDecl singleRecCon)) dd_cons' unless (null dd_derivs) breakpoint- inci $ sep newline (located' p_hsDerivingClause) dd_derivs + sortDerivingClauses <- getPrinterOpt poSortDerivingClauses+ let sortedDeriving = if sortDerivingClauses then sortOn (derivingStrategyKey . fmap unLoc . deriv_clause_strategy . unLoc) dd_derivs else dd_derivs+ inci $ sep newline (located' p_hsDerivingClause) sortedDeriving+ where+ derivingStrategyKey Nothing = ClauseNoStrategy+ derivingStrategyKey (Just strategy) = case strategy of+ StockStrategy _ -> ClauseStockStrategy+ NewtypeStrategy _ -> ClauseNewtypeStrategy+ AnyclassStrategy _ -> ClauseAnyclassStrategy+ ViaStrategy ty -> ClauseViaStrategy (showOutputable ty)++data DerivingClauseSortKey+ = ClauseNoStrategy+ | ClauseStockStrategy+ | ClauseNewtypeStrategy+ | ClauseAnyclassStrategy+ | ClauseViaStrategy String+ deriving (Eq, Ord)+ p_conDecl :: Choice "singleRecCon" -> ConDecl GhcPs -> R () p_conDecl _ ConDeclGADT {..} = do mapM_ (p_hsDoc Pipe (With #endNewline)) con_doc@@ -286,12 +305,14 @@ | [sigTy] <- sigTys, DerivingNever <- singleDerivingParens -> located sigTy p_hsSigType- | otherwise ->+ | otherwise -> do+ sortDerivedClasses <- getPrinterOpt poSortDerivedClasses+ let sort = if sortDerivedClasses then sortOn showOutputable else id parens N $ sep commaDel (sitcc . located' p_hsSigType)- sigTys+ (sort sigTys) space case deriv_clause_strategy of Nothing -> do
src/Ormolu/Printer/Meat/Declaration/OpTree.hs view
@@ -22,7 +22,7 @@ import GHC.Types.Name (occNameString) import GHC.Types.Name.Reader (RdrName, rdrNameOcc) import GHC.Types.SrcLoc-import Ormolu.Config (poIndentation)+import Ormolu.Config (poIndentation, poTrailingSectionOperators) import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common (p_rdrName) import Ormolu.Printer.Meat.Declaration.Value@@ -96,6 +96,8 @@ R () p_exprOpTree s (OpNode x) = located x (p_hsExpr' NotApplicand s) p_exprOpTree s t@(OpBranches exprs@(firstExpr :| otherExprs) ops) = do+ trailingSectionOperators <- getPrinterOpt poTrailingSectionOperators+ let placement = opBranchPlacement exprPlacement@@ -113,9 +115,11 @@ -- Whether we could place the operator in a trailing position, -- followed by a breakpoint before the RHS couldBeTrailing (prevExpr, opi) =- -- An operator with fixity InfixR 0, like seq, $, and $ variants,- -- is required- isHardSplitterOp (opiFixityApproximation opi)+ -- Enabled by config+ trailingSectionOperators+ -- An operator with fixity InfixR 0, like seq, $, and $ variants,+ -- is required+ && isHardSplitterOp (opiFixityApproximation opi) -- the LHS must be single-line && isOneLineSpan (opTreeLoc prevExpr) -- can only happen when a breakpoint would have been added anyway
src/Ormolu/Printer/Meat/Module.hs view
@@ -50,8 +50,10 @@ newline mapM_ (p_hsModuleHeader hsmod) hsmodName newline- preserveGroups <- getPrinterOpt poRespectful- forM_ (normalizeImports preserveGroups hsmodImports) $ \importGroup -> do+ respectful <- getPrinterOpt poRespectful+ localModules <- getLocalModules+ importGrouping <- getPrinterOpt poImportGrouping+ forM_ (normalizeImports respectful localModules importGrouping hsmodImports) $ \importGroup -> do forM_ importGroup (located' p_hsmodImport) newline declNewline
src/Ormolu/Printer/Meat/Type.hs view
@@ -26,6 +26,7 @@ import Control.Monad import Data.Choice (pattern With, pattern Without) import Data.Functor ((<&>))+import Data.List (sortOn) import GHC.Data.Strict qualified as Strict import GHC.Hs hiding (isPromoted) import GHC.Types.SourceText@@ -268,7 +269,10 @@ p_hsContext = \case [] -> txt "()" [x] -> located x p_hsType- xs -> parens N $ sep commaDel (sitcc . located' p_hsType) xs+ xs -> do+ shouldSort <- getPrinterOpt poSortConstraints+ let sort = if shouldSort then sortOn showOutputable else id+ parens N $ sep commaDel (sitcc . located' p_hsType) (sort xs) class IsTyVarBndrFlag flag where isInferred :: flag -> Bool
src/Ormolu/Utils/Cabal.hs view
@@ -20,6 +20,7 @@ import Data.Maybe (maybeToList) import Data.Set (Set) import Data.Set qualified as Set+import Distribution.ModuleName (ModuleName) import Distribution.ModuleName qualified as ModuleName import Distribution.PackageDescription import Distribution.PackageDescription.Parsec@@ -56,7 +57,9 @@ -- | Direct dependencies ciDependencies :: !(Set PackageName), -- | Absolute path to the cabal file- ciCabalFilePath :: !FilePath+ ciCabalFilePath :: !FilePath,+ -- | Local modules+ ciModules :: ![ModuleName] } deriving (Eq, Show) @@ -96,7 +99,9 @@ genericPackageDescription :: GenericPackageDescription, -- | Map from Haskell source file paths (without any extensions) to the -- corresponding 'DynOption's and dependencies.- extensionsAndDeps :: Map FilePath ([DynOption], [PackageName])+ extensionsAndDeps :: Map FilePath ([DynOption], [PackageName]),+ -- | Modules defined in the cabal file+ localModules :: [ModuleName] } deriving (Show) @@ -125,6 +130,7 @@ throwIO . OrmoluCabalFileParsingFailed cabalFile . snd let extensionsAndDeps = getExtensionAndDepsMap cabalFile genericPackageDescription+ localModules = getLocalModules genericPackageDescription pure CachedCabalFile {..} let (dynOpts, dependencies, mentioned) = case M.lookup (dropExtensions sourceFileAbs) extensionsAndDeps of@@ -137,12 +143,45 @@ { ciPackageName = pkgName (package pdesc), ciDynOpts = dynOpts, ciDependencies = Set.fromList dependencies,- ciCabalFilePath = cabalFile+ ciCabalFilePath = cabalFile,+ ciModules = localModules } ) where whenLeft :: (Applicative f) => Either e a -> (e -> f a) -> f a whenLeft eitha ma = either ma pure eitha++getLocalModules :: GenericPackageDescription -> [ModuleName]+getLocalModules = extractPackageModules+ where+ extractPackageModules :: GenericPackageDescription -> [ModuleName]+ extractPackageModules GenericPackageDescription {..} =+ mconcat+ [ maybe [] (extractLibraryModules . condTreeData) condLibrary,+ extractLibraryModules . condTreeData . snd =<< condSubLibraries,+ extractForeignLibraryModules . condTreeData . snd =<< condForeignLibs,+ extractExecutableModules . condTreeData . snd =<< condExecutables,+ extractTestSuiteModules . condTreeData . snd =<< condTestSuites,+ extractBenchmarkModules . condTreeData . snd =<< condBenchmarks+ ]++ extractLibraryModules :: Library -> [ModuleName]+ extractLibraryModules Library {..} = mconcat [exposedModules, extractBuildInfoModules libBuildInfo]++ extractForeignLibraryModules :: ForeignLib -> [ModuleName]+ extractForeignLibraryModules = extractBuildInfoModules . foreignLibBuildInfo++ extractExecutableModules :: Executable -> [ModuleName]+ extractExecutableModules = extractBuildInfoModules . buildInfo++ extractTestSuiteModules :: TestSuite -> [ModuleName]+ extractTestSuiteModules = extractBuildInfoModules . testBuildInfo++ extractBenchmarkModules :: Benchmark -> [ModuleName]+ extractBenchmarkModules = extractBuildInfoModules . benchmarkBuildInfo++ extractBuildInfoModules :: BuildInfo -> [ModuleName]+ extractBuildInfoModules BuildInfo {..} = mconcat [otherModules, virtualModules, autogenModules] -- | Get a map from Haskell source file paths (without any extensions) to -- the corresponding 'DynOption's and dependencies.
+ src/Ormolu/Utils/Glob.hs view
@@ -0,0 +1,50 @@+module Ormolu.Utils.Glob+ ( Glob,+ mkGlob,+ matchesGlob,+ )+where++import Data.List (elemIndex, stripPrefix)+import Data.Maybe (fromMaybe)++newtype Glob = Glob [GlobPart]+ deriving (Eq, Show)++data GlobPart+ = MatchExactly !String+ | SingleWildcard+ | DoubleWildcard+ deriving (Eq, Show)++mkGlob :: String -> Glob+mkGlob = Glob . parsePart+ where+ parsePart :: String -> [GlobPart]+ parsePart s = case s of+ [] ->+ []+ '*' : '*' : t ->+ DoubleWildcard : parsePart t+ '*' : t ->+ SingleWildcard : parsePart t+ t ->+ let (m, t') = break (== '*') t+ in MatchExactly m : parsePart t'++matchesGlob :: String -> Glob -> Bool+matchesGlob s (Glob ps) = s `matchesGlobParts` ps++matchesGlobParts :: String -> [GlobPart] -> Bool+matchesGlobParts s g = case g of+ [] ->+ null s+ MatchExactly p : g' ->+ case stripPrefix p s of+ Nothing -> False+ Just s' -> s' `matchesGlobParts` g'+ SingleWildcard : g' ->+ let l = fromMaybe (length s) (elemIndex '.' s)+ in or [s' `matchesGlobParts` g' | i <- [0 .. l], let s' = drop i s]+ DoubleWildcard : g' ->+ or [s' `matchesGlobParts` g' | i <- [0 .. length s], let s' = drop i s]
tests/Ormolu/Config/PrinterOptsSpec.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-}-{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} -- | Tests for Fourmolu configuration options. Similar to PrinterSpec.hs@@ -12,25 +11,22 @@ import Control.Exception (catch) import Control.Monad (forM_, when)-import Data.Algorithm.DiffContext (getContextDiff, prettyContextDiff)+import Data.Algorithm.DiffContext qualified as Diff import Data.Char (isSpace)+import Data.List.NonEmpty qualified as NonEmpty import Data.Maybe (isJust)+import Data.Set qualified as S import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO.Utf8 qualified as T.Utf8+import Distribution.ModuleName qualified as ModuleName+import Distribution.Types.PackageName (mkPackageName) import GHC.Stack (withFrozenCallStack)-import Ormolu- ( Config (..),- PrinterOpts (..),- PrinterOptsTotal,- defaultConfig,- defaultPrinterOpts,- detectSourceType,- ormolu,- )-import Ormolu.Config (ColumnLimit (..), HaddockPrintStyleModule (..))+import Ormolu (detectSourceType, ormolu)+import Ormolu.Config import Ormolu.Exception (OrmoluException, printOrmoluException)-import Ormolu.Terminal (ColorMode (..), runTerm)+import Ormolu.Terminal (runTerm)+import Ormolu.Utils.Glob (mkGlob) import Path ( File, Path,@@ -129,6 +125,80 @@ checkIdempotence = True }, TestGroup+ { label = "import-grouping",+ isMulti = False,+ testCases =+ [ ImportGroupPreserve,+ ImportGroupSingle,+ ImportGroupByQualified,+ ImportGroupByScope,+ ImportGroupByScopeThenQualified,+ ImportGroupCustom . NonEmpty.fromList $+ [ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "Data.Text"),+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = defaultImportRulePriority+ }+ ]+ },+ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchAllModules,+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = ImportRulePriority 100+ }+ ]+ },+ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "SomeInternal.**"),+ igrQualifiedMatcher = MatchQualifiedOnly,+ igrPriority = defaultImportRulePriority+ },+ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "Unknown.**"),+ igrQualifiedMatcher = MatchUnqualifiedOnly,+ igrPriority = defaultImportRulePriority+ }+ ]+ },+ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchLocalModules,+ igrQualifiedMatcher = MatchUnqualifiedOnly,+ igrPriority = defaultImportRulePriority+ },+ ImportGroupRule+ { igrModuleMatcher = MatchAllModules,+ igrQualifiedMatcher = MatchQualifiedOnly,+ igrPriority = defaultImportRulePriority+ }+ ]+ }+ ]+ ],+ updateConfig = \igs opts ->+ opts+ { poImportGrouping = pure igs+ },+ showTestCase = showStrategy,+ testCaseSuffix = \igs -> suffixWith [showStrategy igs],+ checkIdempotence = True+ },+ TestGroup { label = "record-brace-space", isMulti = False, testCases = allOptions,@@ -210,6 +280,42 @@ checkIdempotence = True }, TestGroup+ { label = "sort-constraints",+ isMulti = False,+ testCases = allOptions,+ updateConfig = \sortConstraints options -> options {poSortConstraints = pure sortConstraints},+ showTestCase = show,+ testCaseSuffix = suffix1,+ checkIdempotence = True+ },+ TestGroup+ { label = "sort-derived-classes",+ isMulti = False,+ testCases = allOptions,+ updateConfig = \sortDerivedClasses opts -> opts {poSortDerivedClasses = pure sortDerivedClasses},+ showTestCase = show,+ testCaseSuffix = suffix1,+ checkIdempotence = True+ },+ TestGroup+ { label = "sort-deriving-clauses",+ isMulti = False,+ testCases = allOptions,+ updateConfig = \sortDerivingClauses opts -> opts {poSortDerivingClauses = pure sortDerivingClauses},+ showTestCase = show,+ testCaseSuffix = suffix1,+ checkIdempotence = True+ },+ TestGroup+ { label = "trailing-section-operators",+ isMulti = False,+ testCases = allOptions,+ updateConfig = \trailingSectionOperators opts -> opts {poTrailingSectionOperators = pure trailingSectionOperators},+ showTestCase = show,+ testCaseSuffix = suffix1,+ checkIdempotence = True+ },+ TestGroup { label = "unicode-syntax", isMulti = False, testCases = allOptions,@@ -281,7 +387,15 @@ defaultConfig { cfgPrinterOpts = opts, cfgSourceType = detectSourceType inputPath,- cfgCheckIdempotence = checkIdempotence+ cfgCheckIdempotence = checkIdempotence,+ cfgDependencies = S.fromList [mkPackageName "base"],+ cfgLocalModules =+ S.fromList $+ ModuleName.fromString+ <$> [ "SomeInternal.Module1",+ "SomeInternal.Module1.SubModuleA",+ "SomeInternal.Module2"+ ] } checkResult :: Path Rel File -> Text -> Expectation@@ -332,8 +446,8 @@ getDiff :: (String, Text) -> (String, Text) -> Text getDiff (s1Name, s1) (s2Name, s2) = T.pack . Doc.render $- prettyContextDiff (Doc.text s1Name) (Doc.text s2Name) (Doc.text . T.unpack) $- getContextDiff 2 (T.lines s1) (T.lines s2)+ Diff.prettyContextDiff (Doc.text s1Name) (Doc.text s2Name) (Doc.text . T.unpack . Diff.unnumber) $+ Diff.getContextDiff (Just 2) (T.lines s1) (T.lines s2) renderOrmoluException :: OrmoluException -> IO String renderOrmoluException e =@@ -360,3 +474,8 @@ spanEnd f xs = let xs' = reverse xs in (reverse $ dropWhile f xs', reverse $ takeWhile f xs')++showStrategy :: ImportGrouping -> String+showStrategy igs = case igs of+ ImportGroupCustom _ -> "custom"+ _ -> show igs
tests/Ormolu/ConfigSpec.hs view
@@ -4,11 +4,13 @@ module Ormolu.ConfigSpec (spec) where import Data.ByteString.Char8 qualified as Char8+import Data.List (isInfixOf) import Data.List.NonEmpty qualified as NonEmpty import Data.Map qualified as Map import Data.Yaml qualified as Yaml-import Ormolu.Config (FourmoluConfig (..), poIndentation, resolvePrinterOpts)+import Ormolu.Config (FourmoluConfig (..), ImportGroup (..), ImportGroupRule (..), ImportGrouping (..), ImportModuleMatcher (..), ImportRulePriority (ImportRulePriority), PrinterOpts (..), QualifiedImportMatcher (MatchBothQualifiedAndUnqualified, MatchQualifiedOnly, MatchUnqualifiedOnly), defaultImportRulePriority, matchAllRulePriority, resolvePrinterOpts) import Ormolu.Fixity (ModuleReexports (..))+import Ormolu.Utils.Glob (mkGlob) import Test.Hspec spec :: Spec@@ -31,3 +33,308 @@ opts2 = mempty {poIndentation = Just 4} configs = [opts1, opts2] poIndentation (resolvePrinterOpts configs) `shouldBe` 4++ context "when using an import grouping configuration" $ do+ it "parses 'legacy' as the 'ImportGroupLegacy' import grouping strategy" $ do+ config <- Yaml.decodeThrow "import-grouping: legacy"+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ actualStrategy `shouldBe` Just ImportGroupLegacy+ it "parses 'preserve' as the 'ImportGroupPreserve' import grouping strategy" $ do+ config <- Yaml.decodeThrow "import-grouping: preserve"+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ actualStrategy `shouldBe` Just ImportGroupPreserve+ it "parses 'single' as the 'ImportGroupSingle' import grouping strategy" $ do+ config <- Yaml.decodeThrow "import-grouping: single"+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ actualStrategy `shouldBe` Just ImportGroupSingle+ it "parses 'by-qualified' as the 'ImportGroupByQualified' import grouping strategy" $ do+ config <- Yaml.decodeThrow "import-grouping: by-qualified"+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ actualStrategy `shouldBe` Just ImportGroupByQualified+ it "parses 'by-scope' as the 'ImportGroupByScope' import grouping strategy" $ do+ config <- Yaml.decodeThrow "import-grouping: by-scope"+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ actualStrategy `shouldBe` Just ImportGroupByScope+ it "parses 'by-scope-then-qualified' as the 'ImportGroupByScopeThenQualified' import grouping strategy" $ do+ config <- Yaml.decodeThrow "import-grouping: by-scope-then-qualified"+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ actualStrategy `shouldBe` Just ImportGroupByScopeThenQualified+ it "fails when an unknown strategy is requested" $ do+ let decodeResult = Yaml.decodeEither' @FourmoluConfig "import-grouping: fake-strategy"+ isAnUnknownStrategyValue e = case e of+ Left (Yaml.AesonException msg) -> "unknown strategy value" `isInfixOf` msg+ _ -> False+ decodeResult `shouldSatisfy` isAnUnknownStrategyValue+ it "parses a grouping rule's name" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - name: Some name",+ " rules:",+ " - match: all"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Just "Some name",+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchAllModules,+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = matchAllRulePriority+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "enables the 'qualified' rule option" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - rules:",+ " - glob: \"**\"",+ " qualified: yes"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "**"),+ igrQualifiedMatcher = MatchQualifiedOnly,+ igrPriority = defaultImportRulePriority+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "disables the 'qualified' rule option" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - rules:",+ " - glob: \"**\"",+ " qualified: no"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "**"),+ igrQualifiedMatcher = MatchUnqualifiedOnly,+ igrPriority = defaultImportRulePriority+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "decodes the 'priority' rule option" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - rules:",+ " - match: all",+ " priority: 55"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchAllModules,+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = ImportRulePriority 55+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "parses a 'glob' rule" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - rules:",+ " - glob: Data.Text.**"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "Data.Text.**"),+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = defaultImportRulePriority+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "parses a 'match' rule for local modules" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - rules:",+ " - match: local-modules"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchLocalModules,+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = defaultImportRulePriority+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "parses a 'match' rule for all modules" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - rules:",+ " - match: all"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Nothing,+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchAllModules,+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = matchAllRulePriority+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "parses multiple group configurations and rules in their order of appearance in the configuration" $ do+ config <-+ Yaml.decodeThrow . Char8.pack . unlines $+ [ "import-grouping:",+ " - name: Text modules",+ " rules:",+ " - glob: Data.Text",+ " - name: The rest",+ " rules:",+ " - match: all",+ " priority: 100",+ " - name: My internals and monads unqualified",+ " rules:",+ " - match: local-modules",+ " qualified: no",+ " - glob: Control.Monad",+ " qualified: no",+ " - name: My internals and monads qualified",+ " rules:",+ " - match: local-modules",+ " qualified: yes",+ " - glob: Control.Monad",+ " qualified: yes",+ " - name: Specific monads",+ " rules:",+ " - glob: Control.Monad.**"+ ]+ let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+ expectedRules =+ NonEmpty.fromList+ [ ImportGroup+ { igName = Just "Text modules",+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "Data.Text"),+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = defaultImportRulePriority+ }+ ]+ },+ ImportGroup+ { igName = Just "The rest",+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchAllModules,+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = ImportRulePriority 100+ }+ ]+ },+ ImportGroup+ { igName = Just "My internals and monads unqualified",+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchLocalModules,+ igrQualifiedMatcher = MatchUnqualifiedOnly,+ igrPriority = defaultImportRulePriority+ },+ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "Control.Monad"),+ igrQualifiedMatcher = MatchUnqualifiedOnly,+ igrPriority = defaultImportRulePriority+ }+ ]+ },+ ImportGroup+ { igName = Just "My internals and monads qualified",+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchLocalModules,+ igrQualifiedMatcher = MatchQualifiedOnly,+ igrPriority = defaultImportRulePriority+ },+ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "Control.Monad"),+ igrQualifiedMatcher = MatchQualifiedOnly,+ igrPriority = defaultImportRulePriority+ }+ ]+ },+ ImportGroup+ { igName = Just "Specific monads",+ igRules =+ NonEmpty.fromList+ [ ImportGroupRule+ { igrModuleMatcher = MatchGlob (mkGlob "Control.Monad.**"),+ igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+ igrPriority = defaultImportRulePriority+ }+ ]+ }+ ]+ actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)+ it "fails when a rule cannot be identified" $ do+ let decodeResult =+ Yaml.decodeEither' @FourmoluConfig . Char8.pack . unlines $+ [ "import-grouping:",+ " - rules:",+ " - some-unknown-rule-type: whatever"+ ]+ isAnUnknownModuleMatcher e = case e of+ Left (Yaml.AesonException msg) -> "Unknown or invalid module matcher" `isInfixOf` msg+ _ -> False+ decodeResult `shouldSatisfy` isAnUnknownModuleMatcher
+ tests/Ormolu/Utils/GlobSpec.hs view
@@ -0,0 +1,50 @@+module Ormolu.Utils.GlobSpec (spec) where++import Ormolu.Utils.Glob (matchesGlob, mkGlob)+import Test.Hspec (Spec, describe, it, shouldBe)+import Test.Hspec.QuickCheck (prop)+import Test.QuickCheck (Arbitrary (..), listOf, suchThat)++newtype Wildcardless = Wildcardless String+ deriving (Show)++instance Arbitrary Wildcardless where+ arbitrary = fmap Wildcardless $ listOf (arbitrary `suchThat` (/= '*'))++newtype ModuleName = ModuleName String+ deriving (Show)++instance Arbitrary ModuleName where+ arbitrary = fmap ModuleName $ listOf (arbitrary `suchThat` (/= '.')) `suchThat` (not . null)++spec :: Spec+spec =+ describe "glob" $ do+ it "should match modules exactly" $+ "Hello.world" `matchesGlob` mkGlob "Hello.world" `shouldBe` True+ it "should fail if case doesn't match" $+ "Some.thing" `matchesGlob` mkGlob "Some.Thing" `shouldBe` False+ it "should allow * to match anything on the same level" $+ "Level1.Level2.Level3" `matchesGlob` mkGlob "Level1.Le*2.Level3" `shouldBe` True+ it "should fail if input doesn't match glob even with wildcards" $+ "Control.Monad.Writer.Lazy" `matchesGlob` mkGlob "Data.Functor.*.Lazy" `shouldBe` False+ it "should disallow * to match anything on different level" $+ "Level1.Level2.Level3.Level4" `matchesGlob` mkGlob "Level1.*.Level4" `shouldBe` False+ it "should allow multiple *" $+ "Some.Fake.Path.With.Internals" `matchesGlob` mkGlob "Some.*.Path.*.Internals" `shouldBe` True+ it "should allow * at the beginning" $+ "MyApp42.Control.Monad" `matchesGlob` mkGlob "*42.Control.Monad" `shouldBe` True+ it "should allow * at the end" $+ "System.IO.Stuff" `matchesGlob` mkGlob "System.IO.*" `shouldBe` True+ it "should allow ** to match anything on the same level" $+ "Level1.Level2.Level3" `matchesGlob` mkGlob "Level1.L**2.Level3" `shouldBe` True+ it "should allow ** to match anything on multiple levels" $+ "Level1.Level2.Level3.Level4.Level5" `matchesGlob` mkGlob "Level1.L**4.Level5" `shouldBe` True++ describe "Properties" $ do+ prop "should obey: s `matches` glob s with s being any string without '*'" $+ \(Wildcardless s) -> s `matchesGlob` mkGlob s+ prop "should obey: (a <> b <> c) `matches` glob (a <> * <> c) with b being a module name" $+ \a (ModuleName b) c -> (a <> b <> c) `matchesGlob` mkGlob (a <> "*" <> c)+ prop "should obey: (a <> b <> c) `matches` glob (a <> ** <> c)" $+ \a b c -> (a <> b <> c) `matchesGlob` mkGlob (a <> "**" <> c)