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 +5/−0
- README.markdown +2/−2
- data/stylish-haskell.yaml +101/−0
- lib/Language/Haskell/Stylish/Config.hs +2/−0
- lib/Language/Haskell/Stylish/Parse.hs +21/−13
- lib/Language/Haskell/Stylish/Step/Imports.hs +202/−19
- stylish-haskell.cabal +3/−2
- tests/Language/Haskell/Stylish/Parse/Tests.hs +8/−0
- tests/Language/Haskell/Stylish/Step/Imports/Tests.hs +385/−24
- tests/Language/Haskell/Stylish/Step/UnicodeSyntax/Tests.hs +12/−0
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" ]