From 23aaaa17b546251c285bd7d12dbcdc6aef17f073 Mon Sep 17 00:00:00 2001 From: Jim McStanton Date: Thu, 29 Oct 2020 19:23:42 -0500 Subject: [PATCH] Add support for post qualified import formatting. Adds an option to use post qualified module imports. Related to #284 Resolves #334 --- data/stylish-haskell.yaml | 24 +++-- lib/Language/Haskell/Stylish/Config.hs | 1 + lib/Language/Haskell/Stylish/Step/Imports.hs | 28 ++++-- .../Haskell/Stylish/Step/Imports/Tests.hs | 91 ++++++++++++++----- 4 files changed, 102 insertions(+), 42 deletions(-) diff --git a/data/stylish-haskell.yaml b/data/stylish-haskell.yaml index a6bd26be..a0339ef1 100644 --- a/data/stylish-haskell.yaml +++ b/data/stylish-haskell.yaml @@ -261,21 +261,25 @@ steps: # Default: false space_surround: false - # Enabling this argument will use the new GHC lib parse to format imports. + # Post qualify option moves any qualifies found in import declarations + # to the end of the declaration. This also adjust padding for any + # unqualified import declarations. # - # This currently assumes a few things, it will assume that you want post - # qualified imports. It is also not as feature complete as the old - # imports formatting. + # - true: Qualified as is moved to the end of the + # declaration. # - # It does not remove redundant lines or merge lines. As such, the full - # feature scope is still pending. + # > import Data.Bar + # > import Data.Foo qualified as F # - # It _is_ however, a fine alternative if you are using features that are - # not parseable by haskell src extensions and you're comfortable with the - # presets. + # - false: Qualified remains in the default location and unqualified + # imports are padded to align with qualified imports. + # + # > import Data.Bar + # > import qualified Data.Foo as F # # Default: false - ghc_lib_parser: false + post_qualify: false + # Language pragmas - language_pragmas: diff --git a/lib/Language/Haskell/Stylish/Config.hs b/lib/Language/Haskell/Stylish/Config.hs index 42daeeda..3cf6a1c5 100644 --- a/lib/Language/Haskell/Stylish/Config.hs +++ b/lib/Language/Haskell/Stylish/Config.hs @@ -274,6 +274,7 @@ parseImports config o = fmap (Imports.step columns) $ Imports.Options <*> (o A..:? "list_padding" >>= maybe (pure $ def Imports.listPadding) parseListPadding) <*> o A..:? "separate_lists" A..!= def Imports.separateLists <*> o A..:? "space_surround" A..!= def Imports.spaceSurround + <*> o A..:? "post_qualify" A..!= def Imports.postQualified where def f = f Imports.defaultOptions diff --git a/lib/Language/Haskell/Stylish/Step/Imports.hs b/lib/Language/Haskell/Stylish/Step/Imports.hs index b89d73fe..d9e3d7e6 100644 --- a/lib/Language/Haskell/Stylish/Step/Imports.hs +++ b/lib/Language/Haskell/Stylish/Step/Imports.hs @@ -61,6 +61,7 @@ data Options = Options , listPadding :: ListPadding , separateLists :: Bool , spaceSurround :: Bool + , postQualified :: Bool } deriving (Eq, Show) defaultOptions :: Options @@ -73,6 +74,7 @@ defaultOptions = Options , listPadding = LPConstant 4 , separateLists = True , spaceSurround = False + , postQualified = False } data ListPadding @@ -143,8 +145,8 @@ formatImports -> NonEmpty (Located Import) -> Lines formatImports maxCols options m moduleStats rawGroup = runPrinter_ (PrinterConfig maxCols) [] m do - let - + let + group = NonEmpty.sortWith unLocated rawGroup & mergeImports @@ -177,10 +179,10 @@ printQualified Options{..} padNames stats (L _ decl) = do when (isSafe decl) (putText "safe" >> space) - case (isQualified decl, isAnyQualified stats) of - (True, _) -> putText "qualified" >> space - (_, True) -> putText " " >> space - _ -> pure () + case (postQualified, isQualified decl, isAnyQualified stats) of + (False, True, _) -> putText "qualified" >> space + (False, _, True) -> putText " " >> space + _ -> pure () moduleNamePosition <- length <$> getCurrentLine forM_ (ideclPkgQual decl') $ \pkg -> putText (stringLiteral pkg) >> space @@ -194,8 +196,11 @@ printQualified Options{..} padNames stats (L _ decl) = do replicate (isLongestImport stats - importModuleNameLength decl) ' ' beforeAliasPosition <- length <$> getCurrentLine - forM_ (ideclAs decl') \(L _ name) -> - space >> putText "as" >> space >> putText (moduleNameString name) + if not postQualified then + forM_ (ideclAs decl') \(L _ name) -> + space >> putText "as" >> space >> putText (moduleNameString name) + else + pure () afterAliasPosition <- length <$> getCurrentLine when (isHiding decl) (space >> putText "hiding") @@ -301,6 +306,13 @@ printQualified Options{..} padNames stats (L _ decl) = do modifyCurrentLine trimRight newline >> putOffset >> printAsSingleLine) printAsMultiLine) + if postQualified && isQualified decl + then do + space + putText "qualified" + forM_ (ideclAs decl') \(L _ name) -> + space >> putText "as" >> space >> putText (moduleNameString name) + else pure () where -- We cannot wrap/repeat 'hiding' imports since then we would get multiple -- imports hiding different things. diff --git a/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs b/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs index 6889db4c..26cae6e2 100644 --- a/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs +++ b/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs @@ -62,6 +62,9 @@ tests = testGroup "Language.Haskell.Stylish.Step.Imports.Tests" , testCase "case 28" case28 , testCase "case 29" case29 , testCase "case 30" case30 + , testCase "case 31" case31 + , testCase "case 32" case32 + , testCase "case 33" case33 ] @@ -190,7 +193,7 @@ case07 = assertSnippet (step (Just 80) $ fromImportAlign File) case08 :: Assertion case08 = let - options = Options Global WithAlias True Inline Inherit (LPConstant 4) True False + options = Options Global WithAlias True Inline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) input [ "module Herp where" @@ -214,7 +217,7 @@ case08 = case08b :: Assertion case08b = let - options = Options Global WithModuleName True Inline Inherit (LPConstant 4) True False + options = Options Global WithModuleName True Inline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) input ["module Herp where" @@ -237,7 +240,7 @@ case08b = case09 :: Assertion case09 = let - options = Options Global WithAlias True Multiline Inherit (LPConstant 4) True False + options = Options Global WithAlias True Multiline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) input [ "module Herp where" @@ -272,7 +275,7 @@ case09 = case10 :: Assertion case10 = let - options = Options Group WithAlias True Multiline Inherit (LPConstant 4) True False + options = Options Group WithAlias True Multiline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 40) options) input [ "module Herp where" @@ -313,7 +316,7 @@ case10 = case11 :: Assertion case11 = let - options = Options Group NewLine True Inline Inherit (LPConstant 4) True False + options = Options Group NewLine True Inline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) input [ "module Herp where" @@ -340,7 +343,7 @@ case11 = case11b :: Assertion case11b = let - options = Options Group WithModuleName True Inline Inherit (LPConstant 4) True False + options = Options Group WithModuleName True Inline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) input [ "module Herp where" @@ -363,7 +366,7 @@ case11b = case12 :: Assertion case12 = let - options = Options Group NewLine True Inline Inherit (LPConstant 2) True False + options = Options Group NewLine True Inline Inherit (LPConstant 2) True False False in assertSnippet (step (Just 80) options) [ "import Data.List (map)" @@ -377,7 +380,7 @@ case12 = case12b :: Assertion case12b = let - options = Options Group WithModuleName True Inline Inherit (LPConstant 2) True False + options = Options Group WithModuleName True Inline Inherit (LPConstant 2) True False False in assertSnippet (step (Just 80) options) ["import Data.List (map)"] @@ -388,7 +391,7 @@ case12b = case13 :: Assertion case13 = let - options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 4) True False + options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) [ "import qualified Data.List as List (concat, foldl, foldr, head, init," @@ -402,7 +405,7 @@ case13 = case13b :: Assertion case13b = let - options = Options None WithModuleName True InlineWithBreak Inherit (LPConstant 4) True False + options = Options None WithModuleName True InlineWithBreak Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) [ "import qualified Data.List as List (concat, foldl, foldr, head, init," @@ -418,7 +421,7 @@ case13b = case14 :: Assertion case14 = let - options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 10) True False + options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 10) True False False in assertSnippet (step (Just 80) options) [ "import qualified Data.List as List (concat, map, null, reverse, tail, (++))" @@ -431,7 +434,7 @@ case14 = case15 :: Assertion case15 = let - options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False + options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) [ "import Data.Acid (AcidState)" @@ -456,7 +459,7 @@ case15 = case16 :: Assertion case16 = let - options = Options None AfterAlias True Multiline Inherit (LPConstant 4) False False + options = Options None AfterAlias True Multiline Inherit (LPConstant 4) False False False in assertSnippet (step (Just 80) options) [ "import Data.Acid (AcidState)" @@ -479,7 +482,7 @@ case16 = case17 :: Assertion case17 = let - options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False + options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 80) options) [ "import Control.Applicative (Applicative ((<*>),pure))" @@ -496,7 +499,7 @@ case17 = case18 :: Assertion case18 = let - options = Options None AfterAlias True InlineToMultiline Inherit (LPConstant 4) True False + options = Options None AfterAlias True InlineToMultiline Inherit (LPConstant 4) True False False in assertSnippet (step (Just 40) options) [ "import Data.Foo as Foo (Bar, Baz, Foo)" @@ -523,7 +526,7 @@ case18 = case19 :: Assertion case19 = let - options = Options Global NewLine True InlineWithBreak RightAfter (LPConstant 17) True False + options = Options Global NewLine True InlineWithBreak RightAfter (LPConstant 17) True False False in assertSnippet (step (Just 40) options) case19input ---------------------------------------- @@ -539,7 +542,7 @@ case19 = case19b :: Assertion case19b = let - options = Options File NewLine True InlineWithBreak RightAfter (LPConstant 17) True False + options = Options File NewLine True InlineWithBreak RightAfter (LPConstant 17) True False False in assertSnippet (step (Just 40) options) case19input ---------------------------------------- @@ -554,7 +557,7 @@ case19b = case19c :: Assertion case19c = let - options = Options File NewLine True InlineWithBreak RightAfter LPModuleName True False + options = Options File NewLine True InlineWithBreak RightAfter LPModuleName True False False in assertSnippet (step (Just 40) options) case19input ---------------------------------------- @@ -569,7 +572,7 @@ case19c = case19d :: Assertion case19d = let - options = Options Global NewLine True InlineWithBreak RightAfter LPModuleName True False + options = Options Global NewLine True InlineWithBreak RightAfter LPModuleName True False False in assertSnippet (step (Just 40) options) case19input ---------------------------------------- @@ -665,7 +668,7 @@ case22 = assertSnippet (step (Just 80) defaultOptions) case23 :: Assertion case23 = let - options = Options None AfterAlias False Inline Inherit (LPConstant 4) True True + options = Options None AfterAlias False Inline Inherit (LPConstant 4) True True False in assertSnippet (step (Just 40) options) [ "import Data.Acid (AcidState)" @@ -690,7 +693,7 @@ case23 = case23b :: Assertion case23b = let - options = Options None WithModuleName False Inline Inherit (LPConstant 4) True True + options = Options None WithModuleName False Inline Inherit (LPConstant 4) True True False in assertSnippet (step (Just 40) options) [ "import Data.Acid (AcidState)" @@ -716,7 +719,7 @@ case23b = case24 :: Assertion case24 = let - options = Options None AfterAlias False InlineWithBreak Inherit (LPConstant 4) True True + options = Options None AfterAlias False InlineWithBreak Inherit (LPConstant 4) True True False in assertSnippet (step (Just 40) options) [ "import Data.Acid (AcidState)" @@ -740,7 +743,7 @@ case24 = case25 :: Assertion case25 = let - options = Options Group AfterAlias False Multiline Inherit (LPConstant 4) False False + options = Options Group AfterAlias False Multiline Inherit (LPConstant 4) False False False in assertSnippet (step (Just 80) options) [ "import Data.Acid (AcidState)" @@ -807,7 +810,7 @@ case28 = assertSnippet (step (Just 80) $ fromImportAlign Global) , "import Data.Set (empty, nub)" ] [ "import Control.Monad" - , "import qualified Data.Aeson as JSON" + , "import qualified Data.Aeson as JSON" , "import Data.Default.Class (Default (def))" , "" , "import Data.Maybe (Maybe (Just, Nothing))" @@ -842,3 +845,43 @@ case30 :: Assertion case30 = assertSnippet (step Nothing defaultOptions {separateLists = False}) ["import Data.Monoid (Monoid (..))"] ["import Data.Monoid (Monoid(..))"] + +-------------------------------------------------------------------------------- +case31 :: Assertion +case31 = assertSnippet (step Nothing defaultOptions {postQualified = True}) + ["import Data.Monoid (Monoid (..))"] + ["import Data.Monoid (Monoid (..))"] + +-------------------------------------------------------------------------------- +case32 :: Assertion +case32 = assertSnippet (step Nothing defaultOptions {postQualified = True}) + ["import qualified Data.Monoid as M"] + ["import Data.Monoid qualified as M"] + +-------------------------------------------------------------------------------- +case33 :: Assertion +case33 = assertSnippet (step Nothing defaultOptions {postQualified = True}) + [ "import Data.Default.Class (Default(def))" + , "import qualified Data.Aeson as JSON" + , "import qualified Data.Aeson as JSON" + , "import Control.Monad" + , "import Control.Monad" + , "" + , "import Data.Maybe (Maybe (Just, Nothing))" + , "import qualified Data.Maybe.Extra (Maybe(Just, Nothing))" + , "" + , "import Data.Foo (Foo (Foo,Bar), Goo(Goo))" + , "import Data.Foo (Foo (Foo,Bar))" + , "import Data.Set (empty, intersect)" + , "import Data.Set (empty, nub)" + ] + [ "import Control.Monad" + , "import Data.Aeson qualified as JSON" + , "import Data.Default.Class (Default (def))" + , "" + , "import Data.Maybe (Maybe (Just, Nothing))" + , "import Data.Maybe.Extra (Maybe (Just, Nothing)) qualified" + , "" + , "import Data.Foo (Foo (Bar, Foo), Goo (Goo))" + , "import Data.Set (empty, intersect, nub)" + ]