diff options
author | Sean Whitton <spwhitton@spwhitton.name> | 2020-07-26 18:40:11 -0700 |
---|---|---|
committer | Sean Whitton <spwhitton@spwhitton.name> | 2020-07-26 18:40:11 -0700 |
commit | e64f6fa52d69631d90ea32cdd42d23037057d5c7 (patch) | |
tree | 27adb9aad9d09e6b2f4351d6f5b6cab774913282 | |
parent | 294cf1927fc105485bcf7043f1ae87819cb62441 (diff) | |
parent | 3621bf3aa5312fef61220e1760d9988307209c6a (diff) | |
download | stylish-haskell-e64f6fa52d69631d90ea32cdd42d23037057d5c7.tar.gz |
Merge tag 'v0.11.0.0'
v0.11.0.0
- 0.11.0.0 (2020-02-24)
* Disable record formatting by default
* Allow more customization for record formatting (by Maxim Koltsov)
* Disable formatting of data types without records (by Maxim Koltsov)
* Add `-r` flag to recursively find Haskell files (by Akos Marton)
44 files changed, 1591 insertions, 215 deletions
@@ -18,3 +18,4 @@ cabal.config cabal.sandbox.config cabal.sandbox.config dist +/dist-newstyle/ @@ -1,5 +1,21 @@ # CHANGELOG +- 0.11.0.0 (2020-02-24) + * Disable record formatting by default + * Allow more customization for record formatting (by Maxim Koltsov) + * Disable formatting of data types without records (by Maxim Koltsov) + * Add `-r` flag to recursively find Haskell files (by Akos Marton) + +- 0.10.0.0 (2020-01-26) + * Switch to HsYAML library (by vijayphoenix) + * Expose `format` from main library (by Łukasz Gołębiewski) + * Support record formatting (by Łukasz Gołębiewski and Pawel Szulc) + * Allow setting `columns` to `null` to disable all wrapping (by Chris + Martin) + * Bump `haskell-src-exts` to 1.23 + * New logo (by Jose Fernando García Parreño) + * Make language extension prefix configurable (by Flavio Corpa) + - 0.9.4.4 (2019-11-03) * Bump `haskell-src-exts` to 1.22 @@ -1,4 +1,5 @@ Copyright (c) 2012, Jasper Van der Jeugt <m@jaspervdj.be> +Copyright (c) 2016, 2018 Sean Whitton <spwhitton@spwhitton.name> All rights reserved. diff --git a/README.markdown b/README.markdown index 8f56ea6..4402a56 100644 --- a/README.markdown +++ b/README.markdown @@ -1,8 +1,8 @@ -stylish-haskell -=============== +## stylish-haskell -Introduction ------------- +<img src="./assets/Logo/SVG/RoundedLogo.svg" width="100px"> + +## Introduction A simple Haskell code prettifier. The goal is not to format all of the code in a file, since I find those kind of tools often "get in the way". However, @@ -10,8 +10,7 @@ manually cleaning up import statements etc. gets tedious very quickly. This tool tries to help where necessary without getting in the way. -Features --------- +## Features - Aligns and sorts `import` statements - Groups and wraps `{-# LANGUAGE #-}` pragmas, can remove (some) redundant @@ -22,13 +21,13 @@ Features - Replaces tabs by four spaces (turned off by default) - Replaces some ASCII sequences by their Unicode equivalents (turned off by default) +- Format data constructors and fields in records. Feature requests are welcome! Use the [issue tracker] for that. [issue tracker]: https://github.com/jaspervdj/stylish-haskell/issues -Example -------- +## Example Turns: @@ -46,10 +45,7 @@ import System.Directory (doesFileExist) import qualified Data.Map as M import Data.Map ((!), keys, Map) -data Point = Point - { pointX, pointY :: Double - , pointName :: String - } deriving (Show) +data Point = Point { pointX, pointY :: Double , pointName :: String} deriving (Show) ``` into: @@ -72,8 +68,8 @@ data Point = Point , pointName :: String } deriving (Show) ``` -Configuration -------------- + +## Configuration The tool is customizable to some extent. It tries to find a config file in the following order: @@ -95,8 +91,62 @@ Use `stylish-haskell --defaults > .stylish-haskell.yaml` to dump a well-documented default configuration to a file, this way you can get started quickly. -VIM integration ---------------- +## Record formatting + +Basically, stylish-haskell supports 4 different styles of records, controlled by `records` +in the config file. + +Here's an example of all four styles: + +```haskell +-- equals: "indent 2", "first_field": "indent 2" +data Foo a + = Foo + { a :: Int + , a2 :: String + -- ^ some haddock + } + | Bar + { b :: a + } + deriving (Eq, Show) + deriving (ToJSON) via Bar Foo + +-- equals: "same_line", "first_field": "indent 2" +data Foo a = Foo + { a :: Int + , a2 :: String + -- ^ some haddock + } + | Bar + { b :: a + } + deriving (Eq, Show) + deriving (ToJSON) via Bar Foo + +-- equals: "same_line", "first_field": "same_line" +data Foo a = Foo { a :: Int + , a2 :: String + -- ^ some haddock + } + | Bar { b :: a + } + deriving (Eq, Show) + deriving (ToJSON) via Bar Foo + +-- equals: "indent 2", first_field: "same_line" +data Foo a + = Foo { a :: Int + , a2 :: String + -- ^ some haddock + } + | Bar { b :: a + } + deriving (Eq, Show) + deriving (ToJSON) via Bar Foo +``` + +## VIM integration Since it works as a filter it is pretty easy to integrate this with VIM. @@ -124,14 +174,13 @@ autocmd FileType haskell let b:autoformat_autoindent=0 There are also plugins that run stylish-haskell automatically when you save a Haskell file: -* [vim-stylish-haskell] -* [vim-stylishask] +- [vim-stylish-haskell] +- [vim-stylishask] [vim-stylish-haskell]: https://github.com/nbouscal/vim-stylish-haskell [vim-stylishask]: https://github.com/alx741/vim-stylishask -Emacs integration ------------------ +## Emacs integration [haskell-mode] for Emacs supports `stylish-haskell`. For configuration, see [the “Using external formatters” section][haskell-mode/format] of the @@ -140,8 +189,7 @@ haskell-mode manual. [haskell-mode]: https://github.com/haskell/haskell-mode [haskell-mode/format]: http://haskell.github.io/haskell-mode/manual/latest/Autoformating.html -Atom integration ----------------- +## Atom integration [ide-haskell] for Atom supports `stylish-haskell`. @@ -150,15 +198,13 @@ Atom integration [ide-haskell]: https://atom.io/packages/ide-haskell [atom-beautify]: Https://atom.io/packages/atom-beautify -Visual Studio Code integration ------------------------------- +## Visual Studio Code integration [stylish-haskell-vscode] for VSCode supports `stylish-haskell`. [stylish-haskell-vscode]: https://github.com/vigoo/stylish-haskell-vscode -Using with Continuous Integration ---------------------------------- +## Using with Continuous Integration You can quickly grab the latest binary and run `stylish-haskell` like so: @@ -166,8 +212,7 @@ You can quickly grab the latest binary and run `stylish-haskell` like so: Where the `.` can be replaced with the arguments you pass to `stylish-haskell`. -Credits -------- +## Credits Written and maintained by Jasper Van der Jeugt. diff --git a/assets/Logo/PNG/1.5x/Recurso 4hdpi.png b/assets/Logo/PNG/1.5x/Recurso 4hdpi.png Binary files differnew file mode 100644 index 0000000..30c7a37 --- /dev/null +++ b/assets/Logo/PNG/1.5x/Recurso 4hdpi.png diff --git a/assets/Logo/PNG/1.5x/Recurso 5hdpi.png b/assets/Logo/PNG/1.5x/Recurso 5hdpi.png Binary files differnew file mode 100644 index 0000000..c73f840 --- /dev/null +++ b/assets/Logo/PNG/1.5x/Recurso 5hdpi.png diff --git a/assets/Logo/PNG/1.5x/Recurso 6hdpi.png b/assets/Logo/PNG/1.5x/Recurso 6hdpi.png Binary files differnew file mode 100644 index 0000000..f574889 --- /dev/null +++ b/assets/Logo/PNG/1.5x/Recurso 6hdpi.png diff --git a/assets/Logo/PNG/1x/Recurso 4mdpi.png b/assets/Logo/PNG/1x/Recurso 4mdpi.png Binary files differnew file mode 100644 index 0000000..cf35dd8 --- /dev/null +++ b/assets/Logo/PNG/1x/Recurso 4mdpi.png diff --git a/assets/Logo/PNG/1x/Recurso 5mdpi.png b/assets/Logo/PNG/1x/Recurso 5mdpi.png Binary files differnew file mode 100644 index 0000000..4d84ff3 --- /dev/null +++ b/assets/Logo/PNG/1x/Recurso 5mdpi.png diff --git a/assets/Logo/PNG/1x/Recurso 6mdpi.png b/assets/Logo/PNG/1x/Recurso 6mdpi.png Binary files differnew file mode 100644 index 0000000..e4a4767 --- /dev/null +++ b/assets/Logo/PNG/1x/Recurso 6mdpi.png diff --git a/assets/Logo/PNG/2x/Recurso 4xhdpi.png b/assets/Logo/PNG/2x/Recurso 4xhdpi.png Binary files differnew file mode 100644 index 0000000..114929e --- /dev/null +++ b/assets/Logo/PNG/2x/Recurso 4xhdpi.png diff --git a/assets/Logo/PNG/2x/Recurso 5xhdpi.png b/assets/Logo/PNG/2x/Recurso 5xhdpi.png Binary files differnew file mode 100644 index 0000000..ec1a2f4 --- /dev/null +++ b/assets/Logo/PNG/2x/Recurso 5xhdpi.png diff --git a/assets/Logo/PNG/2x/Recurso 6xhdpi.png b/assets/Logo/PNG/2x/Recurso 6xhdpi.png Binary files differnew file mode 100644 index 0000000..4b6353e --- /dev/null +++ b/assets/Logo/PNG/2x/Recurso 6xhdpi.png diff --git a/assets/Logo/PNG/3x/Recurso 4xxhdpi.png b/assets/Logo/PNG/3x/Recurso 4xxhdpi.png Binary files differnew file mode 100644 index 0000000..61c667e --- /dev/null +++ b/assets/Logo/PNG/3x/Recurso 4xxhdpi.png diff --git a/assets/Logo/PNG/3x/Recurso 5xxhdpi.png b/assets/Logo/PNG/3x/Recurso 5xxhdpi.png Binary files differnew file mode 100644 index 0000000..c877ce4 --- /dev/null +++ b/assets/Logo/PNG/3x/Recurso 5xxhdpi.png diff --git a/assets/Logo/PNG/3x/Recurso 6xxhdpi.png b/assets/Logo/PNG/3x/Recurso 6xxhdpi.png Binary files differnew file mode 100644 index 0000000..eb9fa18 --- /dev/null +++ b/assets/Logo/PNG/3x/Recurso 6xxhdpi.png diff --git a/assets/Logo/SVG/PinkLogo.svg b/assets/Logo/SVG/PinkLogo.svg new file mode 100644 index 0000000..6ff8d9d --- /dev/null +++ b/assets/Logo/SVG/PinkLogo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144.51 177.64"><defs><style>.cls-1{fill:#95538e;}</style></defs><title>Recurso 8</title><g id="Capa_2" data-name="Capa 2"><g id="Capa_3" data-name="Capa 3"><path class="cls-1" d="M60.76,108.89,41.94,75.78h0L1.41,147.14a10.65,10.65,0,0,0,9.26,15.92H35.74A10.67,10.67,0,0,0,45,157.67l15.75-27.73A21.35,21.35,0,0,0,60.76,108.89Z"/><path class="cls-1" d="M53,76.49A21.31,21.31,0,0,0,71.56,87.28h37.18L62.22,5.39A10.65,10.65,0,0,0,53,0H27.88a10.65,10.65,0,0,0-9.26,15.92Z"/><path class="cls-1" d="M143.13,147.13,120.75,107.4a21.3,21.3,0,0,0-18.57-10.86H65.06l34.38,61.05a10.63,10.63,0,0,0,9.28,5.42h25.12A10.65,10.65,0,0,0,143.13,147.13Z"/><path class="cls-1" d="M72.35,128.53,58.18,153.08a16.37,16.37,0,0,0,14.17,24.56h0a16.37,16.37,0,0,0,14.18-24.56Z"/></g></g></svg>
\ No newline at end of file diff --git a/assets/Logo/SVG/RoundedLogo.svg b/assets/Logo/SVG/RoundedLogo.svg new file mode 100644 index 0000000..bf2fcd7 --- /dev/null +++ b/assets/Logo/SVG/RoundedLogo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 221.77 221.77"><defs><style>.cls-1{fill:url(#Degradado_sin_nombre_17);}.cls-2{fill:#f3f8fc;}</style><linearGradient id="Degradado_sin_nombre_17" x1="32.48" y1="189.3" x2="189.3" y2="32.48" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#7b4a91"/><stop offset="1" stop-color="#9e5597"/></linearGradient></defs><title>Recurso 7</title><g id="Capa_2" data-name="Capa 2"><g id="Capa_3" data-name="Capa 3"><circle class="cls-1" cx="110.89" cy="110.89" r="110.89"/><path class="cls-2" d="M88.07,101.07,57.55,154.79a8,8,0,0,0,7,12H83.4a8,8,0,0,0,7-4.06l11.85-20.87a16,16,0,0,0,0-15.84L88.07,101.07Z"/><path class="cls-2" d="M96.36,44H77.48a8,8,0,0,0-7,12l25.9,45.6a16.06,16.06,0,0,0,14,8.12h28l-35-61.64A8,8,0,0,0,96.36,44Z"/><path class="cls-2" d="M164.23,154.78l-16.84-29.91a16,16,0,0,0-14-8.17H105.47l25.88,46a8,8,0,0,0,7,4.09h18.92A8,8,0,0,0,164.23,154.78Z"/><path class="cls-2" d="M111,140.78l-10.67,18.48A12.33,12.33,0,0,0,111,177.75h0a12.33,12.33,0,0,0,10.67-18.49Z"/></g></g></svg>
\ No newline at end of file diff --git a/assets/Logo/SVG/WhiteLogo.svg b/assets/Logo/SVG/WhiteLogo.svg new file mode 100644 index 0000000..ebb2d69 --- /dev/null +++ b/assets/Logo/SVG/WhiteLogo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144.51 177.64"><defs><style>.cls-1{fill:#f3f8fc;}</style></defs><title>Recurso 6</title><g id="Capa_2" data-name="Capa 2"><g id="Capa_3" data-name="Capa 3"><path class="cls-1" d="M60.76,108.89,41.94,75.78h0L1.41,147.14a10.65,10.65,0,0,0,9.26,15.92H35.74A10.67,10.67,0,0,0,45,157.67l15.75-27.73A21.35,21.35,0,0,0,60.76,108.89Z"/><path class="cls-1" d="M53,76.49A21.31,21.31,0,0,0,71.56,87.28h37.18L62.22,5.39A10.65,10.65,0,0,0,53,0H27.88a10.65,10.65,0,0,0-9.26,15.92Z"/><path class="cls-1" d="M143.13,147.13,120.75,107.4a21.3,21.3,0,0,0-18.57-10.86H65.06l34.38,61.05a10.63,10.63,0,0,0,9.28,5.42h25.12A10.65,10.65,0,0,0,143.13,147.13Z"/><path class="cls-1" d="M72.35,128.53,58.18,153.08a16.37,16.37,0,0,0,14.17,24.56h0a16.37,16.37,0,0,0,14.18-24.56Z"/></g></g></svg>
\ No newline at end of file diff --git a/data/stylish-haskell.yaml b/data/stylish-haskell.yaml index 401d384..d7de260 100644 --- a/data/stylish-haskell.yaml +++ b/data/stylish-haskell.yaml @@ -15,6 +15,34 @@ steps: # # true. # add_language_pragma: true + # Format record definitions. This is disabled by default. + # + # You can control the layout of record fields. The only rules that can't be configured + # are these: + # + # - "|" is always aligned with "=" + # - "," in fields is always aligned with "{" + # - "}" is likewise always aligned with "{" + # + # - records: + # # How to format equals sign between type constructor and data constructor. + # # Possible values: + # # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor. + # # - "indent N" -- insert a new line and N spaces from the beginning of the next line. + # equals: "indent 2" + # + # # How to format first field of each record constructor. + # # Possible values: + # # - "same_line" -- "{" and first field goes on the same line as the data constructor. + # # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor + # first_field: "indent 2" + # + # # How many spaces to insert between the column with "," and the beginning of the comment in the next line. + # field_comment: 2 + # + # # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines. + # deriving: 2 + # Align the right hand side of some elements. This is quite conservative # and only applies to statements where each element occupies a single # line. All default to true. @@ -203,6 +231,11 @@ steps: # is set to true, it will remove those redundant pragmas. Default: true. remove_redundant: true + # Language prefix to be used for pragma declaration, this allows you to + # use other options non case-sensitive like "language" or "Language". + # If a non correct String is provided, it will default to: LANGUAGE. + language_prefix: LANGUAGE + # Replace tabs by spaces. This is disabled by default. # - tabs: # # Number of spaces to use for each tab. Default: 8, as specified by the @@ -218,7 +251,11 @@ steps: # - squash: {} # A common setting is the number of columns (parts of) code will be wrapped -# to. Different steps take this into account. Default: 80. +# to. Different steps take this into account. +# +# Set this to null to disable all line wrapping. +# +# Default: 80. columns: 80 # By default, line endings are converted according to the OS. You can override diff --git a/doc/stylish-haskell.1.adoc b/doc/stylish-haskell.1.adoc new file mode 100644 index 0000000..65c2b6c --- /dev/null +++ b/doc/stylish-haskell.1.adoc @@ -0,0 +1,54 @@ += stylish-haskell(1) + +== NAME + +stylish-haskell - simple Haskell code prettifier + +== SYNOPSIS + +*stylish-haskell* [_-c_|_--config=FILE_] [_-v|--verbose_] +[_-i_|_--inplace_] [--no-utf8] [_FILES_]... + +*stylish-haskell* _-d_|_--defaults_ + +*stylish-haskell* _-?_|_--help_ + +*stylish-haskell* _--version_ + +== DESCRIPTION + +*stylish-haskell* performs automatic formatting on the Haskell code in +the files passed on the command line or piped via STDIN. It outputs to +STDOUT unless *-i* is specified. + +STDIN is assumed to be encoded UTF-8, unless the *--no-utf8* option is +used. + +=== OPTIONS + +*-c*, *--config=FILE*:: + Override the default configuration file. + +*-v*, *--verbose*:: + Turn on verbose output. + +*-i*, *--inplace*:: + Prettify and overwrite the given files in place. + +*-d*, *--defaults*:: + Dump default config and exit. + +*-?*, *--help*:: + Output help text and exit. + +*--version*:: + Output version information and exit. + +*--no-utf8*:: + Don't assume that STDIN is encoded UTF-8, and don't force UTF-8 output. + +== AUTHOR + +This manual page was originally written by Sean Whitton +<\spwhitton@spwhitton.name> for the Debian GNU/Linux system (but may be +used by others). diff --git a/lib/Language/Haskell/Stylish.hs b/lib/Language/Haskell/Stylish.hs index 46543ec..c50db4d 100644 --- a/lib/Language/Haskell/Stylish.hs +++ b/lib/Language/Haskell/Stylish.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE LambdaCase #-} -------------------------------------------------------------------------------- module Language.Haskell.Stylish ( -- * Run @@ -10,12 +11,15 @@ module Language.Haskell.Stylish , trailingWhitespace , unicodeSyntax -- ** Helpers + , findHaskellFiles , stepName -- * Config , module Language.Haskell.Stylish.Config -- * Misc , module Language.Haskell.Stylish.Verbose , version + , format + , ConfigPath(..) , Lines , Step ) where @@ -23,7 +27,11 @@ module Language.Haskell.Stylish -------------------------------------------------------------------------------- import Control.Monad (foldM) - +import System.Directory (doesDirectoryExist, + doesFileExist, + listDirectory) +import System.FilePath (takeExtension, + (</>)) -------------------------------------------------------------------------------- import Language.Haskell.Stylish.Config @@ -40,24 +48,25 @@ import Paths_stylish_haskell (version) -------------------------------------------------------------------------------- -simpleAlign :: Int -- ^ Columns +simpleAlign :: Maybe Int -- ^ Columns -> SimpleAlign.Config -> Step simpleAlign = SimpleAlign.step -------------------------------------------------------------------------------- -imports :: Int -- ^ columns +imports :: Maybe Int -- ^ columns -> Imports.Options -> Step imports = Imports.step -------------------------------------------------------------------------------- -languagePragmas :: Int -- ^ columns +languagePragmas :: Maybe Int -- ^ columns -> LanguagePragmas.Style -> Bool -- ^ Pad to same length in vertical mode? -> Bool -- ^ remove redundant? + -> String -- ^ language prefix -> Step languagePragmas = LanguagePragmas.step @@ -75,6 +84,7 @@ trailingWhitespace = TrailingWhitespace.step -------------------------------------------------------------------------------- unicodeSyntax :: Bool -- ^ add language pragma? + -> String -- ^ language prefix -> Step unicodeSyntax = UnicodeSyntax.step @@ -89,3 +99,47 @@ runStep exts mfp ls step = runSteps :: Extensions -> Maybe FilePath -> [Step] -> Lines -> Either String Lines runSteps exts mfp steps ls = foldM (runStep exts mfp) ls steps + +newtype ConfigPath = ConfigPath { unConfigPath :: FilePath } + +-- |Formats given contents optionally using the config provided as first param. +-- The second file path is the location from which the contents were read. +-- If provided, it's going to be printed out in the error message. +format :: Maybe ConfigPath -> Maybe FilePath -> String -> IO (Either String Lines) +format maybeConfigPath maybeFilePath contents = do + conf <- loadConfig (makeVerbose True) (fmap unConfigPath maybeConfigPath) + pure $ runSteps (configLanguageExtensions conf) maybeFilePath (configSteps conf) $ lines contents + + +-------------------------------------------------------------------------------- +-- | Searches Haskell source files in any given folder recursively. +findHaskellFiles :: Bool -> [FilePath] -> IO [FilePath] +findHaskellFiles v fs = mapM (findFilesR v) fs >>= return . concat + + +-------------------------------------------------------------------------------- +findFilesR :: Bool -> FilePath -> IO [FilePath] +findFilesR _ [] = return [] +findFilesR v path = do + doesFileExist path >>= \case + True -> return [path] + _ -> doesDirectoryExist path >>= \case + True -> findFilesRecursive path >>= + return . filter (\x -> takeExtension x == ".hs") + False -> do + makeVerbose v ("Input folder does not exists: " <> path) + findFilesR v [] + where + findFilesRecursive :: FilePath -> IO [FilePath] + findFilesRecursive = listDirectoryFiles findFilesRecursive + + listDirectoryFiles :: (FilePath -> IO [FilePath]) + -> FilePath -> IO [FilePath] + listDirectoryFiles go topdir = do + ps <- listDirectory topdir >>= + mapM (\x -> do + let dir = topdir </> x + doesDirectoryExist dir >>= \case + True -> go dir + False -> return [dir]) + return $ concat ps diff --git a/lib/Language/Haskell/Stylish/Align.hs b/lib/Language/Haskell/Stylish/Align.hs index 53549b9..1f28d7a 100644 --- a/lib/Language/Haskell/Stylish/Align.hs +++ b/lib/Language/Haskell/Stylish/Align.hs @@ -55,16 +55,21 @@ data Alignable a = Alignable -------------------------------------------------------------------------------- -- | Create changes that perform the alignment. align - :: Int -- ^ Max columns + :: Maybe Int -- ^ Max columns -> [Alignable H.SrcSpan] -- ^ Alignables -> [Change String] -- ^ Changes performing the alignment. align _ [] = [] align maxColumns alignment -- Do not make any change if we would go past the maximum number of columns. - | longestLeft + longestRight > maxColumns = [] - | not (fixable alignment) = [] - | otherwise = map align' alignment + | exceedsColumns (longestLeft + longestRight) = [] + | not (fixable alignment) = [] + | otherwise = map align' alignment where + exceedsColumns i = case maxColumns of + Nothing -> False -- No number exceeds a maximum column count of + -- Nothing, because there is no limit to exceed. + Just c -> i > c + -- The longest thing in the left column. longestLeft = maximum $ map (H.srcSpanEndColumn . aLeft) alignment diff --git a/lib/Language/Haskell/Stylish/Config.hs b/lib/Language/Haskell/Stylish/Config.hs index 8f43131..475a5e3 100644 --- a/lib/Language/Haskell/Stylish/Config.hs +++ b/lib/Language/Haskell/Stylish/Config.hs @@ -16,24 +16,29 @@ import Data.Aeson (FromJSON (..) import qualified Data.Aeson as A import qualified Data.Aeson.Types as A import qualified Data.ByteString as B +import Data.ByteString.Lazy (fromStrict) +import Data.Char (toLower) import qualified Data.FileEmbed as FileEmbed import Data.List (intercalate, nub) import Data.Map (Map) import qualified Data.Map as M import Data.Maybe (fromMaybe) -import Data.Yaml (decodeEither', - prettyPrintParseException) +import qualified Data.Text as T +import Data.YAML (prettyPosWithSource) +import Data.YAML.Aeson (decode1Strict) import System.Directory import System.FilePath ((</>)) import qualified System.IO as IO (Newline (..), nativeNewline) +import Text.Read (readMaybe) -------------------------------------------------------------------------------- import qualified Language.Haskell.Stylish.Config.Cabal as Cabal import Language.Haskell.Stylish.Config.Internal import Language.Haskell.Stylish.Step +import qualified Language.Haskell.Stylish.Step.Data as Data import qualified Language.Haskell.Stylish.Step.Imports as Imports import qualified Language.Haskell.Stylish.Step.LanguagePragmas as LanguagePragmas import qualified Language.Haskell.Stylish.Step.SimpleAlign as SimpleAlign @@ -51,7 +56,7 @@ type Extensions = [String] -------------------------------------------------------------------------------- data Config = Config { configSteps :: [Step] - , configColumns :: Int + , configColumns :: Maybe Int , configLanguageExtensions :: [String] , configNewline :: IO.Newline , configCabal :: Bool @@ -80,12 +85,10 @@ configFilePath verbose Nothing = do current <- getCurrentDirectory configPath <- getXdgDirectory XdgConfig "stylish-haskell" home <- getHomeDirectory - mbConfig <- search verbose $ + search verbose $ [d </> configFileName | d <- ancestors current] ++ [configPath </> "config.yaml", home </> configFileName] - return mbConfig - search :: Verbose -> [FilePath] -> IO (Maybe FilePath) search _ [] = return Nothing search verbose (f : fs) = do @@ -100,9 +103,8 @@ loadConfig verbose userSpecified = do mbFp <- configFilePath verbose userSpecified verbose $ "Loading configuration at " ++ fromMaybe "<embedded>" mbFp bytes <- maybe (return defaultConfigBytes) B.readFile mbFp - case decodeEither' bytes of - Left err -> error $ - "Language.Haskell.Stylish.Config.loadConfig: " ++ prettyPrintParseException err + case decode1Strict bytes of + Left (pos, err) -> error $ prettyPosWithSource pos (fromStrict bytes) ("Language.Haskell.Stylish.Config.loadConfig: " ++ err) Right config -> do cabalLanguageExtensions <- if configCabal config then map show <$> Cabal.findLanguageExtensions verbose @@ -120,7 +122,7 @@ parseConfig (A.Object o) = do -- First load the config without the actual steps config <- Config <$> pure [] - <*> (o A..:? "columns" A..!= 80) + <*> (o A..:! "columns" A..!= Just 80) <*> (o A..:? "language_extensions" A..!= []) <*> (o A..:? "newline" >>= parseEnum newlines IO.nativeNewline) <*> (o A..:? "cabal" A..!= True) @@ -142,6 +144,7 @@ parseConfig _ = mzero catalog :: Map String (Config -> A.Object -> A.Parser Step) catalog = M.fromList [ ("imports", parseImports) + , ("records", parseRecords) , ("language_pragmas", parseLanguagePragmas) , ("simple_align", parseSimpleAlign) , ("squash", parseSquash) @@ -181,6 +184,28 @@ parseSimpleAlign c o = SimpleAlign.step where withDef f k = fromMaybe (f SimpleAlign.defaultConfig) <$> (o A..:? k) +-------------------------------------------------------------------------------- +parseRecords :: Config -> A.Object -> A.Parser Step +parseRecords _ o = Data.step + <$> (Data.Config + <$> (o A..: "equals" >>= parseIndent) + <*> (o A..: "first_field" >>= parseIndent) + <*> (o A..: "field_comment") + <*> (o A..: "deriving")) + + +parseIndent :: A.Value -> A.Parser Data.Indent +parseIndent = A.withText "Indent" $ \t -> + if t == "same_line" + then return Data.SameLine + else + if "indent " `T.isPrefixOf` t + then + case readMaybe (T.unpack $ T.drop 7 t) of + Just n -> return $ Data.Indent n + Nothing -> fail $ "Indent: not a number" <> T.unpack (T.drop 7 t) + else fail $ "can't parse indent setting: " <> T.unpack t + -------------------------------------------------------------------------------- parseSquash :: Config -> A.Object -> A.Parser Step @@ -200,9 +225,9 @@ parseImports config o = Imports.step -- Note that padding has to be at least 1. Default is 4. <*> (o A..:? "empty_list_align" >>= parseEnum emptyListAligns (def Imports.emptyListAlign)) - <*> o A..:? "list_padding" A..!= (def Imports.listPadding) - <*> o A..:? "separate_lists" A..!= (def Imports.separateLists) - <*> o A..:? "space_surround" A..!= (def Imports.spaceSurround)) + <*> o A..:? "list_padding" A..!= def Imports.listPadding + <*> o A..:? "separate_lists" A..!= def Imports.separateLists + <*> o A..:? "space_surround" A..!= def Imports.spaceSurround) where def f = f Imports.defaultOptions @@ -237,8 +262,9 @@ parseLanguagePragmas :: Config -> A.Object -> A.Parser Step parseLanguagePragmas config o = LanguagePragmas.step <$> pure (configColumns config) <*> (o A..:? "style" >>= parseEnum styles LanguagePragmas.Vertical) - <*> o A..:? "align" A..!= True + <*> o A..:? "align" A..!= True <*> o A..:? "remove_redundant" A..!= True + <*> mkLanguage o where styles = [ ("vertical", LanguagePragmas.Vertical) @@ -248,6 +274,19 @@ parseLanguagePragmas config o = LanguagePragmas.step -------------------------------------------------------------------------------- +-- | Utilities for validating language prefixes +mkLanguage :: A.Object -> A.Parser String +mkLanguage o = do + lang <- o A..:? "language_prefix" + maybe (pure "LANGUAGE") validate lang + where + validate :: String -> A.Parser String + validate s + | fmap toLower s == "language" = pure s + | otherwise = fail "please provide a valid language prefix" + + +-------------------------------------------------------------------------------- parseTabs :: Config -> A.Object -> A.Parser Step parseTabs _ o = Tabs.step <$> o A..:? "spaces" A..!= 8 @@ -262,3 +301,4 @@ parseTrailingWhitespace _ _ = return TrailingWhitespace.step parseUnicodeSyntax :: Config -> A.Object -> A.Parser Step parseUnicodeSyntax _ o = UnicodeSyntax.step <$> o A..:? "add_language_pragma" A..!= True + <*> mkLanguage o diff --git a/lib/Language/Haskell/Stylish/Editor.hs b/lib/Language/Haskell/Stylish/Editor.hs index cad7e68..f71d1f6 100644 --- a/lib/Language/Haskell/Stylish/Editor.hs +++ b/lib/Language/Haskell/Stylish/Editor.hs @@ -1,3 +1,5 @@ +{-# language LambdaCase #-} + -------------------------------------------------------------------------------- -- | This module provides you with a line-based editor. It's main feature is -- that you can specify multiple changes at the same time, e.g.: @@ -19,8 +21,7 @@ module Language.Haskell.Stylish.Editor -------------------------------------------------------------------------------- -import Data.List (intercalate, sortBy) -import Data.Ord (comparing) +import Data.List (intercalate, sortOn) -------------------------------------------------------------------------------- @@ -31,7 +32,7 @@ import Language.Haskell.Stylish.Block -- | Changes the lines indicated by the 'Block' into the given 'Lines' data Change a = Change { changeBlock :: Block a - , changeLines :: ([a] -> [a]) + , changeLines :: [a] -> [a] } @@ -49,7 +50,7 @@ applyChanges changes0 intercalate ", " (map printBlock blocks) | otherwise = go 1 changes1 where - changes1 = sortBy (comparing (blockStart . changeBlock)) changes0 + changes1 = sortOn (blockStart . changeBlock) changes0 blocks = map changeBlock changes1 printBlock b = show (blockStart b) ++ "-" ++ show (blockEnd b) @@ -87,7 +88,7 @@ change = Change -------------------------------------------------------------------------------- -- | Change a single line for some other lines changeLine :: Int -> (a -> [a]) -> Change a -changeLine start f = change (Block start start) $ \xs -> case xs of +changeLine start f = change (Block start start) $ \case [] -> [] (x : _) -> f x diff --git a/lib/Language/Haskell/Stylish/Step/Data.hs b/lib/Language/Haskell/Stylish/Step/Data.hs new file mode 100644 index 0000000..1f7732b --- /dev/null +++ b/lib/Language/Haskell/Stylish/Step/Data.hs @@ -0,0 +1,126 @@ +{-# LANGUAGE RecordWildCards #-} + +module Language.Haskell.Stylish.Step.Data where + +import Data.List (find, intercalate) +import Data.Maybe (fromMaybe, maybeToList) +import qualified Language.Haskell.Exts as H +import Language.Haskell.Exts.Comments +import Language.Haskell.Stylish.Block +import Language.Haskell.Stylish.Editor +import Language.Haskell.Stylish.Step +import Language.Haskell.Stylish.Util +import Prelude hiding (init) + +data Indent + = SameLine + | Indent !Int + deriving (Show) + +data Config = Config + { cEquals :: !Indent + -- ^ Indent between type constructor and @=@ sign (measured from column 0) + , cFirstField :: !Indent + -- ^ Indent between data constructor and @{@ line (measured from column with data constructor name) + , cFieldComment :: !Int + -- ^ Indent between column with @{@ and start of field line comment (this line has @cFieldComment = 2@) + , cDeriving :: !Int + -- ^ Indent before @deriving@ lines (measured from column 0) + } deriving (Show) + +datas :: H.Module l -> [H.Decl l] +datas (H.Module _ _ _ _ decls) = decls +datas _ = [] + +type ChangeLine = Change String + +step :: Config -> Step +step cfg = makeStep "Data" (step' cfg) + +step' :: Config -> Lines -> Module -> Lines +step' cfg ls (module', allComments) = applyChanges changes ls + where + datas' = datas $ fmap linesFromSrcSpan module' + changes = datas' >>= maybeToList . changeDecl allComments cfg + +findCommentOnLine :: LineBlock -> [Comment] -> Maybe Comment +findCommentOnLine lb = find commentOnLine + where + commentOnLine (Comment _ (H.SrcSpan _ start _ end _) _) = + blockStart lb == start && blockEnd lb == end + +findCommentBelowLine :: LineBlock -> [Comment] -> Maybe Comment +findCommentBelowLine lb = find commentOnLine + where + commentOnLine (Comment _ (H.SrcSpan _ start _ end _) _) = + blockStart lb == start - 1 && blockEnd lb == end - 1 + +commentsWithin :: LineBlock -> [Comment] -> [Comment] +commentsWithin lb = filter within + where + within (Comment _ (H.SrcSpan _ start _ end _) _) = + start >= blockStart lb && end <= blockEnd lb + +changeDecl :: [Comment] -> Config -> H.Decl LineBlock -> Maybe ChangeLine +changeDecl _ _ (H.DataDecl _ (H.DataType _) Nothing _ [] _) = Nothing +changeDecl allComments cfg@Config{..} (H.DataDecl block (H.DataType _) Nothing dhead decls derivings) + | hasRecordFields = Just $ change block (const $ concat newLines) + | otherwise = Nothing + where + hasRecordFields = any + (\qual -> case qual of + (H.QualConDecl _ _ _ (H.RecDecl {})) -> True + _ -> False) + decls + + typeConstructor = "data " <> H.prettyPrint dhead + + -- In any case set @pipeIndent@ such that @|@ is aligned with @=@. + (firstLine, firstLineInit, pipeIndent) = + case cEquals of + SameLine -> (Nothing, typeConstructor <> " = ", length typeConstructor + 1) + Indent n -> (Just [[typeConstructor]], indent n "= ", n) + + newLines = fromMaybe [] firstLine ++ fmap constructors zipped <> [fmap (indent cDeriving . H.prettyPrint) derivings] + zipped = zip decls ([1..] ::[Int]) + + constructors (decl, 1) = processConstructor allComments firstLineInit cfg decl + constructors (decl, _) = processConstructor allComments (indent pipeIndent "| ") cfg decl +changeDecl _ _ _ = Nothing + +processConstructor :: [Comment] -> String -> Config -> H.QualConDecl LineBlock -> [String] +processConstructor allComments init Config{..} (H.QualConDecl _ _ _ (H.RecDecl _ dname (f:fs))) = do + fromMaybe [] firstLine <> n1 <> ns <> [indent fieldIndent "}"] + where + n1 = processName firstLinePrefix (extractField f) + ns = fs >>= processName (indent fieldIndent ", ") . extractField + + -- Set @fieldIndent@ such that @,@ is aligned with @{@. + (firstLine, firstLinePrefix, fieldIndent) = + case cFirstField of + SameLine -> + ( Nothing + , init <> H.prettyPrint dname <> " { " + , length init + length (H.prettyPrint dname) + 1 + ) + Indent n -> + ( Just [init <> H.prettyPrint dname] + , indent (length init + n) "{ " + , length init + n + ) + + processName prefix (fnames, _type, lineComment, commentBelowLine) = + [prefix <> intercalate ", " (fmap H.prettyPrint fnames) <> " :: " <> H.prettyPrint _type <> addLineComment lineComment + ] ++ addCommentBelow commentBelowLine + + addLineComment (Just (Comment _ _ c)) = " --" <> c + addLineComment Nothing = "" + + -- Field comment indent is measured from the column with @{@, hence adding of @fieldIndent@ here. + addCommentBelow Nothing = [] + addCommentBelow (Just (Comment _ _ c)) = [indent (fieldIndent + cFieldComment) "--" <> c] + + extractField (H.FieldDecl lb names _type) = + (names, _type, findCommentOnLine lb allComments, findCommentBelowLine lb allComments) + +processConstructor _ init _ decl = [init <> trimLeft (H.prettyPrint decl)] diff --git a/lib/Language/Haskell/Stylish/Step/Imports.hs b/lib/Language/Haskell/Stylish/Step/Imports.hs index 4ceb802..7cb78d4 100644 --- a/lib/Language/Haskell/Stylish/Step/Imports.hs +++ b/lib/Language/Haskell/Stylish/Step/Imports.hs @@ -258,7 +258,7 @@ prettyImportSpec separate = prettyImportSpec' -------------------------------------------------------------------------------- prettyImport :: (Ord l, Show l) => - Int -> Options -> Bool -> Bool -> Int -> H.ImportDecl l -> [String] + Maybe Int -> Options -> Bool -> Bool -> Int -> H.ImportDecl l -> [String] prettyImport columns Options{..} padQualified padName longest imp | (void `fmap` H.importSpecs imp) == emptyImportSpec = emptyWrap | otherwise = case longListAlign of @@ -277,7 +277,7 @@ prettyImport columns Options{..} padQualified padName longest imp longListWrapper shortWrap longWrap | listAlign == NewLine || length shortWrap > 1 - || length (head shortWrap) > columns + || exceedsColumns (length (head shortWrap)) = longWrap | otherwise = shortWrap @@ -292,14 +292,14 @@ prettyImport columns Options{..} padQualified padName longest imp . withLast (++ (maybeSpace ++ ")")) inlineWrapper = case listAlign of - NewLine -> (paddedNoSpecBase :) . wrapRest columns listPadding' - WithModuleName -> wrap columns paddedBase (withModuleNameBaseLength + 4) - WithAlias -> wrap columns paddedBase (inlineBaseLength + 1) + NewLine -> (paddedNoSpecBase :) . wrapRestMaybe columns listPadding' + WithModuleName -> wrapMaybe columns paddedBase (withModuleNameBaseLength + 4) + WithAlias -> wrapMaybe columns paddedBase (inlineBaseLength + 1) -- Add 1 extra space to ensure same padding as in original code. AfterAlias -> withTail ((' ' : maybeSpace) ++) - . wrap columns paddedBase (afterAliasBaseLength + 1) + . wrapMaybe columns paddedBase (afterAliasBaseLength + 1) - inlineWithBreakWrap = paddedNoSpecBase : wrapRest columns listPadding' + inlineWithBreakWrap = paddedNoSpecBase : wrapRestMaybe columns listPadding' ( mapSpecs $ withInit (++ ",") . withHead (("(" ++ maybeSpace) ++) @@ -307,7 +307,7 @@ prettyImport columns Options{..} padQualified padName longest imp inlineToMultilineWrap | length inlineWithBreakWrap > 2 - || any ((> columns) . length) (tail inlineWithBreakWrap) + || any (exceedsColumns . length) (tail inlineWithBreakWrap) = multilineWrap | otherwise = inlineWithBreakWrap @@ -389,9 +389,14 @@ prettyImport columns Options{..} padQualified padName longest imp True -> " " False -> "" + exceedsColumns i = case columns of + Nothing -> False -- No number exceeds a maximum column count of + -- Nothing, because there is no limit to exceed. + Just c -> i > c + -------------------------------------------------------------------------------- -prettyImportGroup :: Int -> Options -> Bool -> Int +prettyImportGroup :: Maybe Int -> Options -> Bool -> Int -> [H.ImportDecl LineBlock] -> Lines prettyImportGroup columns align fileAlign longest imps = @@ -415,12 +420,12 @@ prettyImportGroup columns align fileAlign longest imps = -------------------------------------------------------------------------------- -step :: Int -> Options -> Step +step :: Maybe Int -> Options -> Step step columns = makeStep "Imports" . step' columns -------------------------------------------------------------------------------- -step' :: Int -> Options -> Lines -> Module -> Lines +step' :: Maybe Int -> Options -> Lines -> Module -> Lines step' columns align ls (module', _) = applyChanges [ change block $ const $ prettyImportGroup columns align fileAlign longest importGroup diff --git a/lib/Language/Haskell/Stylish/Step/LanguagePragmas.hs b/lib/Language/Haskell/Stylish/Step/LanguagePragmas.hs index cdedfa8..c9d461f 100644 --- a/lib/Language/Haskell/Stylish/Step/LanguagePragmas.hs +++ b/lib/Language/Haskell/Stylish/Step/LanguagePragmas.hs @@ -2,7 +2,6 @@ module Language.Haskell.Stylish.Step.LanguagePragmas ( Style (..) , step - -- * Utilities , addLanguagePragma ) where @@ -42,9 +41,9 @@ firstLocation = minimum . map (blockStart . fst) -------------------------------------------------------------------------------- -verticalPragmas :: Int -> Bool -> [String] -> Lines -verticalPragmas longest align pragmas' = - [ "{-# LANGUAGE " ++ pad pragma ++ " #-}" +verticalPragmas :: String -> Int -> Bool -> [String] -> Lines +verticalPragmas lg longest align pragmas' = + [ "{-# " ++ lg ++ " " ++ pad pragma ++ " #-}" | pragma <- pragmas' ] where @@ -54,27 +53,23 @@ verticalPragmas longest align pragmas' = -------------------------------------------------------------------------------- -compactPragmas :: Int -> [String] -> Lines -compactPragmas columns pragmas' = wrap columns "{-# LANGUAGE" 13 $ +compactPragmas :: String -> Maybe Int -> [String] -> Lines +compactPragmas lg columns pragmas' = wrapMaybe columns ("{-# " ++ lg) 13 $ map (++ ",") (init pragmas') ++ [last pragmas' ++ " #-}"] -------------------------------------------------------------------------------- -compactLinePragmas :: Int -> Bool -> [String] -> Lines -compactLinePragmas _ _ [] = [] -compactLinePragmas columns align pragmas' = map (wrapLanguage . pad) prags +compactLinePragmas :: String -> Maybe Int -> Bool -> [String] -> Lines +compactLinePragmas _ _ _ [] = [] +compactLinePragmas lg columns align pragmas' = map (wrapLanguage . pad) prags where - wrapLanguage ps = "{-# LANGUAGE" ++ ps ++ " #-}" - - maxWidth = columns - 16 - + wrapLanguage ps = "{-# " ++ lg ++ ps ++ " #-}" + maxWidth = fmap (\c -> c - 16) columns longest = maximum $ map length prags - pad | align = padRight longest | otherwise = id - - prags = map truncateComma $ wrap maxWidth "" 1 $ + prags = map truncateComma $ wrapMaybe maxWidth "" 1 $ map (++ ",") (init pragmas') ++ [last pragmas'] @@ -87,10 +82,10 @@ truncateComma xs -------------------------------------------------------------------------------- -prettyPragmas :: Int -> Int -> Bool -> Style -> [String] -> Lines -prettyPragmas _ longest align Vertical = verticalPragmas longest align -prettyPragmas cols _ _ Compact = compactPragmas cols -prettyPragmas cols _ align CompactLine = compactLinePragmas cols align +prettyPragmas :: String -> Maybe Int -> Int -> Bool -> Style -> [String] -> Lines +prettyPragmas lp _ longest align Vertical = verticalPragmas lp longest align +prettyPragmas lp cols _ _ Compact = compactPragmas lp cols +prettyPragmas lp cols _ align CompactLine = compactLinePragmas lp cols align -------------------------------------------------------------------------------- @@ -110,35 +105,34 @@ filterRedundant isRedundant' = snd . foldr filterRedundant' (S.empty, []) known' = xs' `S.union` known -------------------------------------------------------------------------------- -step :: Int -> Style -> Bool -> Bool -> Step -step = (((makeStep "LanguagePragmas" .) .) .) . step' +step :: Maybe Int -> Style -> Bool -> Bool -> String -> Step +step = ((((makeStep "LanguagePragmas" .) .) .) .) . step' -------------------------------------------------------------------------------- -step' :: Int -> Style -> Bool -> Bool -> Lines -> Module -> Lines -step' columns style align removeRedundant ls (module', _) +step' :: Maybe Int -> Style -> Bool -> Bool -> String -> Lines -> Module -> Lines +step' columns style align removeRedundant lngPrefix ls (module', _) | null pragmas' = ls | otherwise = applyChanges changes ls where isRedundant' | removeRedundant = isRedundant module' | otherwise = const False - pragmas' = pragmas $ fmap linesFromSrcSpan module' longest = maximum $ map length $ snd =<< pragmas' groups = [(b, concat pgs) | (b, pgs) <- groupAdjacent pragmas'] changes = - [ change b (const $ prettyPragmas columns longest align style pg) + [ change b (const $ prettyPragmas lngPrefix columns longest align style pg) | (b, pg) <- filterRedundant isRedundant' groups ] -------------------------------------------------------------------------------- -- | Add a LANGUAGE pragma to a module if it is not present already. -addLanguagePragma :: String -> H.Module H.SrcSpanInfo -> [Change String] -addLanguagePragma prag modu +addLanguagePragma :: String -> String -> H.Module H.SrcSpanInfo -> [Change String] +addLanguagePragma lg prag modu | prag `elem` present = [] - | otherwise = [insert line ["{-# LANGUAGE " ++ prag ++ " #-}"]] + | otherwise = [insert line ["{-# " ++ lg ++ " " ++ prag ++ " #-}"]] where pragmas' = pragmas (fmap linesFromSrcSpan modu) present = concatMap snd pragmas' @@ -158,7 +152,7 @@ isRedundant _ _ = False -- | Check if the ViewPatterns language pragma is redundant. isRedundantViewPatterns :: H.Module H.SrcSpanInfo -> Bool isRedundantViewPatterns m = null - [() | H.PViewPat _ _ _ <- everything m :: [H.Pat H.SrcSpanInfo]] + [() | H.PViewPat {} <- everything m :: [H.Pat H.SrcSpanInfo]] -------------------------------------------------------------------------------- diff --git a/lib/Language/Haskell/Stylish/Step/SimpleAlign.hs b/lib/Language/Haskell/Stylish/Step/SimpleAlign.hs index 924d6c5..5e61123 100644 --- a/lib/Language/Haskell/Stylish/Step/SimpleAlign.hs +++ b/lib/Language/Haskell/Stylish/Step/SimpleAlign.hs @@ -108,7 +108,7 @@ fieldDeclToAlignable (H.FieldDecl ann names ty) = Just $ Alignable -------------------------------------------------------------------------------- -step :: Int -> Config -> Step +step :: Maybe Int -> Config -> Step step maxColumns config = makeStep "Cases" $ \ls (module', _) -> let module'' = fmap H.srcInfoSpan module' changes search toAlign = diff --git a/lib/Language/Haskell/Stylish/Step/UnicodeSyntax.hs b/lib/Language/Haskell/Stylish/Step/UnicodeSyntax.hs index 01e29e8..266e8e5 100644 --- a/lib/Language/Haskell/Stylish/Step/UnicodeSyntax.hs +++ b/lib/Language/Haskell/Stylish/Step/UnicodeSyntax.hs @@ -39,12 +39,12 @@ replaceAll :: [(Int, [(Int, String)])] -> [Change String] replaceAll = map changeLine' where changeLine' (r, ns) = changeLine r $ \str -> return $ - flip applyChanges str + applyChanges [ change (Block c ec) (const repl) | (c, needle) <- sort ns , let ec = c + length needle - 1 , repl <- maybeToList $ M.lookup needle unicodeReplacements - ] + ] str -------------------------------------------------------------------------------- @@ -104,15 +104,15 @@ between (startRow, startCol) (endRow, endCol) needle = -------------------------------------------------------------------------------- -step :: Bool -> Step -step = makeStep "UnicodeSyntax" . step' +step :: Bool -> String -> Step +step = (makeStep "UnicodeSyntax" .) . step' -------------------------------------------------------------------------------- -step' :: Bool -> Lines -> Module -> Lines -step' alp ls (module', _) = applyChanges changes ls +step' :: Bool -> String -> Lines -> Module -> Lines +step' alp lg ls (module', _) = applyChanges changes ls where - changes = (if alp then addLanguagePragma "UnicodeSyntax" module' else []) ++ + changes = (if alp then addLanguagePragma lg "UnicodeSyntax" module' else []) ++ replaceAll perLine perLine = sort $ groupPerLine $ typeSigs module' ls ++ diff --git a/lib/Language/Haskell/Stylish/Util.hs b/lib/Language/Haskell/Stylish/Util.hs index c634043..9883f4b 100644 --- a/lib/Language/Haskell/Stylish/Util.hs +++ b/lib/Language/Haskell/Stylish/Util.hs @@ -10,6 +10,8 @@ module Language.Haskell.Stylish.Util , trimRight , wrap , wrapRest + , wrapMaybe + , wrapRestMaybe , withHead , withInit @@ -99,6 +101,27 @@ wrap maxWidth leading ind = wrap' leading -------------------------------------------------------------------------------- +wrapMaybe :: Maybe Int -- ^ Maximum line width (maybe) + -> String -- ^ Leading string + -> Int -- ^ Indentation + -> [String] -- ^ Strings to add/wrap + -> Lines -- ^ Resulting lines +wrapMaybe (Just maxWidth) = wrap maxWidth +wrapMaybe Nothing = noWrap + + +-------------------------------------------------------------------------------- +noWrap :: String -- ^ Leading string + -> Int -- ^ Indentation + -> [String] -- ^ Strings to add + -> Lines -- ^ Resulting lines +noWrap leading _ind = noWrap' leading + where + noWrap' ss [] = [ss] + noWrap' ss (str:strs) = noWrap' (ss ++ " " ++ str) strs + + +-------------------------------------------------------------------------------- wrapRest :: Int -> Int -> [String] @@ -117,6 +140,29 @@ wrapRest maxWidth ind = reverse . wrapRest' [] "" -------------------------------------------------------------------------------- +wrapRestMaybe :: Maybe Int + -> Int + -> [String] + -> Lines +wrapRestMaybe (Just maxWidth) = wrapRest maxWidth +wrapRestMaybe Nothing = noWrapRest + + +-------------------------------------------------------------------------------- +noWrapRest :: Int + -> [String] + -> Lines +noWrapRest ind = reverse . noWrapRest' [] "" + where + noWrapRest' ls ss [] + | null ss = ls + | otherwise = ss:ls + noWrapRest' ls ss (str:strs) + | null ss = noWrapRest' ls (indent ind str) strs + | otherwise = noWrapRest' ls (ss ++ " " ++ str) strs + + +-------------------------------------------------------------------------------- withHead :: (a -> a) -> [a] -> [a] withHead _ [] = [] withHead f (x : xs) = f x : xs diff --git a/src/Main.hs b/src/Main.hs index e71c795..b1ca2d5 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -21,13 +21,14 @@ import Language.Haskell.Stylish -------------------------------------------------------------------------------- data StylishArgs = StylishArgs - { saVersion :: Bool - , saConfig :: Maybe FilePath - , saVerbose :: Bool - , saDefaults :: Bool - , saInPlace :: Bool - , saNoUtf8 :: Bool - , saFiles :: [FilePath] + { saVersion :: Bool + , saConfig :: Maybe FilePath + , saRecursive :: Bool + , saVerbose :: Bool + , saDefaults :: Bool + , saInPlace :: Bool + , saNoUtf8 :: Bool + , saFiles :: [FilePath] } deriving (Show) @@ -45,6 +46,11 @@ parseStylishArgs = StylishArgs OA.short 'c' <> OA.hidden) <*> OA.switch ( + OA.help "Recursive file search" <> + OA.long "recursive" <> + OA.short 'r' <> + OA.hidden) + <*> OA.switch ( OA.help "Run in verbose mode" <> OA.long "verbose" <> OA.short 'v' <> @@ -99,14 +105,20 @@ stylishHaskell sa = do else do conf <- loadConfig verbose' (saConfig sa) + filesR <- case (saRecursive sa) of + True -> findHaskellFiles (saVerbose sa) (saFiles sa) + _ -> return $ saFiles sa let steps = configSteps conf forM_ steps $ \s -> verbose' $ "Enabled " ++ stepName s ++ " step" verbose' $ "Extra language extensions: " ++ show (configLanguageExtensions conf) - mapM_ (file sa conf) files' + mapM_ (file sa conf) $ files' filesR where verbose' = makeVerbose (saVerbose sa) - files' = if null (saFiles sa) then [Nothing] else map Just (saFiles sa) + files' x = case (saRecursive sa, null x) of + (True,True) -> [] -- No file to format and recursive enabled. + (_,True) -> [Nothing] -- Involving IO.stdin. + (_,False) -> map Just x -- Process available files. -------------------------------------------------------------------------------- @@ -1,7 +1,9 @@ -resolver: lts-14.6 +resolver: lts-14.20 packages: - '.' extra-deps: - 'Cabal-3.0.0.0' -- 'haskell-src-exts-1.22.0' +- 'haskell-src-exts-1.23.0' +- 'HsYAML-0.2.1.0' +- 'HsYAML-aeson-0.2.0.0' diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..bc43b4e --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,40 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: +- completed: + hackage: Cabal-3.0.0.0@sha256:1ba37b8d80e89213b17db7b8b9ea0108da55ca65f8c0cbb7433881a284c5cf67,26027 + pantry-tree: + size: 71616 + sha256: 4f16f0a65304ab22f01cb7f6d25db2f15a168f4cefacde7864cb1e02eb3ea867 + original: + hackage: Cabal-3.0.0.0 +- completed: + hackage: haskell-src-exts-1.23.0@sha256:1bb9f7e97d569e56973133cb075fdcc1bfd11f90d94b035b5cf44814bb39a73d,4541 + pantry-tree: + size: 97804 + sha256: 8e5bc885533431db9bf75e9609f6b80b56ab0c289a903d701f8628e78322afd0 + original: + hackage: haskell-src-exts-1.23.0 +- completed: + hackage: HsYAML-0.2.1.0@sha256:e4677daeba57f7a1e9a709a1f3022fe937336c91513e893166bd1f023f530d68,5311 + pantry-tree: + size: 1340 + sha256: 21f61bf9cad31674126b106071dd9b852e408796aeffc90eec1792f784107eff + original: + hackage: HsYAML-0.2.1.0 +- completed: + hackage: HsYAML-aeson-0.2.0.0@sha256:04796abfc01cffded83f37a10e6edba4f0c0a15d45bef44fc5bb4313d9c87757,1791 + pantry-tree: + size: 234 + sha256: 67cc9ba17c79e71d3abdb465a3ee2825477856fff3b8b7d543cbbbefdae9a9d9 + original: + hackage: HsYAML-aeson-0.2.0.0 +snapshots: +- completed: + size: 524154 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/20.yaml + sha256: 2f5099f69ddb6abfe64400fe1e6a604e8e628f55e6837211cd70a81eb0a8fa4d + original: lts-14.20 diff --git a/stylish-haskell.cabal b/stylish-haskell.cabal index a0b1479..8e9dffd 100644 --- a/stylish-haskell.cabal +++ b/stylish-haskell.cabal @@ -1,5 +1,5 @@ Name: stylish-haskell -Version: 0.9.4.4 +Version: 0.11.0.0 Synopsis: Haskell code prettifier Homepage: https://github.com/jaspervdj/stylish-haskell License: BSD3 @@ -29,6 +29,7 @@ Library Exposed-modules: Language.Haskell.Stylish + Language.Haskell.Stylish.Step.Data Language.Haskell.Stylish.Step.Imports Language.Haskell.Stylish.Step.LanguagePragmas Language.Haskell.Stylish.Step.SimpleAlign @@ -59,11 +60,13 @@ Library directory >= 1.2.3 && < 1.4, filepath >= 1.1 && < 1.5, file-embed >= 0.0.10 && < 0.1, - haskell-src-exts >= 1.18 && < 1.23, + haskell-src-exts >= 1.18 && < 1.24, mtl >= 2.0 && < 2.3, semigroups >= 0.18 && < 0.20, syb >= 0.3 && < 0.8, - yaml >= 0.8.11 && < 0.12 + text >= 1.2 && < 1.3, + HsYAML-aeson >=0.2.0 && < 0.3, + HsYAML >=0.2.0 && < 0.3 Executable stylish-haskell Ghc-options: -Wall @@ -83,10 +86,11 @@ Executable stylish-haskell directory >= 1.2.3 && < 1.4, filepath >= 1.1 && < 1.5, file-embed >= 0.0.10 && < 0.1, - haskell-src-exts >= 1.18 && < 1.23, + haskell-src-exts >= 1.18 && < 1.24, mtl >= 2.0 && < 2.3, syb >= 0.3 && < 0.8, - yaml >= 0.8.11 && < 0.12 + HsYAML-aeson >=0.2.0 && < 0.3, + HsYAML >=0.2.0 && < 0.3 Test-suite stylish-haskell-tests Ghc-options: -Wall @@ -95,6 +99,7 @@ Test-suite stylish-haskell-tests Type: exitcode-stdio-1.0 Other-modules: + Language.Haskell.Stylish Language.Haskell.Stylish.Align Language.Haskell.Stylish.Block Language.Haskell.Stylish.Config @@ -107,6 +112,8 @@ Test-suite stylish-haskell-tests Language.Haskell.Stylish.Step Language.Haskell.Stylish.Step.Imports Language.Haskell.Stylish.Step.Imports.Tests + Language.Haskell.Stylish.Step.Data + Language.Haskell.Stylish.Step.Data.Tests Language.Haskell.Stylish.Step.LanguagePragmas Language.Haskell.Stylish.Step.LanguagePragmas.Tests Language.Haskell.Stylish.Step.SimpleAlign @@ -119,9 +126,11 @@ Test-suite stylish-haskell-tests Language.Haskell.Stylish.Step.TrailingWhitespace.Tests Language.Haskell.Stylish.Step.UnicodeSyntax Language.Haskell.Stylish.Step.UnicodeSyntax.Tests + Language.Haskell.Stylish.Tests Language.Haskell.Stylish.Tests.Util Language.Haskell.Stylish.Util Language.Haskell.Stylish.Verbose + Paths_stylish_haskell Build-depends: HUnit >= 1.2 && < 1.7, @@ -137,10 +146,12 @@ Test-suite stylish-haskell-tests directory >= 1.2.3 && < 1.4, filepath >= 1.1 && < 1.5, file-embed >= 0.0.10 && < 0.1, - haskell-src-exts >= 1.18 && < 1.23, + haskell-src-exts >= 1.18 && < 1.24, mtl >= 2.0 && < 2.3, syb >= 0.3 && < 0.8, - yaml >= 0.8.11 && < 0.12 + text >= 1.2 && < 1.3, + HsYAML-aeson >=0.2.0 && < 0.3, + HsYAML >=0.2.0 && < 0.3 Source-repository head Type: git diff --git a/tests/Language/Haskell/Stylish/Config/Tests.hs b/tests/Language/Haskell/Stylish/Config/Tests.hs index f62b571..a8b2ee2 100644 --- a/tests/Language/Haskell/Stylish/Config/Tests.hs +++ b/tests/Language/Haskell/Stylish/Config/Tests.hs @@ -4,17 +4,17 @@ module Language.Haskell.Stylish.Config.Tests -------------------------------------------------------------------------------- -import Control.Exception hiding (assert) import qualified Data.Set as Set import System.Directory -import System.FilePath ((</>)) -import System.IO.Error -import System.Random import Test.Framework (Test, testGroup) import Test.Framework.Providers.HUnit (testCase) import Test.HUnit (Assertion, assert) + + -------------------------------------------------------------------------------- import Language.Haskell.Stylish.Config +import Language.Haskell.Stylish.Tests.Util + -------------------------------------------------------------------------------- tests :: Test @@ -25,38 +25,20 @@ tests = testGroup "Language.Haskell.Stylish.Config" testExtensionsFromDotStylish , testCase "Extensions extracted correctly from .stylish-haskell.yaml and .cabal files" testExtensionsFromBoth + , testCase "Correctly read .stylish-haskell.yaml file with default max column number" + testDefaultColumns + , testCase "Correctly read .stylish-haskell.yaml file with specified max column number" + testSpecifiedColumns + , testCase "Correctly read .stylish-haskell.yaml file with no max column number" + testNoColumns ] --------------------------------------------------------------------------------- --- | Create a temporary directory with a randomised name built from the template provided -createTempDirectory :: String -> IO FilePath -createTempDirectory template = do - tmpRootDir <- getTemporaryDirectory - dirId <- randomIO :: IO Word - findTempName tmpRootDir dirId - where - findTempName :: FilePath -> Word -> IO FilePath - findTempName tmpRootDir x = do - let dirpath = tmpRootDir </> template ++ show x - r <- try $ createDirectory dirpath - case r of - Right _ -> return dirpath - Left e | isAlreadyExistsError e -> findTempName tmpRootDir (x+1) - | otherwise -> ioError e - --- | Perform an action inside a temporary directory tree and purge the tree afterwords -withTestDirTree :: IO a -> IO a -withTestDirTree action = bracket - ((,) <$> getCurrentDirectory <*> createTempDirectory "stylish_haskell") - (\(current, temp) -> - setCurrentDirectory current *> - removeDirectoryRecursive temp) - (\(_, temp) -> setCurrentDirectory temp *> action) +-------------------------------------------------------------------------------- -- | Put an example config files (.cabal/.stylish-haskell.yaml/both) -- into the current directory and extract extensions from it. -createFilesAndGetExtensions :: [(FilePath, String)] -> IO Extensions -createFilesAndGetExtensions files = withTestDirTree $ do +createFilesAndGetConfig :: [(FilePath, String)] -> IO Config +createFilesAndGetConfig files = withTestDirTree $ do mapM_ (\(k, v) -> writeFile k v) files -- create an empty directory and change into it createDirectory "src" @@ -64,34 +46,65 @@ createFilesAndGetExtensions files = withTestDirTree $ do -- from that directory read the config file and extract extensions -- to make sure the search for .cabal file works config <- loadConfig (const (pure ())) Nothing - pure $ configLanguageExtensions config + pure config + -------------------------------------------------------------------------------- testExtensionsFromDotCabal :: Assertion testExtensionsFromDotCabal = - assert $ (expected ==) . Set.fromList <$> - createFilesAndGetExtensions [("test.cabal", dotCabal True)] + assert $ (expected ==) . Set.fromList . configLanguageExtensions <$> + createFilesAndGetConfig [("test.cabal", dotCabal True)] where expected = Set.fromList ["ScopedTypeVariables", "DataKinds"] + -------------------------------------------------------------------------------- testExtensionsFromDotStylish :: Assertion testExtensionsFromDotStylish = - assert $ (expected ==) . Set.fromList <$> - createFilesAndGetExtensions [(".stylish-haskell.yaml", dotStylish)] + assert $ (expected ==) . Set.fromList . configLanguageExtensions <$> + createFilesAndGetConfig [(".stylish-haskell.yaml", dotStylish)] where expected = Set.fromList ["TemplateHaskell", "QuasiQuotes"] + -------------------------------------------------------------------------------- testExtensionsFromBoth :: Assertion testExtensionsFromBoth = - assert $ (expected ==) . Set.fromList <$> - createFilesAndGetExtensions [ ("test.cabal", dotCabal True) - , (".stylish-haskell.yaml", dotStylish)] + assert $ (expected ==) . Set.fromList . configLanguageExtensions <$> + createFilesAndGetConfig [ ("test.cabal", dotCabal True) + , (".stylish-haskell.yaml", dotStylish)] where expected = Set.fromList ["ScopedTypeVariables", "DataKinds", "TemplateHaskell", "QuasiQuotes"] + +-------------------------------------------------------------------------------- +testSpecifiedColumns :: Assertion +testSpecifiedColumns = + assert $ (expected ==) . configColumns <$> + createFilesAndGetConfig [(".stylish-haskell.yaml", dotStylish)] + where + expected = Just 110 + + +-------------------------------------------------------------------------------- +testDefaultColumns :: Assertion +testDefaultColumns = + assert $ (expected ==) . configColumns <$> + createFilesAndGetConfig [(".stylish-haskell.yaml", dotStylish2)] + where + expected = Just 80 + + +-------------------------------------------------------------------------------- +testNoColumns :: Assertion +testNoColumns = + assert $ (expected ==) . configColumns <$> + createFilesAndGetConfig [(".stylish-haskell.yaml", dotStylish3)] + where + expected = Nothing + + -- | Example cabal file borrowed from -- https://www.haskell.org/cabal/users-guide/developing-packages.html -- with some default-extensions added @@ -135,8 +148,52 @@ dotStylish = unlines $ , " align: false" , " remove_redundant: true" , " - trailing_whitespace: {}" + , " - records:" + , " equals: \"same_line\"" + , " first_field: \"indent 2\"" + , " field_comment: 2" + , " deriving: 4" , "columns: 110" , "language_extensions:" , " - TemplateHaskell" , " - QuasiQuotes" ] + +-- | Example .stylish-haskell.yaml +dotStylish2 :: String +dotStylish2 = unlines $ + [ "steps:" + , " - imports:" + , " align: none" + , " list_align: after_alias" + , " long_list_align: inline" + , " separate_lists: true" + , " - language_pragmas:" + , " style: vertical" + , " align: false" + , " remove_redundant: true" + , " - trailing_whitespace: {}" + , "language_extensions:" + , " - TemplateHaskell" + , " - QuasiQuotes" + ] + +-- | Example .stylish-haskell.yaml +dotStylish3 :: String +dotStylish3 = unlines $ + [ "steps:" + , " - imports:" + , " align: none" + , " list_align: after_alias" + , " long_list_align: inline" + , " separate_lists: true" + , " - language_pragmas:" + , " style: vertical" + , " align: false" + , " remove_redundant: true" + , " - trailing_whitespace: {}" + , "columns: null" + , "language_extensions:" + , " - TemplateHaskell" + , " - QuasiQuotes" + ] diff --git a/tests/Language/Haskell/Stylish/Step/Data/Tests.hs b/tests/Language/Haskell/Stylish/Step/Data/Tests.hs new file mode 100644 index 0000000..b43e6dc --- /dev/null +++ b/tests/Language/Haskell/Stylish/Step/Data/Tests.hs @@ -0,0 +1,536 @@ +module Language.Haskell.Stylish.Step.Data.Tests + ( tests + ) where + +import Language.Haskell.Stylish.Step.Data +import Language.Haskell.Stylish.Tests.Util (testStep) +import Test.Framework (Test, testGroup) +import Test.Framework.Providers.HUnit (testCase) +import Test.HUnit (Assertion, (@=?)) + +tests :: Test +tests = testGroup "Language.Haskell.Stylish.Step.Data.Tests" + [ testCase "case 00" case00 + , testCase "case 01" case01 + , testCase "case 02" case02 + , testCase "case 03" case03 + , testCase "case 04" case04 + , testCase "case 05" case05 + , testCase "case 06" case06 + , testCase "case 07" case07 + , testCase "case 08" case08 + , testCase "case 09" case09 + , testCase "case 10" case10 + , testCase "case 11" case11 + , testCase "case 12" case12 + , testCase "case 13" case13 + , testCase "case 14" case14 + , testCase "case 15" case15 + , testCase "case 16" case16 + , testCase "case 17" case17 + , testCase "case 18" case18 + , testCase "case 19" case19 + , testCase "case 20 (issue 262)" case20 + , testCase "case 21" case21 + , testCase "case 22" case22 + , testCase "case 23" case23 + , testCase "case 24" case24 + ] + +case00 :: Assertion +case00 = expected @=? testStep (step sameSameStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo" + ] + + expected = input + +case01 :: Assertion +case01 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo = Foo { a :: Int }" + ] + + expected = unlines + [ "module Herp where" + , "" + , "data Foo" + , " = Foo" + , " { a :: Int" + , " }" + ] + +case02 :: Assertion +case02 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo = Foo { a :: Int, a2 :: String }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo" + , " = Foo" + , " { a :: Int" + , " , a2 :: String" + , " }" + ] + +case03 :: Assertion +case03 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo a = Foo { a :: a, a2 :: String }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo a" + , " = Foo" + , " { a :: a" + , " , a2 :: String" + , " }" + ] + +case04 :: Assertion +case04 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo a = Foo { a :: a, a2 :: String } | Bar { b :: a }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo a" + , " = Foo" + , " { a :: a" + , " , a2 :: String" + , " }" + , " | Bar" + , " { b :: a" + , " }" + ] + +case05 :: Assertion +case05 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo = Foo {" + , " a :: Int" + , " , a2 :: String" + , " }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo" + , " = Foo" + , " { a :: Int" + , " , a2 :: String" + , " }" + ] + +case06 :: Assertion +case06 = expected @=? testStep (step sameSameStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo = Foo Int String" + ] + expected = input + +case07 :: Assertion +case07 = expected @=? testStep (step sameSameStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Phantom a = Phantom" + ] + expected = input + +case08 :: Assertion +case08 = input @=? testStep (step sameSameStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Phantom a =" + , " Phantom" + ] + +case09 :: Assertion +case09 = expected @=? testStep (step indentIndentStyle4) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo a b = Foo { a :: a, a2 :: String } | Bar { b :: a, c:: b }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo a b" + , " = Foo" + , " { a :: a" + , " , a2 :: String" + , " }" + , " | Bar" + , " { b :: a" + , " , c :: b" + , " }" + ] + +case10 :: Assertion +case10 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo = Foo { a :: Int } deriving (Eq, Generic) deriving (Show)" + ] + + expected = unlines + [ "module Herp where" + , "" + , "data Foo" + , " = Foo" + , " { a :: Int" + , " }" + , " deriving (Eq, Generic)" + , " deriving (Show)" + ] + +case11 :: Assertion +case11 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "{-# LANGUAGE DerivingStrategies #-}" + , "module Herp where" + , "" + , "data Foo = Foo { a :: Int } deriving stock (Show)" + ] + + expected = unlines + [ "{-# LANGUAGE DerivingStrategies #-}" + , "module Herp where" + , "" + , "data Foo" + , " = Foo" + , " { a :: Int" + , " }" + , " deriving stock (Show)" + ] + +case12 :: Assertion +case12 = expected @=? testStep (step indentIndentStyle4) input + where + input = unlines + [ "module Herp where" + , "" + , "data Point = Point { pointX, pointY :: Double , pointName :: String} deriving (Show)" + ] + + expected = unlines + [ "module Herp where" + , "" + , "data Point" + , " = Point" + , " { pointX, pointY :: Double" + , " , pointName :: String" + , " }" + , " deriving (Show)" + ] + +case13 :: Assertion +case13 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "-- this is a comment" + , "data Foo = Foo { a :: Int }" + ] + expected = unlines + [ "module Herp where" + , "" + , "-- this is a comment" + , "data Foo" + , " = Foo" + , " { a :: Int" + , " }" + ] + +case14 :: Assertion +case14 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "{- this is" + , " a comment -}" + , "data Foo = Foo { a :: Int }" + ] + expected = unlines + [ "module Herp where" + , "" + , "{- this is" + , " a comment -}" + , "data Foo" + , " = Foo" + , " { a :: Int" + , " }" + ] + +case15 :: Assertion +case15 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo a = Foo" + , " { a :: a, -- comment" + , " a2 :: String" + , " }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo a" + , " = Foo" + , " { a :: a -- comment" + , " , a2 :: String" + , " }" + ] + +case16 :: Assertion +case16 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo = Foo {" + , " a :: Int -- ^ comment" + , " }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo" + , " = Foo" + , " { a :: Int -- ^ comment" + , " }" + ] + +case17 :: Assertion +case17 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo a = Foo" + , " { a :: a," + , "-- comment" + , " a2 :: String" + , " }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo a" + , " = Foo" + , " { a :: a" + , " -- comment" + , " , a2 :: String" + , " }" + ] + +case18 :: Assertion +case18 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo a = Foo" + , " { a :: a," + , "-- ^ comment" + , " a2 :: String" + , " }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo a" + , " = Foo" + , " { a :: a" + , " -- ^ comment" + , " , a2 :: String" + , " }" + ] + +case19 :: Assertion +case19 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Foo a = Foo" + , " { firstName, lastName :: String," + , "-- ^ names" + , " age :: Int" + , " }" + ] + expected = unlines + [ "module Herp where" + , "" + , "data Foo a" + , " = Foo" + , " { firstName, lastName :: String" + , " -- ^ names" + , " , age :: Int" + , " }" + ] + +-- | Should not break Enums (data without records) formatting +-- +-- See https://github.com/jaspervdj/stylish-haskell/issues/262 +case20 :: Assertion +case20 = input @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "module Herp where" + , "" + , "data Tag = Title | Text deriving (Eq, Show)" + ] + +case21 :: Assertion +case21 = expected @=? testStep (step sameSameStyle) input + where + input = unlines + [ "data Foo a" + , " = Foo { a :: Int," + , " a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar { b :: a } deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + + expected = unlines + [ "data Foo a = Foo { a :: Int" + , " , a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar { b :: a" + , " }" + , " deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + +case22 :: Assertion +case22 = expected @=? testStep (step sameIndentStyle) input + where + input = unlines + [ "data Foo a" + , " = Foo { a :: Int," + , " a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar { b :: a } deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + + expected = unlines + [ "data Foo a = Foo" + , " { a :: Int" + , " , a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar" + , " { b :: a" + , " }" + , " deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + +case23 :: Assertion +case23 = expected @=? testStep (step indentSameStyle) input + where + input = unlines + [ "data Foo a" + , " = Foo { a :: Int," + , " a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar { b :: a } deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + + expected = unlines + [ "data Foo a" + , " = Foo { a :: Int" + , " , a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar { b :: a" + , " }" + , " deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + +case24 :: Assertion +case24 = expected @=? testStep (step indentIndentStyle) input + where + input = unlines + [ "data Foo a" + , " = Foo { a :: Int," + , " a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar { b :: a } deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + + expected = unlines + [ "data Foo a" + , " = Foo" + , " { a :: Int" + , " , a2 :: String" + , " -- ^ some haddock" + , " }" + , " | Bar" + , " { b :: a" + , " }" + , " deriving (Eq, Show)" + , " deriving (ToJSON)" + ] + +sameSameStyle :: Config +sameSameStyle = Config SameLine SameLine 2 2 + +sameIndentStyle :: Config +sameIndentStyle = Config SameLine (Indent 2) 2 2 + +indentSameStyle :: Config +indentSameStyle = Config (Indent 2) SameLine 2 2 + +indentIndentStyle :: Config +indentIndentStyle = Config (Indent 2) (Indent 2) 2 2 + +indentIndentStyle4 :: Config +indentIndentStyle4 = Config (Indent 4) (Indent 4) 4 4 diff --git a/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs b/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs index 760018a..22031d4 100644 --- a/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs +++ b/tests/Language/Haskell/Stylish/Step/Imports/Tests.hs @@ -58,6 +58,7 @@ tests = testGroup "Language.Haskell.Stylish.Step.Imports.Tests" , testCase "case 24" case24 , testCase "case 25" case25 , testCase "case 26 (issue 185)" case26 + , testCase "case 27" case27 ] @@ -82,7 +83,7 @@ input = unlines -------------------------------------------------------------------------------- case01 :: Assertion -case01 = expected @=? testStep (step 80 $ fromImportAlign Global) input +case01 = expected @=? testStep (step (Just 80) $ fromImportAlign Global) input where expected = unlines [ "module Herp where" @@ -104,7 +105,7 @@ case01 = expected @=? testStep (step 80 $ fromImportAlign Global) input -------------------------------------------------------------------------------- case02 :: Assertion -case02 = expected @=? testStep (step 80 $ fromImportAlign Group) input +case02 = expected @=? testStep (step (Just 80) $ fromImportAlign Group) input where expected = unlines [ "module Herp where" @@ -125,7 +126,7 @@ case02 = expected @=? testStep (step 80 $ fromImportAlign Group) input -------------------------------------------------------------------------------- case03 :: Assertion -case03 = expected @=? testStep (step 80 $ fromImportAlign None) input +case03 = expected @=? testStep (step (Just 80) $ fromImportAlign None) input where expected = unlines [ "module Herp where" @@ -146,7 +147,7 @@ case03 = expected @=? testStep (step 80 $ fromImportAlign None) input -------------------------------------------------------------------------------- case04 :: Assertion -case04 = expected @=? testStep (step 80 $ fromImportAlign Global) input' +case04 = expected @=? testStep (step (Just 80) $ fromImportAlign Global) input' where input' = "import Data.Aeson.Types (object, typeMismatch, FromJSON(..)," ++ @@ -161,7 +162,7 @@ case04 = expected @=? testStep (step 80 $ fromImportAlign Global) input' -------------------------------------------------------------------------------- case05 :: Assertion -case05 = input' @=? testStep (step 80 $ fromImportAlign Group) input' +case05 = input' @=? testStep (step (Just 80) $ fromImportAlign Group) input' where input' = "import Distribution.PackageDescription.Configuration " ++ "(finalizePackageDescription)\n" @@ -169,7 +170,7 @@ case05 = input' @=? testStep (step 80 $ fromImportAlign Group) input' -------------------------------------------------------------------------------- case06 :: Assertion -case06 = input' @=? testStep (step 80 $ fromImportAlign File) input' +case06 = input' @=? testStep (step (Just 80) $ fromImportAlign File) input' where input' = unlines [ "import Bar.Qux" @@ -179,7 +180,7 @@ case06 = input' @=? testStep (step 80 $ fromImportAlign File) input' -------------------------------------------------------------------------------- case07 :: Assertion -case07 = expected @=? testStep (step 80 $ fromImportAlign File) input' +case07 = expected @=? testStep (step (Just 80) $ fromImportAlign File) input' where input' = unlines [ "import Bar.Qux" @@ -197,7 +198,7 @@ case07 = expected @=? testStep (step 80 $ fromImportAlign File) input' -------------------------------------------------------------------------------- case08 :: Assertion case08 = expected - @=? testStep (step 80 $ Options Global WithAlias True Inline Inherit (LPConstant 4) True False) input + @=? testStep (step (Just 80) $ Options Global WithAlias True Inline Inherit (LPConstant 4) True False) input where expected = unlines [ "module Herp where" @@ -220,7 +221,7 @@ case08 = expected -------------------------------------------------------------------------------- case08b :: Assertion case08b = expected - @=? testStep (step 80 $ Options Global WithModuleName True Inline Inherit (LPConstant 4) True False) input + @=? testStep (step (Just 80) $ Options Global WithModuleName True Inline Inherit (LPConstant 4) True False) input where expected = unlines ["module Herp where" @@ -242,7 +243,7 @@ case08b = expected -------------------------------------------------------------------------------- case09 :: Assertion case09 = expected - @=? testStep (step 80 $ Options Global WithAlias True Multiline Inherit (LPConstant 4) True False) input + @=? testStep (step (Just 80) $ Options Global WithAlias True Multiline Inherit (LPConstant 4) True False) input where expected = unlines [ "module Herp where" @@ -276,7 +277,7 @@ case09 = expected -------------------------------------------------------------------------------- case10 :: Assertion case10 = expected - @=? testStep (step 40 $ Options Group WithAlias True Multiline Inherit (LPConstant 4) True False) input + @=? testStep (step (Just 40) $ Options Group WithAlias True Multiline Inherit (LPConstant 4) True False) input where expected = unlines [ "module Herp where" @@ -315,7 +316,7 @@ case10 = expected -------------------------------------------------------------------------------- case11 :: Assertion case11 = expected - @=? testStep (step 80 $ Options Group NewLine True Inline Inherit (LPConstant 4) True False) input + @=? testStep (step (Just 80) $ Options Group NewLine True Inline Inherit (LPConstant 4) True False) input where expected = unlines [ "module Herp where" @@ -342,7 +343,7 @@ case11 = expected case11b :: Assertion case11b = expected - @=? testStep (step 80 $ Options Group WithModuleName True Inline Inherit (LPConstant 4) True False) input + @=? testStep (step (Just 80) $ Options Group WithModuleName True Inline Inherit (LPConstant 4) True False) input where expected = unlines [ "module Herp where" @@ -364,7 +365,7 @@ case11b = expected -------------------------------------------------------------------------------- case12 :: Assertion case12 = expected - @=? testStep (step 80 $ Options Group NewLine True Inline Inherit (LPConstant 2) True False) input' + @=? testStep (step (Just 80) $ Options Group NewLine True Inline Inherit (LPConstant 2) True False) input' where input' = unlines [ "import Data.List (map)" @@ -379,7 +380,7 @@ case12 = expected -------------------------------------------------------------------------------- case12b :: Assertion case12b = expected - @=? testStep (step 80 $ Options Group WithModuleName True Inline Inherit (LPConstant 2) True False) input' + @=? testStep (step (Just 80) $ Options Group WithModuleName True Inline Inherit (LPConstant 2) True False) input' where input' = unlines [ "import Data.List (map)" @@ -391,7 +392,7 @@ case12b = expected -------------------------------------------------------------------------------- case13 :: Assertion case13 = expected - @=? testStep (step 80 $ Options None WithAlias True InlineWithBreak Inherit (LPConstant 4) True False) input' + @=? testStep (step (Just 80) $ Options None WithAlias True InlineWithBreak Inherit (LPConstant 4) True False) input' where input' = unlines [ "import qualified Data.List as List (concat, foldl, foldr, head, init," @@ -408,7 +409,7 @@ case13 = expected -------------------------------------------------------------------------------- case13b :: Assertion case13b = expected - @=? testStep (step 80 $ Options None WithModuleName True InlineWithBreak Inherit (LPConstant 4) True False) input' + @=? testStep (step (Just 80) $ Options None WithModuleName True InlineWithBreak Inherit (LPConstant 4) True False) input' where input' = unlines [ "import qualified Data.List as List (concat, foldl, foldr, head, init," @@ -426,7 +427,7 @@ case13b = expected case14 :: Assertion case14 = expected @=? testStep - (step 80 $ Options None WithAlias True InlineWithBreak Inherit (LPConstant 10) True False) expected + (step (Just 80) $ Options None WithAlias True InlineWithBreak Inherit (LPConstant 10) True False) expected where expected = unlines [ "import qualified Data.List as List (concat, map, null, reverse, tail, (++))" @@ -436,7 +437,7 @@ case14 = expected -------------------------------------------------------------------------------- case15 :: Assertion case15 = expected - @=? testStep (step 80 $ Options None AfterAlias True Multiline Inherit (LPConstant 4) True False) input' + @=? testStep (step (Just 80) $ Options None AfterAlias True Multiline Inherit (LPConstant 4) True False) input' where expected = unlines [ "import Data.Acid (AcidState)" @@ -462,7 +463,7 @@ case15 = expected -------------------------------------------------------------------------------- case16 :: Assertion case16 = expected - @=? testStep (step 80 $ Options None AfterAlias True Multiline Inherit (LPConstant 4) False False) input' + @=? testStep (step (Just 80) $ Options None AfterAlias True Multiline Inherit (LPConstant 4) False False) input' where expected = unlines [ "import Data.Acid (AcidState)" @@ -486,7 +487,7 @@ case16 = expected -------------------------------------------------------------------------------- case17 :: Assertion case17 = expected - @=? testStep (step 80 $ Options None AfterAlias True Multiline Inherit (LPConstant 4) True False) input' + @=? testStep (step (Just 80) $ Options None AfterAlias True Multiline Inherit (LPConstant 4) True False) input' where expected = unlines [ "import Control.Applicative (Applicative (pure, (<*>)))" @@ -504,7 +505,7 @@ case17 = expected -------------------------------------------------------------------------------- case18 :: Assertion case18 = expected @=? testStep - (step 40 $ Options None AfterAlias True InlineToMultiline Inherit (LPConstant 4) True False) input' + (step (Just 40) $ Options None AfterAlias True InlineToMultiline Inherit (LPConstant 4) True False) input' where expected = unlines ---------------------------------------- @@ -532,7 +533,7 @@ case18 = expected @=? testStep -------------------------------------------------------------------------------- case19 :: Assertion case19 = expected @=? testStep - (step 40 $ Options Global NewLine True InlineWithBreak RightAfter (LPConstant 17) True False) case19input + (step (Just 40) $ Options Global NewLine True InlineWithBreak RightAfter (LPConstant 17) True False) case19input where expected = unlines ---------------------------------------- @@ -548,7 +549,7 @@ case19 = expected @=? testStep case19b :: Assertion case19b = expected @=? testStep - (step 40 $ Options File NewLine True InlineWithBreak RightAfter (LPConstant 17) True False) case19input + (step (Just 40) $ Options File NewLine True InlineWithBreak RightAfter (LPConstant 17) True False) case19input where expected = unlines ---------------------------------------- @@ -564,7 +565,7 @@ case19b = expected @=? testStep case19c :: Assertion case19c = expected @=? testStep - (step 40 $ Options File NewLine True InlineWithBreak RightAfter LPModuleName True False) case19input + (step (Just 40) $ Options File NewLine True InlineWithBreak RightAfter LPModuleName True False) case19input where expected = unlines ---------------------------------------- @@ -580,7 +581,7 @@ case19c = expected @=? testStep case19d :: Assertion case19d = expected @=? testStep - (step 40 $ Options Global NewLine True InlineWithBreak RightAfter LPModuleName True False) case19input + (step (Just 40) $ Options Global NewLine True InlineWithBreak RightAfter LPModuleName True False) case19input where expected = unlines ---------------------------------------- @@ -606,7 +607,7 @@ case19input = unlines -------------------------------------------------------------------------------- case20 :: Assertion case20 = expected - @=? testStep (step 80 defaultOptions) input' + @=? testStep (step (Just 80) defaultOptions) input' where expected = unlines [ "import {-# SOURCE #-} Data.ByteString as BS" @@ -625,7 +626,7 @@ case20 = expected -------------------------------------------------------------------------------- case21 :: Assertion case21 = expected - @=? testStep (step 80 defaultOptions) input' + @=? testStep (step (Just 80) defaultOptions) input' where expected = unlines [ "{-# LANGUAGE ExplicitNamespaces #-}" @@ -656,7 +657,7 @@ case21 = expected -------------------------------------------------------------------------------- case22 :: Assertion case22 = expected - @=? testStep (step 80 defaultOptions) input' + @=? testStep (step (Just 80) defaultOptions) input' where expected = unlines [ "{-# LANGUAGE PackageImports #-}" @@ -683,7 +684,7 @@ case22 = expected -------------------------------------------------------------------------------- case23 :: Assertion case23 = expected - @=? testStep (step 40 $ Options None AfterAlias False Inline Inherit (LPConstant 4) True True) input' + @=? testStep (step (Just 40) $ Options None AfterAlias False Inline Inherit (LPConstant 4) True True) input' where expected = unlines [ "import Data.Acid ( AcidState )" @@ -708,7 +709,7 @@ case23 = expected -------------------------------------------------------------------------------- case23b :: Assertion case23b = expected - @=? testStep (step 40 $ Options None WithModuleName False Inline Inherit (LPConstant 4) True True) input' + @=? testStep (step (Just 40) $ Options None WithModuleName False Inline Inherit (LPConstant 4) True True) input' where expected = unlines [ "import Data.Acid ( AcidState )" @@ -734,7 +735,7 @@ case23b = expected -------------------------------------------------------------------------------- case24 :: Assertion case24 = expected - @=? testStep (step 40 $ Options None AfterAlias False InlineWithBreak Inherit (LPConstant 4) True True) input' + @=? testStep (step (Just 40) $ Options None AfterAlias False InlineWithBreak Inherit (LPConstant 4) True True) input' where expected = unlines [ "import Data.Acid ( AcidState )" @@ -758,7 +759,7 @@ case24 = expected -------------------------------------------------------------------------------- case25 :: Assertion case25 = expected - @=? testStep (step 80 $ Options Group AfterAlias False Multiline Inherit (LPConstant 4) False False) input' + @=? testStep (step (Just 80) $ Options Group AfterAlias False Multiline Inherit (LPConstant 4) False False) input' where expected = unlines [ "import Data.Acid (AcidState)" @@ -783,7 +784,7 @@ case25 = expected -------------------------------------------------------------------------------- case26 :: Assertion case26 = expected - @=? testStep (step 80 options ) input' + @=? testStep (step (Just 80) options ) input' where options = defaultOptions { listAlign = NewLine, longListAlign = Multiline } input' = unlines @@ -792,3 +793,23 @@ case26 = expected expected = unlines [ "import Data.List" ] + + +-------------------------------------------------------------------------------- +case27 :: Assertion +case27 = expected @=? testStep (step Nothing $ fromImportAlign Global) input + where + expected = unlines + [ "module Herp where" + , "" + , "import Control.Monad" + , "import Data.List as List (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail, (++))" + , "import Data.Map (Map, insert, lookup, (!))" + , "import qualified Data.Map as M" + , "import Only.Instances ()" + , "" + , "import Foo (Bar (..))" + , "import Herp.Derp.Internals hiding (foo)" + , "" + , "herp = putStrLn \"import Hello world\"" + ] diff --git a/tests/Language/Haskell/Stylish/Step/LanguagePragmas/Tests.hs b/tests/Language/Haskell/Stylish/Step/LanguagePragmas/Tests.hs index 2d74813..0ede803 100644 --- a/tests/Language/Haskell/Stylish/Step/LanguagePragmas/Tests.hs +++ b/tests/Language/Haskell/Stylish/Step/LanguagePragmas/Tests.hs @@ -28,12 +28,16 @@ tests = testGroup "Language.Haskell.Stylish.Step.LanguagePragmas.Tests" , testCase "case 08" case08 , testCase "case 09" case09 , testCase "case 10" case10 + , testCase "case 11" case11 + , testCase "case 12" case12 ] +lANG :: String +lANG = "LANGUAGE" -------------------------------------------------------------------------------- case01 :: Assertion -case01 = expected @=? testStep (step 80 Vertical True False) input +case01 = expected @=? testStep (step (Just 80) Vertical True False lANG) input where input = unlines [ "{-# LANGUAGE ViewPatterns #-}" @@ -52,7 +56,7 @@ case01 = expected @=? testStep (step 80 Vertical True False) input -------------------------------------------------------------------------------- case02 :: Assertion -case02 = expected @=? testStep (step 80 Vertical True True) input +case02 = expected @=? testStep (step (Just 80) Vertical True True lANG) input where input = unlines [ "{-# LANGUAGE BangPatterns #-}" @@ -68,7 +72,7 @@ case02 = expected @=? testStep (step 80 Vertical True True) input -------------------------------------------------------------------------------- case03 :: Assertion -case03 = expected @=? testStep (step 80 Vertical True True) input +case03 = expected @=? testStep (step (Just 80) Vertical True True lANG) input where input = unlines [ "{-# LANGUAGE BangPatterns #-}" @@ -84,7 +88,7 @@ case03 = expected @=? testStep (step 80 Vertical True True) input -------------------------------------------------------------------------------- case04 :: Assertion -case04 = expected @=? testStep (step 80 Compact True False) input +case04 = expected @=? testStep (step (Just 80) Compact True False lANG) input where input = unlines [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable," @@ -101,7 +105,7 @@ case04 = expected @=? testStep (step 80 Compact True False) input -------------------------------------------------------------------------------- case05 :: Assertion -case05 = expected @=? testStep (step 80 Vertical True False) input +case05 = expected @=? testStep (step (Just 80) Vertical True False lANG) input where input = unlines [ "{-# LANGUAGE CPP #-}" @@ -122,7 +126,7 @@ case05 = expected @=? testStep (step 80 Vertical True False) input -------------------------------------------------------------------------------- case06 :: Assertion -case06 = expected @=? testStep (step 80 CompactLine True False) input +case06 = expected @=? testStep (step (Just 80) CompactLine True False lANG) input where input = unlines [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable," @@ -137,7 +141,7 @@ case06 = expected @=? testStep (step 80 CompactLine True False) input -------------------------------------------------------------------------------- case07 :: Assertion -case07 = expected @=? testStep (step 80 Vertical False False) input +case07 = expected @=? testStep (step (Just 80) Vertical False False lANG) input where input = unlines [ "{-# LANGUAGE ViewPatterns #-}" @@ -157,7 +161,7 @@ case07 = expected @=? testStep (step 80 Vertical False False) input -------------------------------------------------------------------------------- case08 :: Assertion -case08 = expected @=? testStep (step 80 CompactLine False False) input +case08 = expected @=? testStep (step (Just 80) CompactLine False False lANG) input where input = unlines [ "{-# LANGUAGE TypeOperators, StandaloneDeriving, DeriveDataTypeable," @@ -173,7 +177,7 @@ case08 = expected @=? testStep (step 80 CompactLine False False) input -------------------------------------------------------------------------------- case09 :: Assertion -case09 = expected @=? testStep (step 80 Compact True False) input +case09 = expected @=? testStep (step (Just 80) Compact True False lANG) input where input = unlines [ "{-# LANGUAGE DefaultSignatures, FlexibleInstances, LambdaCase, " ++ @@ -187,7 +191,7 @@ case09 = expected @=? testStep (step 80 Compact True False) input -------------------------------------------------------------------------------- case10 :: Assertion -case10 = expected @=? testStep (step 80 Compact True False) input +case10 = expected @=? testStep (step (Just 80) Compact True False lANG) input where input = unlines [ "{-# LANGUAGE NondecreasingIndentation, ScopedTypeVariables," @@ -197,3 +201,38 @@ case10 = expected @=? testStep (step 80 Compact True False) input [ "{-# LANGUAGE NondecreasingIndentation, ScopedTypeVariables, " ++ "TypeApplications #-}" ] + +-------------------------------------------------------------------------------- +case11 :: Assertion +case11 = expected @=? testStep (step (Just 80) Vertical False False "language") input + where + input = unlines + [ "{-# LANGUAGE ViewPatterns #-}" + , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}" + , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}" + , "module Main where" + ] + + expected = unlines + [ "{-# language NoImplicitPrelude #-}" + , "{-# language ScopedTypeVariables #-}" + , "{-# language TemplateHaskell #-}" + , "{-# language ViewPatterns #-}" + , "module Main where" + ] + +-------------------------------------------------------------------------------- +case12 :: Assertion +case12 = expected @=? testStep (step Nothing Compact False False "language") input + where + input = unlines + [ "{-# LANGUAGE ViewPatterns, OverloadedStrings #-}" + , "{-# LANGUAGE TemplateHaskell, ViewPatterns #-}" + , "{-# LANGUAGE ScopedTypeVariables, NoImplicitPrelude #-}" + , "module Main where" + ] + + expected = unlines + [ "{-# language NoImplicitPrelude, OverloadedStrings, ScopedTypeVariables, TemplateHaskell, ViewPatterns #-}" + , "module Main where" + ] diff --git a/tests/Language/Haskell/Stylish/Step/SimpleAlign/Tests.hs b/tests/Language/Haskell/Stylish/Step/SimpleAlign/Tests.hs index b8afab4..a2a51fc 100644 --- a/tests/Language/Haskell/Stylish/Step/SimpleAlign/Tests.hs +++ b/tests/Language/Haskell/Stylish/Step/SimpleAlign/Tests.hs @@ -26,12 +26,13 @@ tests = testGroup "Language.Haskell.Stylish.Step.SimpleAlign.Tests" , testCase "case 06" case06 , testCase "case 07" case07 , testCase "case 08" case08 + , testCase "case 09" case09 ] -------------------------------------------------------------------------------- case01 :: Assertion -case01 = expected @=? testStep (step 80 defaultConfig) input +case01 = expected @=? testStep (step (Just 80) defaultConfig) input where input = unlines [ "eitherToMaybe e = case e of" @@ -48,7 +49,7 @@ case01 = expected @=? testStep (step 80 defaultConfig) input -------------------------------------------------------------------------------- case02 :: Assertion -case02 = expected @=? testStep (step 80 defaultConfig) input +case02 = expected @=? testStep (step (Just 80) defaultConfig) input where input = unlines [ "eitherToMaybe (Left _) = Nothing" @@ -63,7 +64,7 @@ case02 = expected @=? testStep (step 80 defaultConfig) input -------------------------------------------------------------------------------- case03 :: Assertion -case03 = expected @=? testStep (step 80 defaultConfig) input +case03 = expected @=? testStep (step (Just 80) defaultConfig) input where input = unlines [ "heady def [] = def" @@ -78,7 +79,7 @@ case03 = expected @=? testStep (step 80 defaultConfig) input -------------------------------------------------------------------------------- case04 :: Assertion -case04 = expected @=? testStep (step 80 defaultConfig) input +case04 = expected @=? testStep (step (Just 80) defaultConfig) input where input = unlines [ "data Foo = Foo" @@ -97,7 +98,7 @@ case04 = expected @=? testStep (step 80 defaultConfig) input -------------------------------------------------------------------------------- case05 :: Assertion -case05 = input @=? testStep (step 80 defaultConfig) input +case05 = input @=? testStep (step (Just 80) defaultConfig) input where -- Don't attempt to align this since a field spans multiple lines input = unlines @@ -113,7 +114,7 @@ case05 = input @=? testStep (step 80 defaultConfig) input case06 :: Assertion case06 = -- 22 max columns is /just/ enough to align this stuff. - expected @=? testStep (step 22 defaultConfig) input + expected @=? testStep (step (Just 22) defaultConfig) input where input = unlines [ "data Foo = Foo" @@ -134,7 +135,7 @@ case06 = case07 :: Assertion case07 = -- 21 max columns is /just NOT/ enough to align this stuff. - expected @=? testStep (step 21 defaultConfig) input + expected @=? testStep (step (Just 21) defaultConfig) input where input = unlines [ "data Foo = Foo" @@ -153,7 +154,7 @@ case07 = -------------------------------------------------------------------------------- case08 :: Assertion -case08 = expected @=? testStep (step 80 defaultConfig) input +case08 = expected @=? testStep (step (Just 80) defaultConfig) input where input = unlines [ "canDrink mbAge = case mbAge of" @@ -166,3 +167,23 @@ case08 = expected @=? testStep (step 80 defaultConfig) input , " Just age | age > 18 -> True" , " _ -> False" ] + + +-------------------------------------------------------------------------------- +case09 :: Assertion +case09 = + expected @=? testStep (step Nothing defaultConfig) input + where + input = unlines + [ "data Foo = Foo" + , " { foo :: String" + , " , barqux :: Int" + , " }" + ] + + expected = unlines + [ "data Foo = Foo" + , " { foo :: String" + , " , barqux :: Int" + , " }" + ] diff --git a/tests/Language/Haskell/Stylish/Step/UnicodeSyntax/Tests.hs b/tests/Language/Haskell/Stylish/Step/UnicodeSyntax/Tests.hs index 9652350..e2ba34f 100644 --- a/tests/Language/Haskell/Stylish/Step/UnicodeSyntax/Tests.hs +++ b/tests/Language/Haskell/Stylish/Step/UnicodeSyntax/Tests.hs @@ -19,12 +19,13 @@ import Language.Haskell.Stylish.Tests.Util tests :: Test tests = testGroup "Language.Haskell.Stylish.Step.UnicodeSyntax.Tests" [ testCase "case 01" case01 + , testCase "case 02" case02 ] -------------------------------------------------------------------------------- case01 :: Assertion -case01 = expected @=? testStep (step True) input +case01 = expected @=? testStep (step True "LANGUAGE") input where input = unlines [ "sort :: Ord a => [a] -> [a]" @@ -36,3 +37,19 @@ case01 = expected @=? testStep (step True) input , "sort ∷ Ord a ⇒ [a] → [a]" , "sort _ = []" ] + + +-------------------------------------------------------------------------------- +case02 :: Assertion +case02 = expected @=? testStep (step True "LaNgUaGe") input + where + input = unlines + [ "sort :: Ord a => [a] -> [a]" + , "sort _ = []" + ] + + expected = unlines + [ "{-# LaNgUaGe UnicodeSyntax #-}" + , "sort ∷ Ord a ⇒ [a] → [a]" + , "sort _ = []" + ]
\ No newline at end of file diff --git a/tests/Language/Haskell/Stylish/Tests.hs b/tests/Language/Haskell/Stylish/Tests.hs new file mode 100644 index 0000000..97eab8a --- /dev/null +++ b/tests/Language/Haskell/Stylish/Tests.hs @@ -0,0 +1,144 @@ +-------------------------------------------------------------------------------- +module Language.Haskell.Stylish.Tests + ( tests + ) where + + +-------------------------------------------------------------------------------- +import Data.List (sort) +import System.Directory (createDirectory) +import System.FilePath (normalise, (</>)) +import Test.Framework (Test, testGroup) +import Test.Framework.Providers.HUnit (testCase) +import Test.HUnit (Assertion, (@?=)) + + +-------------------------------------------------------------------------------- +import Language.Haskell.Stylish +import Language.Haskell.Stylish.Tests.Util + + +-------------------------------------------------------------------------------- +tests :: Test +tests = testGroup "Language.Haskell.Stylish.Tests" + [ testCase "case 01" case01 + , testCase "case 02" case02 + , testCase "case 03" case03 + , testCase "case 04" case04 + , testCase "case 05" case05 + , testCase "case 06" case06 + , testCase "case 07" case07 + ] + + +-------------------------------------------------------------------------------- +case01 :: Assertion +case01 = (@?= result) =<< format Nothing Nothing input + where + input = "module Herp where\ndata Foo = Bar | Baz { baz :: Int }" + result = Right $ lines input + + +-------------------------------------------------------------------------------- +case02 :: Assertion +case02 = withTestDirTree $ do + writeFile "test-config.yaml" $ unlines + [ "steps:" + , " - records:" + , " equals: \"indent 2\"" + , " first_field: \"indent 2\"" + , " field_comment: 2" + , " deriving: 2" + ] + + actual <- format (Just $ ConfigPath "test-config.yaml") Nothing input + actual @?= result + where + input = "module Herp where\ndata Foo = Bar | Baz { baz :: Int }" + result = Right [ "module Herp where" + , "data Foo" + , " = Bar" + , " | Baz" + , " { baz :: Int" + , " }" + ] + +-------------------------------------------------------------------------------- +case03 :: Assertion +case03 = withTestDirTree $ do + writeFile "test-config.yaml" $ unlines + [ "steps:" + , " - records:" + , " equals: \"same_line\"" + , " first_field: \"same_line\"" + , " field_comment: 2" + , " deriving: 2" + ] + + actual <- format (Just $ ConfigPath "test-config.yaml") Nothing input + actual @?= result + where + input = unlines [ "module Herp where" + , "data Foo" + , " = Bar" + , " | Baz" + , " { baz :: Int" + , " }" + ] + result = Right [ "module Herp where" + , "data Foo = Bar" + , " | Baz { baz :: Int" + , " }" + ] + +-------------------------------------------------------------------------------- +case04 :: Assertion +case04 = (@?= result) =<< format Nothing (Just fileLocation) input + where + fileLocation = "directory/File.hs" + input = "module Herp" + result = Left $ + "Language.Haskell.Stylish.Parse.parseModule: could not parse " <> + fileLocation <> + ": ParseFailed (SrcLoc \"<unknown>.hs\" 2 1) \"Parse error: EOF\"" + + +-------------------------------------------------------------------------------- +-- | When providing current dir including folders and files. +case05 :: Assertion +case05 = withTestDirTree $ do + createDirectory aDir >> writeFile c fileCont + mapM_ (flip writeFile fileCont) fs + result <- findHaskellFiles False input + sort result @?= (sort $ map normalise expected) + where + input = c : fs + fs = ["b.hs", "a.hs"] + c = aDir </> "c.hs" + aDir = "aDir" + expected = ["a.hs", "b.hs", c] + fileCont = "" + + +-------------------------------------------------------------------------------- +-- | When the input item is not file, do not recurse it. +case06 :: Assertion +case06 = withTestDirTree $ do + mapM_ (flip writeFile "") input + result <- findHaskellFiles False input + result @?= expected + where + input = ["b.hs"] + expected = map normalise input + + +-------------------------------------------------------------------------------- +-- | Empty input should result in empty output. +case07 :: Assertion +case07 = withTestDirTree $ do + mapM_ (flip writeFile "") input + result <- findHaskellFiles False input + result @?= expected + where + input = [] + expected = input diff --git a/tests/Language/Haskell/Stylish/Tests/Util.hs b/tests/Language/Haskell/Stylish/Tests/Util.hs index 40b5629..f43b6b5 100644 --- a/tests/Language/Haskell/Stylish/Tests/Util.hs +++ b/tests/Language/Haskell/Stylish/Tests/Util.hs @@ -1,9 +1,22 @@ module Language.Haskell.Stylish.Tests.Util ( testStep + , withTestDirTree ) where -------------------------------------------------------------------------------- +import Control.Exception (bracket, try) +import System.Directory (createDirectory, + getCurrentDirectory, + getTemporaryDirectory, + removeDirectoryRecursive, + setCurrentDirectory) +import System.FilePath ((</>)) +import System.IO.Error (isAlreadyExistsError) +import System.Random (randomIO) + + +-------------------------------------------------------------------------------- import Language.Haskell.Stylish.Parse import Language.Haskell.Stylish.Step @@ -15,3 +28,34 @@ testStep step str = case parseModule [] Nothing str of Right module' -> unlines $ stepFilter step ls module' where ls = lines str + + +-------------------------------------------------------------------------------- +-- | Create a temporary directory with a randomised name built from the template +-- provided +createTempDirectory :: String -> IO FilePath +createTempDirectory template = do + tmpRootDir <- getTemporaryDirectory + dirId <- randomIO :: IO Word + findTempName tmpRootDir dirId + where + findTempName :: FilePath -> Word -> IO FilePath + findTempName tmpRootDir x = do + let dirpath = tmpRootDir </> template ++ show x + r <- try $ createDirectory dirpath + case r of + Right _ -> return dirpath + Left e | isAlreadyExistsError e -> findTempName tmpRootDir (x+1) + | otherwise -> ioError e + + +-------------------------------------------------------------------------------- +-- | Perform an action inside a temporary directory tree and purge the tree +-- afterwards +withTestDirTree :: IO a -> IO a +withTestDirTree action = bracket + ((,) <$> getCurrentDirectory <*> createTempDirectory "stylish_haskell") + (\(current, temp) -> + setCurrentDirectory current *> + removeDirectoryRecursive temp) + (\(_, temp) -> setCurrentDirectory temp *> action) diff --git a/tests/TestSuite.hs b/tests/TestSuite.hs index b5bec90..d2023ed 100644 --- a/tests/TestSuite.hs +++ b/tests/TestSuite.hs @@ -11,6 +11,7 @@ import Test.Framework (default -------------------------------------------------------------------------------- import qualified Language.Haskell.Stylish.Config.Tests import qualified Language.Haskell.Stylish.Parse.Tests +import qualified Language.Haskell.Stylish.Step.Data.Tests import qualified Language.Haskell.Stylish.Step.Imports.Tests import qualified Language.Haskell.Stylish.Step.LanguagePragmas.Tests import qualified Language.Haskell.Stylish.Step.SimpleAlign.Tests @@ -18,6 +19,7 @@ import qualified Language.Haskell.Stylish.Step.Squash.Tests import qualified Language.Haskell.Stylish.Step.Tabs.Tests import qualified Language.Haskell.Stylish.Step.TrailingWhitespace.Tests import qualified Language.Haskell.Stylish.Step.UnicodeSyntax.Tests +import qualified Language.Haskell.Stylish.Tests -------------------------------------------------------------------------------- @@ -25,6 +27,7 @@ main :: IO () main = defaultMain [ Language.Haskell.Stylish.Parse.Tests.tests , Language.Haskell.Stylish.Config.Tests.tests + , Language.Haskell.Stylish.Step.Data.Tests.tests , Language.Haskell.Stylish.Step.Imports.Tests.tests , Language.Haskell.Stylish.Step.LanguagePragmas.Tests.tests , Language.Haskell.Stylish.Step.SimpleAlign.Tests.tests @@ -32,4 +35,5 @@ main = defaultMain , Language.Haskell.Stylish.Step.Tabs.Tests.tests , Language.Haskell.Stylish.Step.TrailingWhitespace.Tests.tests , Language.Haskell.Stylish.Step.UnicodeSyntax.Tests.tests + , Language.Haskell.Stylish.Tests.tests ] |