packages feed

stylish-haskell 0.14.2.0 → 0.14.3.0

raw patch · 10 files changed

+741/−60 lines, 10 filesdep +regex-tdfadep ~Cabal

Dependencies added: regex-tdfa

Dependency ranges changed: Cabal

Files

CHANGELOG view
@@ -1,5 +1,10 @@ # CHANGELOG +- 0.14.3.0 (2022-09-28)+     * Fix parsing of NoXyz extensions+     * Bump `Cabal` upper bound to 4.0+     * Add option to automatically group imports (by Tikhon Jelvis)+ - 0.14.2.0 (2022-04-27)      *  Add a build flag to force the use of ghc-lib-parser 
README.markdown view
@@ -168,8 +168,8 @@ can use stylish-haskell for formatting.  [HLS]: https://github.com/haskell/haskell-language-server-[HLS option]: https://github.com/haskell/haskell-language-server#language-specific-server-options-[HLS stylish-haskell Plugin]: https://github.com/haskell/haskell-language-server/blob/master/plugins/default/src/Ide/Plugin/StylishHaskell.hs+[HLS option]: https://haskell-language-server.readthedocs.io/en/latest/configuration.html#language-specific-server-options+[HLS stylish-haskell Plugin]: https://github.com/haskell/haskell-language-server/blob/master/plugins/hls-stylish-haskell-plugin/src/Ide/Plugin/StylishHaskell.hs [LSP]: https://microsoft.github.io/language-server-protocol/  ### VIM integration
data/stylish-haskell.yaml view
@@ -294,6 +294,107 @@       # Default: false       post_qualify: false +      # Automatically group imports based on their module names, with+      # a blank line separating each group. Groups are ordered in+      # alphabetical order.+      #+      # By default, this groups by the first part of each module's+      # name (Control.* will be grouped together, Data.*... etc), but+      # this can be configured with the group_patterns setting.+      #+      # When enabled, this rewrites existing blank lines and groups.+      #+      # - true: Group imports by the first part of the module name.+      #+      #   > import Control.Applicative+      #   > import Control.Monad+      #   > import Control.Monad.MonadError+      #   >+      #   > import Data.Functor+      #+      # - false: Keep import groups as-is (still sorting and+      #   formatting the imports within each group)+      #+      #   > import Control.Monad+      #   > import Data.Functor+      #   >+      #   > import Control.Applicative+      #   > import Control.Monad.MonadError+      #+      # Default: false+      group_imports: false++      # A list of rules specifying how to group modules and how to+      # order the groups.+      #+      # Each rule has a match field; the rule only applies to module+      # names matched by this pattern. Patterns are POSIX extended+      # regular expressions; see the documentation of Text.Regex.TDFA+      # for details:+      # https://hackage.haskell.org/package/regex-tdfa-1.3.1.2/docs/Text-Regex-TDFA.html+      #+      # Rules are processed in order, so only the *first* rule that+      # matches a specific module will apply. Any module names that do+      # not match a single rule will be put into a single group at the+      # end of the import block.+      #+      # Example: group MyApp modules first, with everything else in+      # one group at the end.+      #+      #  group_rules:+      #    - match: "^MyApp\\>"+      #+      #  > import MyApp+      #  > import MyApp.Foo+      #  >+      #  > import Control.Monad+      #  > import MyApps+      #  > import Test.MyApp+      #+      # A rule can also optionally have a sub_group pattern. Imports+      # that match the rule will be broken up into further groups by+      # the part of the module name matched by the sub_group pattern.+      #+      # Example: group MyApp modules first, then everything else+      # sub-grouped by the first part of the module name.+      #+      #  group_rules:+      #    - match: "^MyApp\\>"+      #    - match: "."+      #      sub_group: "^[^.]+"+      #+      #  > import MyApp+      #  > import MyApp.Foo+      #  >+      #  > import Control.Applicative+      #  > import Control.Monad+      #  >+      #  > import Data.Map+      #+      # A pattern only needs to match part of the module name, which+      # could be in the middle. You can use ^pattern to anchor to the+      # beginning of the module name, pattern$ to anchor to the end+      # and ^pattern$ to force a full match. Example:+      #+      #  - "Test\\." would match "Test.Foo" and "Foo.Test.Lib"+      #  - "^Test\\." would match "Test.Foo" but not "Foo.Test.Lib"+      #  - "\\.Test$" would match "Foo.Test" but not "Foo.Test.Lib"+      #  - "^Test$" would *only* match "Test"+      #+      # You can use \\< and \\> to anchor against the beginning and+      # end of words, respectively. For example:+      #+      #  - "^Test\\." would match "Test.Foo" but not "Test" or "Tests"+      #  - "^Test\\>" would match "Test.Foo" and "Test", but not+      #    "Tests"+      #+      # The default is a single rule that matches everything and+      # sub-groups based on the first component of the module name.+      #+      # Default: [{ "match" : ".*", "sub_group": "^[^.]+" }]+      group_rules:+        - match: ".*"+          sub_group: "^[^.]+"    # Language pragmas   - language_pragmas:
lib/Language/Haskell/Stylish/Config.hs view
@@ -287,6 +287,8 @@       <*> o A..:? "separate_lists" A..!= def Imports.separateLists       <*> o A..:? "space_surround" A..!= def Imports.spaceSurround       <*> o A..:? "post_qualify" A..!= def Imports.postQualified+      <*> o A..:? "group_imports" A..!= def Imports.groupImports+      <*> o A..:? "group_rules" A..!= def Imports.groupRules   where     def f = f Imports.defaultOptions 
lib/Language/Haskell/Stylish/Parse.hs view
@@ -5,13 +5,11 @@   ---------------------------------------------------------------------------------import           Control.Monad                                      ((>=>)) import           Data.List                                          (foldl',                                                                      stripPrefix) import           Data.Maybe                                         (fromMaybe,                                                                      listToMaybe,                                                                      mapMaybe)-import           Data.Traversable                                   (for) import qualified GHC.Data.StringBuffer                              as GHC import           GHC.Driver.Ppr                                     as GHC import qualified GHC.Driver.Session                                 as GHC@@ -36,6 +34,15 @@   --------------------------------------------------------------------------------+parseExtension :: String -> Either String (LangExt.Extension, Bool)+parseExtension str = case GHCEx.readExtension str of+    Just e  -> Right (e, True)+    Nothing -> case str of+        'N' : 'o' : str' -> fmap not <$> parseExtension str'+        _                -> Left $ "Unknown extension: " ++ show str+++-------------------------------------------------------------------------------- -- | Filter out lines which use CPP macros unCpp :: String -> String unCpp = unlines . go False . lines@@ -60,23 +67,24 @@ parseModule :: Extensions -> Maybe FilePath -> String -> Either String Module parseModule externalExts0 fp string = do     -- Parse extensions.-    externalExts1 <- for externalExts0 $ \s -> case GHCEx.readExtension s of-        Nothing -> Left $ "Unknown extension: " ++ show s-        Just e  -> Right e+    externalExts1 <- traverse parseExtension externalExts0      -- Build first dynflags.-    let dynFlags0 = foldl' turnOn baseDynFlags externalExts1+    let dynFlags0 = foldl' toggleExt baseDynFlags externalExts1      -- Parse options from file     let fileOptions = fmap GHC.unLoc $ GHC.getOptions dynFlags0             (GHC.stringToStringBuffer string)             (fromMaybe "-" fp)-        fileExtensions = mapMaybe-            (stripPrefix "-X" >=> GHCEx.readExtension)+        fileExtensions = mapMaybe (\str -> do+            str' <- stripPrefix "-X" str+            case parseExtension str' of+                Left _  -> Nothing+                Right x -> pure x)             fileOptions      -- Set further dynflags.-    let dynFlags1 = foldl' turnOn dynFlags0 fileExtensions+    let dynFlags1 = foldl' toggleExt dynFlags0 fileExtensions             `GHC.gopt_set` GHC.Opt_KeepRawTokenStream      -- Possibly strip CPP.@@ -92,7 +100,7 @@   where     withFileName x = maybe "" (<> ": ") fp <> x -    turnOn dynFlags ext = foldl'-        turnOn-        (GHC.xopt_set dynFlags ext)-        [rhs | (lhs, True, rhs) <- GHC.impliedXFlags, lhs == ext]+    toggleExt dynFlags (ext, onOff) = foldl'+        toggleExt+        ((if onOff then GHC.xopt_set else GHC.xopt_unset) dynFlags ext)+        [(rhs, onOff') | (lhs, onOff', rhs) <- GHC.impliedXFlags, lhs == ext]
lib/Language/Haskell/Stylish/Step/Imports.hs view
@@ -1,7 +1,10 @@-{-# LANGUAGE BlockArguments  #-}-{-# LANGUAGE DoAndIfThenElse #-}-{-# LANGUAGE LambdaCase      #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE BlockArguments    #-}+{-# LANGUAGE DoAndIfThenElse   #-}+{-# LANGUAGE FlexibleContexts  #-}+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE NamedFieldPuns    #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-} module Language.Haskell.Stylish.Step.Imports   ( Options (..)   , defaultOptions@@ -10,22 +13,31 @@   , LongListAlign (..)   , EmptyListAlign (..)   , ListPadding (..)+  , GroupRule (..)   , step    , printImport++  , parsePattern+  , unsafeParsePattern   ) where  -------------------------------------------------------------------------------- import           Control.Monad                     (forM_, void, when)+import qualified Data.Aeson                        as A import           Data.Foldable                     (toList) import           Data.Function                     (on, (&)) import           Data.Functor                      (($>))-import           Data.List                         (sortBy)+import           Data.List                         (groupBy, intercalate,+                                                    partition, sortBy, sortOn) import           Data.List.NonEmpty                (NonEmpty (..)) import qualified Data.List.NonEmpty                as NonEmpty import qualified Data.Map                          as Map-import           Data.Maybe                        (fromMaybe, isJust)+import           Data.Maybe                        (fromMaybe, isJust, mapMaybe)+import           Data.Sequence                     (Seq ((:|>)))+import qualified Data.Sequence                     as Seq import qualified Data.Set                          as Set+import qualified Data.Text                         as T import qualified GHC.Data.FastString               as GHC import qualified GHC.Hs                            as GHC import qualified GHC.Types.Name.Reader             as GHC@@ -33,7 +45,9 @@ import qualified GHC.Types.SrcLoc                  as GHC import qualified GHC.Unit.Module.Name              as GHC import qualified GHC.Unit.Types                    as GHC-+import           Text.Regex.TDFA                   (Regex)+import qualified Text.Regex.TDFA                   as Regex+import           Text.Regex.TDFA.ReadRegex         (parseRegex)  -------------------------------------------------------------------------------- import           Language.Haskell.Stylish.Block@@ -44,7 +58,6 @@ import           Language.Haskell.Stylish.Step import           Language.Haskell.Stylish.Util - -------------------------------------------------------------------------------- data Options = Options     { importAlign    :: ImportAlign@@ -56,20 +69,28 @@     , separateLists  :: Bool     , spaceSurround  :: Bool     , postQualified  :: Bool+    , groupImports   :: Bool+    , groupRules     :: [GroupRule]     } deriving (Eq, Show)  defaultOptions :: Options defaultOptions = Options-    { importAlign     = Global-    , listAlign       = AfterAlias-    , padModuleNames  = True-    , longListAlign   = Inline-    , emptyListAlign  = Inherit-    , listPadding     = LPConstant 4-    , separateLists   = True-    , spaceSurround   = False-    , postQualified   = False+    { importAlign    = Global+    , listAlign      = AfterAlias+    , padModuleNames = True+    , longListAlign  = Inline+    , emptyListAlign = Inherit+    , listPadding    = LPConstant 4+    , separateLists  = True+    , spaceSurround  = False+    , postQualified  = False+    , groupImports   = False+    , groupRules     = [defaultGroupRule]     }+  where defaultGroupRule = GroupRule+          { match    = unsafeParsePattern ".*"+          , subGroup = Just $ unsafeParsePattern "^[^.]+"+          }  data ListPadding     = LPConstant Int@@ -103,7 +124,78 @@     | Multiline -- multiline     deriving (Eq, Show) +-- | A rule for grouping imports that specifies which module names+-- belong in a group and (optionally) how to break them up into+-- sub-groups.+--+-- See the documentation for the group_rules setting in+-- data/stylish-haskell.yaml for more details.+data GroupRule = GroupRule+  { match    :: Pattern+    -- ^ The pattern that determines whether a rule applies to a+    -- module name.+  , subGroup :: Maybe Pattern+    -- ^ An optional pattern for breaking the group up into smaller+    -- sub-groups.+  } deriving (Show, Eq) +instance A.FromJSON GroupRule where+  parseJSON = A.withObject "group_rule" parse+    where parse o = GroupRule+                <$> (o A..: "match")+                <*> (o A..:? "sub_group")++-- | A compiled regular expression. Provides instances that 'Regex'+-- does not have (eg 'Show', 'Eq' and 'FromJSON').+--+-- Construct with 'parsePattern' to maintain the invariant that+-- 'string' is the exact regex string used to compile 'regex'.+data Pattern = Pattern+  { regex  :: Regex+    -- ^ The compiled regular expression.+  , string :: String+    -- ^ The valid regex string that 'regex' was compiled from.+  }++instance Show Pattern where show = show . string++instance Eq Pattern where (==) = (==) `on` string++instance A.FromJSON Pattern where+  parseJSON = A.withText "regex" parse+    where parse text = case parsePattern $ T.unpack text of+            Left err  -> fail $ "Invalid regex:\n" <> err+            Right pat -> pure pat+++-- | Parse a string into a compiled regular expression ('Pattern').+--+-- Returns a human-readable parse error message if the string is not+-- valid regex syntax.+--+-- >>> parsePattern "^([^.]+)"+-- Right "^([^.]+)"+--+-- >>> parsePattern "("+-- Left "\"(\" (line 1, column 2):\nunexpected end of input\nexpecting empty () or anchor ^ or $ or an atom"+parsePattern :: String -> Either String Pattern+parsePattern string = case parseRegex string of+  Right _  -> Right $ Pattern { string, regex = Regex.makeRegex string }+  Left err -> Left (show err)++-- | Parse a string into a regular expression, raising a runtime+-- exception if the string is not valid regex syntax.+--+-- >>> unsafeParsePattern "^([^.]+)"+-- "^([^.]+)"+--+-- >>> unsafeParsePattern "("+-- "*** Exception: "(" (line 1, column 2):+-- unexpected end of input+-- expecting empty () or anchor ^ or $ or an atom+unsafeParsePattern :: String -> Pattern+unsafeParsePattern = either error id . parsePattern+ -------------------------------------------------------------------------------- step :: Maybe Int -> Options -> Step step columns = makeStep "Imports (ghc-lib-parser)" . printImports columns@@ -111,11 +203,15 @@  -------------------------------------------------------------------------------- printImports :: Maybe Int -> Options -> Lines -> Module -> Lines-printImports maxCols align ls m = Editor.apply changes ls+printImports maxCols options ls m = Editor.apply changes ls   where     groups = moduleImportGroups m     moduleStats = foldMap importStats . fmap GHC.unLoc $ concatMap toList groups-    changes = foldMap (formatGroup maxCols align moduleStats) groups+    changes+      | groupImports options =+          groupAndFormat maxCols options moduleStats groups+      | otherwise =+          foldMap (formatGroup maxCols options moduleStats) groups  formatGroup     :: Maybe Int -> Options -> ImportStats@@ -158,6 +254,93 @@         None   -> mempty    forM_ group \imp -> printQualified options padNames stats imp >> newline+++--------------------------------------------------------------------------------+-- | Reorganize imports into groups based on 'groupPatterns', then+-- format each group as specified by the rest of 'Options'.+--+-- Note: this will discard blank lines and comments inside the imports+-- section.+groupAndFormat+  :: Maybe Int+  -> Options+  -> ImportStats+  -> [NonEmpty (GHC.LImportDecl GHC.GhcPs)]+  -> Editor.Edits+groupAndFormat _ _ _ [] = mempty+groupAndFormat maxCols options moduleStats groups =+  Editor.changeLines block (const regroupedLines)+  where+    regroupedLines :: Lines+    regroupedLines = intercalate [""] $+      map (formatImports maxCols options moduleStats) grouped++    grouped :: [NonEmpty (GHC.LImportDecl GHC.GhcPs)]+    grouped = groupByRules (groupRules options) imports++    imports :: [GHC.LImportDecl GHC.GhcPs]+    imports = concatMap toList groups++    -- groups is non-empty by the pattern for this case+    -- imports is non-empty as long as groups is non-empty+    block = Block+      (GHC.srcSpanStartLine . src $ head imports)+      (GHC.srcSpanEndLine   . src $ last imports)+    src = fromMaybe (error "regroupImports: missing location") .+      GHC.srcSpanToRealSrcSpan . GHC.getLocA++-- | Group imports based on a list of patterns.+--+-- See the documentation for @group_patterns@ in+-- @data/stylish-haskell.yaml@ for details about the patterns and+-- grouping logic.+groupByRules+  :: [GroupRule]+  -- ^ The patterns specifying the groups to build. Order matters:+  -- earlier patterns take precedence over later ones.+  -> [GHC.LImportDecl GHC.GhcPs]+  -- ^ The imports to group. Order does not matter.+  -> [NonEmpty (GHC.LImportDecl GHC.GhcPs)]+groupByRules rules allImports = toList $ go rules allImports Seq.empty+  where+    go :: [GroupRule]+       -> [GHC.LImportDecl GHC.GhcPs]+       -> Seq (NonEmpty (GHC.LImportDecl GHC.GhcPs))+       -> Seq (NonEmpty (GHC.LImportDecl GHC.GhcPs))+    go [] [] groups            = groups+    go [] imports groups       = groups :|> NonEmpty.fromList imports+    go (r : rs) imports groups =+      let+        (groups', rest) = extract r imports+      in+        go rs rest (groups <> groups')++    extract :: GroupRule+            -> [GHC.LImportDecl GHC.GhcPs]+            -> ( Seq (NonEmpty (GHC.LImportDecl GHC.GhcPs))+               , [GHC.LImportDecl GHC.GhcPs]+               )+    extract GroupRule { match, subGroup } imports =+      let+        (matched, rest) = partition (matches match) imports+        subgroups = groupBy ((==) `on` firstMatch subGroup) $+                      sortOn (firstMatch subGroup) matched+      in+        -- groupBy never produces empty groups, so this mapMaybe will+        -- not discard anything from subgroups+        (Seq.fromList $ mapMaybe NonEmpty.nonEmpty subgroups, rest)++    matches :: Pattern -> GHC.LImportDecl GHC.GhcPs -> Bool+    matches Pattern { regex } import_ = Regex.match regex $ moduleName import_++    firstMatch :: Maybe Pattern -> GHC.LImportDecl GHC.GhcPs -> String+    firstMatch (Just Pattern { regex }) import_ =+      Regex.match regex $ moduleName import_+    firstMatch Nothing _ =+      "" -- constant grouping key, so everything will be grouped together++    moduleName = importModuleName . GHC.unLoc   --------------------------------------------------------------------------------
stylish-haskell.cabal view
@@ -1,6 +1,6 @@ Cabal-version: 2.4 Name:          stylish-haskell-Version:       0.14.2.0+Version:       0.14.3.0 Synopsis:      Haskell code prettifier Homepage:      https://github.com/haskell/stylish-haskell License:       BSD-3-Clause@@ -37,12 +37,13 @@     aeson             >= 0.6    && < 2.1,     base              >= 4.8    && < 5,     bytestring        >= 0.9    && < 0.12,-    Cabal             >= 3.4    && < 3.7,+    Cabal             >= 3.4    && < 4.0,     containers        >= 0.3    && < 0.7,     directory         >= 1.2.3  && < 1.4,     filepath          >= 1.1    && < 1.5,     file-embed        >= 0.0.10 && < 0.1,     mtl               >= 2.0    && < 2.3,+    regex-tdfa        >= 1.3    && < 1.4,     syb               >= 0.3    && < 0.8,     text              >= 1.2    && < 1.3,     HsYAML-aeson      >=0.2.0   && < 0.3,
tests/Language/Haskell/Stylish/Parse/Tests.hs view
@@ -30,6 +30,7 @@     , testCase "UnicodeSyntax extension"     testUnicodeSyntax     , testCase "XmlSyntax regression"        testXmlSyntaxRegression     , testCase "MagicHash regression"        testMagicHashRegression+    , testCase "Disabling extensions"        testDisableExtensions     ]  --------------------------------------------------------------------------------@@ -137,6 +138,13 @@ testMagicHashRegression = returnsRight $ parseModule [] Nothing $ unlines   [ "xs = \"foo\"#|1#|'a'#|bar#|Nil"   ]++testDisableExtensions :: Assertion+testDisableExtensions = returnsRight $+    parseModule ["NoImplicitPrelude"] Nothing $ unlines+    [ "{-# NoStarIsType #-}"+    , "main = return ()"+    ]  -------------------------------------------------------------------------------- returnsRight :: HasCallStack => Show a => Either a b -> Assertion
tests/Language/Haskell/Stylish/Step/Imports/Tests.hs view
@@ -1,5 +1,6 @@+{-# LANGUAGE OverloadedStrings #-} ---------------------------------------------------------------------------------{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedLists   #-} module Language.Haskell.Stylish.Step.Imports.Tests     ( tests     ) where@@ -70,6 +71,14 @@     , testCase "case 36" case36     , testCase "case 37" case37     , testCase "case 38" case38+    , testCase "case 39" case39+    , testCase "case 40" case40+    , testCase "case 41" case41+    , testCase "case 42" case42+    , testCase "case 43" case43+    , testCase "case 44a" case44a+    , testCase "case 44b" case44b+    , testCase "case 44c" case44c     ]  @@ -198,7 +207,7 @@ case08 :: Assertion case08 =   let-    options = Options Global WithAlias True Inline Inherit (LPConstant 4) True False False+    options = defaultOptions { listAlign = WithAlias }   in     assertSnippet (step (Just 80) options) input     [ "module Herp where"@@ -222,7 +231,7 @@ case08b :: Assertion case08b =   let-    options = Options Global WithModuleName True Inline Inherit (LPConstant 4) True False False+    options = defaultOptions { listAlign = WithModuleName }   in     assertSnippet (step (Just 80) options) input     ["module Herp where"@@ -245,7 +254,7 @@ case09 :: Assertion case09 =   let-    options = Options Global WithAlias True Multiline Inherit (LPConstant 4) True False False+    options = defaultOptions { listAlign = WithAlias, longListAlign = Multiline }   in     assertSnippet (step (Just 80) options) input     [ "module Herp where"@@ -280,7 +289,11 @@ case10 :: Assertion case10 =   let-    options = Options Group WithAlias True Multiline Inherit (LPConstant 4) True False False+    options = defaultOptions+      { importAlign   = Group+      , listAlign     = WithAlias+      , longListAlign = Multiline+      }   in     assertSnippet (step (Just 40) options) input     [ "module Herp where"@@ -321,7 +334,7 @@ case11 :: Assertion case11 =   let-    options = Options Group NewLine True Inline Inherit (LPConstant 4) True False False+    options = defaultOptions { importAlign = Group, listAlign = NewLine }   in     assertSnippet (step (Just 80) options) input     [ "module Herp where"@@ -348,7 +361,7 @@ case11b :: Assertion case11b =   let-    options = Options Group WithModuleName True Inline Inherit (LPConstant 4) True False False+    options = defaultOptions { importAlign = Group, listAlign = WithModuleName }   in     assertSnippet (step (Just 80) options) input     [ "module Herp where"@@ -371,7 +384,11 @@ case12 :: Assertion case12 =   let-    options = Options Group NewLine True Inline Inherit (LPConstant 2) True False False+    options = defaultOptions+      { importAlign = Group+      , listAlign   = NewLine+      , listPadding = LPConstant 2+      }   in     assertSnippet (step (Just 80) options)     [ "import Data.List (map)"@@ -385,7 +402,11 @@ case12b :: Assertion case12b =   let-    options = Options Group WithModuleName True Inline Inherit (LPConstant 2) True False False+    options = defaultOptions+      { importAlign = Group+      , listAlign   = WithModuleName+      , listPadding = LPConstant 2+      }   in     assertSnippet (step (Just 80) options)     ["import Data.List (map)"]@@ -396,7 +417,11 @@ case13 :: Assertion case13 =   let-    options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 4) True False False+    options = defaultOptions+      { importAlign   = None+      , listAlign     = WithAlias+      , longListAlign = InlineWithBreak+      }   in     assertSnippet (step (Just 80) options)     [ "import qualified Data.List as List (concat, foldl, foldr, head, init,"@@ -410,7 +435,11 @@ case13b :: Assertion case13b =   let-    options = Options None WithModuleName True InlineWithBreak Inherit (LPConstant 4) True False False+    options = defaultOptions+      { importAlign   = None+      , listAlign     = WithModuleName+      , longListAlign = InlineWithBreak+      }   in     assertSnippet (step (Just 80) options)     [ "import qualified Data.List as List (concat, foldl, foldr, head, init,"@@ -426,7 +455,12 @@ case14 :: Assertion case14 =   let-    options = Options None WithAlias True InlineWithBreak Inherit (LPConstant 10) True False False+    options = defaultOptions+      { importAlign   = None+      , listAlign     = WithAlias+      , longListAlign = InlineWithBreak+      , listPadding   = LPConstant 10+      }   in     assertSnippet (step (Just 80) options)     [ "import qualified Data.List as List (concat, map, null, reverse, tail, (++))"@@ -439,7 +473,7 @@ case15 :: Assertion case15 =   let-    options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False False+    options = defaultOptions { importAlign = None, longListAlign = Multiline }   in     assertSnippet (step (Just 80) options)     [ "import Data.Acid (AcidState)"@@ -464,7 +498,11 @@ case16 :: Assertion case16 =   let-    options = Options None AfterAlias True Multiline Inherit (LPConstant 4) False False False+    options = defaultOptions+      { importAlign   = None+      , longListAlign = Multiline+      , separateLists = False+      }   in     assertSnippet (step (Just 80) options)     [ "import Data.Acid (AcidState)"@@ -487,7 +525,7 @@ case17 :: Assertion case17 =   let-    options = Options None AfterAlias True Multiline Inherit (LPConstant 4) True False False+    options = defaultOptions { importAlign = None, longListAlign = Multiline }   in     assertSnippet (step (Just 80) options)     [ "import Control.Applicative (Applicative ((<*>),pure))"@@ -504,7 +542,7 @@ case18 :: Assertion case18 =   let-    options = Options None AfterAlias True InlineToMultiline Inherit (LPConstant 4) True False False+    options = defaultOptions { importAlign = None, longListAlign = InlineToMultiline }   in     assertSnippet (step (Just 40) options)     [ "import Data.Foo as Foo (Bar, Baz, Foo)"@@ -531,7 +569,12 @@ case19 :: Assertion case19 =   let-    options = Options Global NewLine True InlineWithBreak RightAfter (LPConstant 17) True False False+    options = defaultOptions+      { listAlign      = NewLine+      , longListAlign  = InlineWithBreak+      , emptyListAlign = RightAfter+      , listPadding    = LPConstant 17+      }   in     assertSnippet (step (Just 40) options) case19input        ----------------------------------------@@ -547,7 +590,13 @@ case19b :: Assertion case19b =   let-    options = Options File NewLine True InlineWithBreak RightAfter (LPConstant 17) True False False+    options = defaultOptions+      { importAlign    = File+      , listAlign      = NewLine+      , longListAlign  = InlineWithBreak+      , emptyListAlign = RightAfter+      , listPadding    = LPConstant 17+      }   in     assertSnippet (step (Just 40) options) case19input        ----------------------------------------@@ -562,7 +611,13 @@ case19c :: Assertion case19c =   let-    options = Options File NewLine True InlineWithBreak RightAfter LPModuleName True False False+    options = defaultOptions+      { importAlign    = File+      , listAlign      = NewLine+      , longListAlign  = InlineWithBreak+      , emptyListAlign = RightAfter+      , listPadding    = LPModuleName+      }   in     assertSnippet (step (Just 40) options) case19input        ----------------------------------------@@ -577,7 +632,12 @@ case19d :: Assertion case19d =   let-    options = Options Global NewLine True InlineWithBreak RightAfter LPModuleName True False False+    options = defaultOptions+      { listAlign      = NewLine+      , longListAlign  = InlineWithBreak+      , emptyListAlign = RightAfter+      , listPadding    = LPModuleName+      }   in     assertSnippet (step (Just 40) options) case19input        ----------------------------------------@@ -669,7 +729,11 @@ case23 :: Assertion case23 =   let-    options = Options None AfterAlias False Inline Inherit (LPConstant 4) True True False+    options = defaultOptions+      { importAlign    = None+      , padModuleNames = False+      , spaceSurround  = True+      }   in     assertSnippet (step (Just 40) options)     [ "import Data.Acid (AcidState)"@@ -694,7 +758,12 @@ case23b :: Assertion case23b =   let-    options = Options None WithModuleName False Inline Inherit (LPConstant 4) True True False+    options = defaultOptions+      { importAlign    = None+      , listAlign      = WithModuleName+      , padModuleNames = False+      , spaceSurround  = True+      }   in     assertSnippet (step (Just 40) options)     [ "import Data.Acid (AcidState)"@@ -720,7 +789,12 @@ case24 :: Assertion case24 =   let-    options = Options None AfterAlias False InlineWithBreak Inherit (LPConstant 4) True True False+    options = defaultOptions+      { importAlign    = None+      , padModuleNames = False+      , longListAlign  = InlineWithBreak+      , spaceSurround  = True+      }   in     assertSnippet (step (Just 40) options)     [ "import Data.Acid (AcidState)"@@ -744,7 +818,12 @@ case25 :: Assertion case25 =   let-    options = Options Group AfterAlias False Multiline Inherit (LPConstant 4) False False False+    options = defaultOptions+      { importAlign    = Group+      , padModuleNames = False+      , longListAlign  = Multiline+      , separateLists  = False+      }   in     assertSnippet (step (Just 80) options)     [ "import Data.Acid (AcidState)"@@ -930,3 +1009,285 @@     [ "import Happstack.Server"     , "import HSP"     ]++--------------------------------------------------------------------------------+case39 :: Assertion+case39 = assertSnippet (step Nothing options)+  [ "import Something.A"+  , "import SomethingElse.A"+  , "import SomeThing.B"+  , "import SomeThingelse.B"+  ]+  [ "import           SomeThing.B"+  , ""+  , "import           SomeThingelse.B"+  , ""+  , "import           Something.A"+  , ""+  , "import           SomethingElse.A"+  ]+  where options = defaultOptions { groupImports = True }++--------------------------------------------------------------------------------+case40 :: Assertion+case40 = assertSnippet (step Nothing options)+    [ "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.Foo           (Foo (Bar, Foo), Goo (Goo))"+    , "import Data.Maybe         (Maybe (Just, Nothing))"+    , "import Data.Maybe.Extra   qualified (Maybe (Just, Nothing))"+    , "import Data.Set           (empty, intersect, nub)"+    ]+  where options = defaultOptions { groupImports = True, postQualified = True }++--------------------------------------------------------------------------------+case41 :: Assertion+case41 = assertSnippet (step Nothing options)+    [ "import Data.Default.Class (Default(def))"+    , "import qualified Data.Aeson as JSON"+    , "import Control.Monad"+    , "import Control.Monad"+    , "import qualified Foo.Bar.Baz"+    , ""+    , "import Data.Set (empty, intersect)"+    , "import Data.Maybe (Maybe   (Just, Nothing))"+    , "import qualified Data.Maybe.Extra (Maybe(Just, Nothing))"+    , ""+    , "import qualified Data.Aeson as JSON"+    , ""+    , "import Data.Foo (Foo (Foo,Bar), Goo(Goo))"+    , "import Data.Foo (Foo (Foo,Bar))"+    , "import Data.Set (empty, nub)"+    , "import Foo.Bar.Baz (Foo)"+    ]+    [ "import Control.Monad"+    , ""+    , "import qualified Data.Aeson         as JSON"+    , "import           Data.Default.Class (Default (def))"+    , "import           Data.Foo           (Foo (Bar, Foo), Goo (Goo))"+    , "import           Data.Maybe         (Maybe (Just, Nothing))"+    , "import qualified Data.Maybe.Extra   (Maybe (Just, Nothing))"+    , "import           Data.Set           (empty, intersect, nub)"+    , ""+    , "import           Foo.Bar.Baz (Foo)"+    , "import qualified Foo.Bar.Baz"+    ]+  where options = defaultOptions { groupImports = True, importAlign = Group }++--------------------------------------------------------------------------------+case42 :: Assertion+case42 =+    assertSnippet (step (Just 80) options)+    [ "import Data.Acid (AcidState)"+    , "import Data.Default.Class (Default (def))"+    , "import Control.Monad"+    , ""+    , "import qualified Data.Acid as Acid (closeAcidState, createCheckpoint, openLocalStateFrom)"+    , ""+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (foo, bar)"+    ]+    [ "import Control.Monad"+    , ""+    , "import Data.Acid (AcidState)"+    , "import qualified Data.Acid as Acid"+    , "    ( closeAcidState"+    , "    , createCheckpoint"+    , "    , openLocalStateFrom"+    , "    )"+    , "import Data.Default.Class (Default (def))"+    , ""+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (bar, foo)"+    ]+  where options = defaultOptions+          { groupImports  = True+          , importAlign   = None+          , longListAlign = Multiline+          }++--------------------------------------------------------------------------------+case43 :: Assertion+case43 =+    assertSnippet (step (Just 80) options)+    [ "import Project.Internal.Blah"+    , "import Project.Something"+    , "import Control.Monad"+    , ""+    , "import qualified Data.Acid as Acid (closeAcidState, createCheckpoint, openLocalStateFrom)"+    , ""+    , "import qualified Project.Internal.Blarg as Blarg"+    , "import Control.Applicative"+    , "import Data.Functor"+    , "import Data.Acid (AcidState)"+    , "import Project"+    , ""+    , "import Data.Map (Map)"+    , "import qualified Data.Map as Map"+    ]+    [ "import Project.Internal.Blah"+    , "import qualified Project.Internal.Blarg as Blarg"+    , ""+    , "import Project"+    , "import Project.Something"+    , ""+    , "import Control.Applicative"+    , "import Control.Monad"+    , ""+    , "import Data.Acid (AcidState)"+    , "import qualified Data.Acid as Acid"+    , "    ( closeAcidState"+    , "    , createCheckpoint"+    , "    , openLocalStateFrom"+    , "    )"+    , "import Data.Functor"+    , "import Data.Map (Map)"+    , "import qualified Data.Map as Map"+    ]+  where options = defaultOptions+          { groupImports  = True+          , groupRules    =+              [ GroupRule+                { match = unsafeParsePattern "Project\\.Internal"+                , subGroup = Nothing+                }+              , GroupRule+                { match = unsafeParsePattern "Project"+                , subGroup = Nothing+                }+              , GroupRule+                { match = unsafeParsePattern ".*"+                , subGroup = Just $ unsafeParsePattern "^[^.]+"+                }+              ]+          , importAlign   = None+          , longListAlign = Multiline+          }++--------------------------------------------------------------------------------+case44a :: Assertion+case44a =+    assertSnippet (step (Just 80) options)+    [ "import Project"+    , "import Control.Monad"+    , ""+    , "import qualified Data.Acid as Acid"+    , "import Project.Something"+    , "import Data.Default.Class (Default (def))"+    , ""+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (foo, bar)"+    , "import ProJect.WrongCapitalization"+    ]+    [ "import Project"+    , "import Project.Something"+    , ""+    , "import Control.Monad"+    , "import qualified Data.Acid as Acid"+    , "import Data.Default.Class (Default (def))"+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (bar, foo)"+    , "import ProJect.WrongCapitalization"+    ]+  where options = defaultOptions+          { groupImports = True+          , groupRules   = [ GroupRule+                             { match = unsafeParsePattern "Project"+                             , subGroup = Nothing+                             }+                           ]+          , importAlign  = None+          }++--------------------------------------------------------------------------------+case44b :: Assertion+case44b =+    assertSnippet (step (Just 80) options)+    [ "import Project"+    , "import Control.Monad"+    , ""+    , "import qualified Data.Acid as Acid"+    , "import Project.Something"+    , "import Data.Default.Class (Default (def))"+    , ""+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (foo, bar)"+    , "import ProJect.WrongCapitalization"+    ]+    [ "import Project"+    , "import Project.Something"+    , ""+    , "import qualified Data.Acid as Acid"+    , ""+    , "import Data.Default.Class (Default (def))"+    , ""+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (bar, foo)"+    , ""+    , "import Control.Monad"+    , ""+    , "import ProJect.WrongCapitalization"+    ]+  where options = defaultOptions+          { groupImports = True+          , groupRules   =+              [ GroupRule+                { match = unsafeParsePattern "Project"+                , subGroup = Nothing+                }+              , GroupRule+                { match    = unsafeParsePattern "[^.]+\\.[^.]+"+                , subGroup = Just $ unsafeParsePattern "\\.[^.]+"+                }+              ]+          , importAlign  = None+          }+++--------------------------------------------------------------------------------+case44c :: Assertion+case44c =+    assertSnippet (step (Just 80) options)+    [ "import Project"+    , "import Control.Monad"+    , ""+    , "import qualified Data.Acid as Acid"+    , "import Project.Something"+    , "import Data.Default.Class (Default (def))"+    , ""+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (foo, bar)"+    , "import ProJect.WrongCapitalization"+    ]+    [ "import Project"+    , "import Project.Something"+    , ""+    , "import Control.Monad"+    , "import qualified Data.Acid as Acid"+    , "import Data.Default.Class (Default (def))"+    , "import qualified Herp.Derp.Internal.Types.Foobar as Internal (bar, foo)"+    , "import ProJect.WrongCapitalization"+    ]+  where options = defaultOptions+          { groupImports = True+          , groupRules   =+              [ GroupRule+                { match = unsafeParsePattern "Project"+                , subGroup = Nothing+                }+              , GroupRule+                { match = unsafeParsePattern "[^.]+\\.[^.]+"+                , subGroup = Nothing+                }+              ]+          , importAlign  = None+          }
tests/Language/Haskell/Stylish/Step/UnicodeSyntax/Tests.hs view
@@ -21,6 +21,7 @@ tests = testGroup "Language.Haskell.Stylish.Step.UnicodeSyntax.Tests"     [ testCase "case 01" case01     , testCase "case 02" case02+    , testCase "case 03" case03     ]  @@ -45,4 +46,15 @@     [ "{-# LaNgUaGe UnicodeSyntax #-}"     , "sort ∷ Ord a ⇒ [a] → [a]"     , "sort _ = []"+    ]+++--------------------------------------------------------------------------------+case03 :: Assertion+case03 = assertSnippet (step False "LANGUAGE")+    [ "x :: Int -> Int -> Int"+    , "x = undefined"+    ]+    [ "x ∷ Int → Int → Int"+    , "x = undefined"     ]