ormolu 0.3.0.1 → 0.3.1.0
raw patch · 22 files changed
+269/−105 lines, 22 filesdep ~optparse-applicative
Dependency ranges changed: optparse-applicative
Files
- CHANGELOG.md +11/−0
- DESIGN.md +1/−1
- README.md +32/−13
- app/Main.hs +44/−30
- data/examples/declaration/value/function/multiple-guards.hs +2/−3
- data/examples/declaration/value/function/pattern/many-guards-in-singleline-out.hs +3/−0
- data/examples/declaration/value/function/pattern/many-guards-in-singleline.hs +3/−0
- data/examples/declaration/value/function/pattern/multiline-guard-statement-out.hs +6/−0
- data/examples/declaration/value/function/pattern/multiline-guard-statement.hs +5/−0
- data/examples/declaration/value/function/pattern/multiple-guard-statements-out.hs +6/−0
- data/examples/declaration/value/function/pattern/multiple-guard-statements.hs +4/−0
- data/examples/other/invalid-haddock-out.hs +17/−0
- data/examples/other/invalid-haddock-weird-out.hs +3/−0
- data/examples/other/invalid-haddock-weird.hs +3/−0
- data/examples/other/invalid-haddock.hs +17/−0
- ormolu.cabal +1/−1
- src/Ormolu.hs +1/−7
- src/Ormolu/Diff/ParseResult.hs +9/−10
- src/Ormolu/Parser.hs +1/−1
- src/Ormolu/Parser/CommentStream.hs +77/−32
- src/Ormolu/Printer/Meat/Declaration/Value.hs +11/−6
- src/Ormolu/Utils/IO.hs +12/−1
CHANGELOG.md view
@@ -1,3 +1,14 @@+## Ormolu 0.3.1.0++* Allow check mode when working with stdin input. [Issue 634](+ https://github.com/tweag/ormolu/issues/634).+* Now guards are printed on a new line if at least one guard is multiline or+ if all guards together occupy more than one line. The body of each guard+ is also indented one level deeper in that case. [Issue+ 712](https://github.com/tweag/ormolu/issues/712).+* Invalid Haddock comments are no longer silently deleted, but rather converted+ into regular comments. [Issue 474](https://github.com/tweag/ormolu/issues/474).+ ## Ormolu 0.3.0.1 * Improvements to `.cabal` file handling:
DESIGN.md view
@@ -113,7 +113,7 @@ Stylish Haskell is not so invasive as the other formatters and most reported bugs are about parsing issues and CPP. As I understand it, people mostly use-it to screw their import lists.+it to sort their import lists. ### Haskell formatter
README.md view
@@ -6,8 +6,8 @@ [](http://stackage.org/lts/package/ormolu) [](https://buildkite.com/tweag-1/ormolu) -* [Building and installation](#building-and-installation)- * [Arch Linux](#arch-linux)+* [Installation](#installation)+* [Building from source](#building-from-source) * [Usage](#usage) * [Editor integration](#editor-integration) * [GitHub actions](#github-actions)@@ -39,8 +39,26 @@ * Be well-tested and robust so that the formatter can be used in large projects. -## Building and installation+## Installation +The [release page][releases] has binaries for Linux, macOS and Windows.++You can also install using `cabal` or `stack`:++```console+$ cabal install ormolu+$ stack install ormolu+```++Ormolu is also included in several package repositories. E.g., on Arch Linux,+one can use [the package on AUR][aur]:++```console+$ yay -S ormolu+```++## Building from source+ The easiest way to build the project is with Nix: ```console@@ -73,14 +91,6 @@ in (import source { }).ormoluExe # this is e.g. the executable derivation ``` -### Arch Linux--To install Ormolu on Arch Linux, one can use [the package on AUR][aur]:--```console-yay -S ormolu-```- ## Usage The following will print the formatted output to the standard output.@@ -114,6 +124,14 @@ $ ormolu --mode check $(find . -name '*.hs') ``` +#### :zap: Beware git's `core.autocrlf` on Windows :zap:+Ormolu's output always uses LF line endings. In particular,+`ormolu --mode check` will fail if its input is correctly formatted+*except* that it has CRLF line endings. This situation can happen on Windows+when checking out a git repository without having set [`core.autocrlf`](+https://www.git-scm.com/docs/git-config#Documentation/git-config.txt-coreautocrlf)+to `false`.+ ### Editor integration We know of the following editor integrations:@@ -174,7 +192,7 @@ 8 | Cabal file parsing failed 9 | Missing input file path when using stdin input and accounting for .cabal files 100 | In checking mode: unformatted files-101 | Inplace and check modes do not work with stdin+101 | Inplace mode does not work with stdin 102 | Other issue (with multiple input files) ## Limitations@@ -211,14 +229,15 @@ Copyright © 2018–present Tweag I/O -[iohk-hydra-binary-cache]: https://input-output-hk.github.io/haskell.nix/tutorials/getting-started/#setting-up-the-binary-cache [aur]: https://aur.archlinux.org/packages/ormolu [contributing]: https://github.com/tweag/ormolu/blob/master/CONTRIBUTING.md [design-cpp]: https://github.com/tweag/ormolu/blob/master/DESIGN.md#cpp [emacs-package]: https://github.com/vyorkin/ormolu.el [haskell-src-exts]: https://hackage.haskell.org/package/haskell-src-exts+[iohk-hydra-binary-cache]: https://input-output-hk.github.io/haskell.nix/tutorials/getting-started/#setting-up-the-binary-cache [license]: https://github.com/tweag/ormolu/blob/master/LICENSE.md [neoformat]: https://github.com/sbdchd/neoformat+[releases]: https://github.com/tweag/ormolu/releases [ormolu-action]: https://github.com/marketplace/actions/ormolu-action [vim-ormolu]: https://github.com/sdiehl/vim-ormolu [vs-code-plugin]: https://marketplace.visualstudio.com/items?itemName=sjurmillidahl.ormolu-vscode
app/Main.hs view
@@ -11,6 +11,7 @@ import Data.Bool (bool) import Data.List (intercalate, sort) import Data.Maybe (mapMaybe)+import qualified Data.Text as T import qualified Data.Text.IO as TIO import Data.Version (showVersion) import Development.GitRev@@ -66,54 +67,67 @@ formatOne CabalDefaultExtensionsOpts {..} mode config mpath = withPrettyOrmoluExceptions (cfgColorMode config) $ case FP.normalise <$> mpath of+ -- input source = STDIN Nothing -> do- extraDynOptions <-- if optUseCabalDefaultExtensions- then case optStdinInputFile of- Just stdinInputFile ->- getCabalExtensionDynOptions stdinInputFile- Nothing -> throwIO OrmoluMissingStdinInputFile- else pure []- r <- ormoluStdin (configPlus extraDynOptions)+ resultConfig <-+ configPlus+ <$> if optUseCabalDefaultExtensions+ then case optStdinInputFile of+ Just stdinInputFile ->+ getCabalExtensionDynOptions stdinInputFile+ Nothing -> throwIO OrmoluMissingStdinInputFile+ else pure [] case mode of Stdout -> do- TIO.putStr r+ ormoluStdin resultConfig >>= TIO.putStr return ExitSuccess- _ -> do+ InPlace -> do hPutStrLn stderr- "This feature is not supported when input comes from stdin."+ "In place editing is not supported when input comes from stdin." -- 101 is different from all the other exit codes we already use. return (ExitFailure 101)+ Check -> do+ -- ormoluStdin is not used because we need the originalInput+ originalInput <- getContentsUtf8+ let stdinRepr = "<stdin>"+ formattedInput <-+ ormolu resultConfig stdinRepr (T.unpack originalInput)+ handleDiff originalInput formattedInput stdinRepr+ -- input source = a file Just inputFile -> do- extraDynOptions <-- if optUseCabalDefaultExtensions- then getCabalExtensionDynOptions inputFile- else pure []- originalInput <- readFileUtf8 inputFile- formattedInput <- ormoluFile (configPlus extraDynOptions) inputFile+ resultConfig <-+ configPlus+ <$> if optUseCabalDefaultExtensions+ then getCabalExtensionDynOptions inputFile+ else pure [] case mode of Stdout -> do- TIO.putStr formattedInput+ ormoluFile resultConfig inputFile >>= TIO.putStr return ExitSuccess InPlace -> do- -- Only write when the contents have changed, in order to avoid- -- updating the modified timestamp if the file was already correctly- -- formatted.+ -- ormoluFile is not used because we need originalInput+ originalInput <- readFileUtf8 inputFile+ formattedInput <- ormolu resultConfig inputFile (T.unpack originalInput) when (formattedInput /= originalInput) $ writeFileUtf8 inputFile formattedInput return ExitSuccess- Check ->- case diffText originalInput formattedInput inputFile of- Nothing -> return ExitSuccess- Just diff -> do- runTerm (printTextDiff diff) (cfgColorMode config) stderr- -- 100 is different to all the other exit code that are emitted- -- either from an 'OrmoluException' or from 'error' and- -- 'notImplemented'.- return (ExitFailure 100)+ Check -> do+ -- ormoluFile is not used because we need originalInput+ originalInput <- readFileUtf8 inputFile+ formattedInput <- ormolu resultConfig inputFile (T.unpack originalInput)+ handleDiff originalInput formattedInput inputFile where configPlus dynOpts = config {cfgDynOptions = cfgDynOptions config ++ dynOpts}+ handleDiff originalInput formattedInput fileRepr =+ case diffText originalInput formattedInput fileRepr of+ Nothing -> return ExitSuccess+ Just diff -> do+ runTerm (printTextDiff diff) (cfgColorMode config) stderr+ -- 100 is different to all the other exit code that are emitted+ -- either from an 'OrmoluException' or from 'error' and+ -- 'notImplemented'.+ return (ExitFailure 100) ---------------------------------------------------------------------------- -- Command line options parsing
data/examples/declaration/value/function/multiple-guards.hs view
@@ -1,7 +1,6 @@ foo :: Int -> Int-foo x- | x == 5 = 10- | otherwise = 12+foo x | x == 5 = 10+ | otherwise = 12 bar :: Int -> Int bar x
+ data/examples/declaration/value/function/pattern/many-guards-in-singleline-out.hs view
@@ -0,0 +1,3 @@+foobar x | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7++foobaz | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7
+ data/examples/declaration/value/function/pattern/many-guards-in-singleline.hs view
@@ -0,0 +1,3 @@+foobar x | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7++foobaz | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7
+ data/examples/declaration/value/function/pattern/multiline-guard-statement-out.hs view
@@ -0,0 +1,6 @@+foobarbar :: Int -> Bool+foobarbar+ | x <-+ 5 = case x of+ 5 -> True+ _ -> False
+ data/examples/declaration/value/function/pattern/multiline-guard-statement.hs view
@@ -0,0 +1,5 @@+foobarbar :: Int -> Bool+foobarbar | x <-+ 5 = case x of+ 5 -> True+ _ -> False
+ data/examples/declaration/value/function/pattern/multiple-guard-statements-out.hs view
@@ -0,0 +1,6 @@+foobarbar :: Int -> Bool+foobarbar+ | x <- 5,+ y <- 6 = case x of+ 5 -> True+ _ -> False
+ data/examples/declaration/value/function/pattern/multiple-guard-statements.hs view
@@ -0,0 +1,4 @@+foobarbar :: Int -> Bool+foobarbar | x <- 5, y <- 6 = case x of+ 5 -> True+ _ -> False
+ data/examples/other/invalid-haddock-out.hs view
@@ -0,0 +1,17 @@+test = undefined+ where+ a ::+ -- foo+ Int ->+ -- misplaced+ Int+ a = undefined++test = undefined+ where+ -- Comment+ a = undefined++ -- A multiline+ -- comment+ b = b
+ data/examples/other/invalid-haddock-weird-out.hs view
@@ -0,0 +1,3 @@+{-# LANGUAGE TemplateHaskell #-}++foo = foo -- # ${
+ data/examples/other/invalid-haddock-weird.hs view
@@ -0,0 +1,3 @@+{-# LANGUAGE TemplateHaskell #-}++foo = foo -- |# ${
+ data/examples/other/invalid-haddock.hs view
@@ -0,0 +1,17 @@+test = undefined+ where+ a ::+ -- ** foo+ Int ->+ -- | misplaced+ Int+ a = undefined++test = undefined+ where+ -- | Comment+ a = undefined++ -- | A multiline+ -- comment+ b = b
ormolu.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: ormolu-version: 0.3.0.1+version: 0.3.1.0 license: BSD-3-Clause license-file: LICENSE.md maintainer: Mark Karpov <mark.karpov@tweag.io>
src/Ormolu.hs view
@@ -97,9 +97,6 @@ -- | Load a file and format it. The file stays intact and the rendered -- version is returned as 'Text'.------ > ormoluFile cfg path =--- > liftIO (readFile path) >>= ormolu cfg path ormoluFile :: MonadIO m => -- | Ormolu configuration@@ -112,9 +109,6 @@ readFileUtf8 path >>= ormolu cfg path . T.unpack -- | Read input from stdin and format it.------ > ormoluStdin cfg =--- > liftIO (hGetContents stdin) >>= ormolu cfg "<stdin>" ormoluStdin :: MonadIO m => -- | Ormolu configuration@@ -122,7 +116,7 @@ -- | Resulting rendition m Text ormoluStdin cfg =- liftIO getContents >>= ormolu cfg "<stdin>"+ getContentsUtf8 >>= ormolu cfg "<stdin>" . T.unpack ---------------------------------------------------------------------------- -- Helpers
src/Ormolu/Diff/ParseResult.hs view
@@ -16,6 +16,7 @@ where import Data.ByteString (ByteString)+import Data.Foldable import Data.Generics import GHC.Hs import GHC.Types.Basic@@ -54,11 +55,18 @@ { prCommentStream = cstream1, prParsedSource = hs1 } =- matchIgnoringSrcSpans cstream0 cstream1+ diffCommentStream cstream0 cstream1 <> matchIgnoringSrcSpans hs0 {hsmodImports = normalizeImports (hsmodImports hs0)} hs1 {hsmodImports = normalizeImports (hsmodImports hs1)} +diffCommentStream :: CommentStream -> CommentStream -> ParseResultDiff+diffCommentStream (CommentStream cs) (CommentStream cs')+ | commentLines cs == commentLines cs' = Same+ | otherwise = Different []+ where+ commentLines = concatMap (toList . unComment . unLoc)+ -- | Compare two values for equality disregarding the following aspects: -- -- * 'SrcSpan's@@ -83,7 +91,6 @@ gzipWithQ ( genericQuery `extQ` srcSpanEq- `extQ` commentEq `extQ` sourceTextEq `extQ` hsDocStringEq `extQ` importDeclQualifiedStyleEq@@ -96,14 +103,6 @@ | otherwise = Different [] srcSpanEq :: SrcSpan -> GenericQ ParseResultDiff srcSpanEq _ _ = Same- commentEq :: Comment -> GenericQ ParseResultDiff- commentEq (Comment _ x) d =- case cast d :: Maybe Comment of- Nothing -> Different []- Just (Comment _ y) ->- if x == y- then Same- else Different [] sourceTextEq :: SourceText -> GenericQ ParseResultDiff sourceTextEq _ _ = Same importDeclQualifiedStyleEq ::
src/Ormolu/Parser.hs view
@@ -116,7 +116,7 @@ Just err -> Left err Nothing -> let (stackHeader, pragmas, comments) =- mkCommentStream input pstate+ mkCommentStream input pstate hsModule in Right ParseResult { prParsedSource = hsModule,
src/Ormolu/Parser/CommentStream.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TypeApplications #-} -- | Functions for working with comment stream. module Ormolu.Parser.CommentStream@@ -19,15 +20,23 @@ import Data.Char (isSpace) import Data.Data (Data)+import Data.Generics.Schemes import qualified Data.List as L import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE-import Data.Maybe (mapMaybe)+import qualified Data.Map.Lazy as M+import Data.Maybe+import qualified Data.Set as S+import GHC.Hs (HsModule)+import GHC.Hs.Decls (HsDecl (..), LDocDecl, LHsDecl)+import GHC.Hs.Doc+import GHC.Hs.Extension+import GHC.Hs.ImpExp import qualified GHC.Parser.Annotation as GHC import qualified GHC.Parser.Lexer as GHC import GHC.Types.SrcLoc import Ormolu.Parser.Pragma-import Ormolu.Utils (onTheSameLine, showOutputable)+import Ormolu.Utils (onTheSameLine, showOutputable, unSrcSpan) ---------------------------------------------------------------------------- -- Comment stream@@ -44,12 +53,14 @@ String -> -- | Parser state to use for comment extraction GHC.PState ->+ -- | Parsed module+ HsModule -> -- | Stack header, pragmas, and comment stream ( Maybe (RealLocated Comment), [([RealLocated Comment], Pragma)], CommentStream )-mkCommentStream input pstate =+mkCommentStream input pstate hsModule = ( mstackHeader, pragmas, CommentStream comments@@ -57,12 +68,46 @@ where (comments, pragmas) = extractPragmas input rawComments1 (rawComments1, mstackHeader) = extractStackHeader rawComments0++ -- We want to extract all comments except _valid_ Haddock comments rawComments0 =- L.sortOn (realSrcSpanStart . getRealSrcSpan) $- mapMaybe (liftMaybe . fmap unAnnotationComment) (GHC.comment_q pstate)- ++ concatMap- (mapMaybe (liftMaybe . fmap unAnnotationComment) . snd)- (GHC.annotations_comments pstate)+ fmap (uncurry L)+ . M.toAscList+ . flip M.withoutKeys validHaddockCommentSpans+ . M.fromList+ . fmap (\(L l a) -> (l, a))+ $ allComments+ where+ -- All comments, including valid and invalid Haddock comments+ allComments =+ fmap (fmap unAnnotationComment)+ . (GHC.comment_q <> (concatMap snd . GHC.annotations_comments))+ $ pstate+ -- All spans of valid Haddock comments+ -- (everywhere where we use p_hsDoc{String,Name})+ validHaddockCommentSpans =+ S.fromList+ . mapMaybe unSrcSpan+ . mconcat+ [ fmap getLoc . listify (only @LHsDocString),+ fmap getLoc . listify (only @LDocDecl),+ fmap getLoc . listify isDocD,+ fmap getLoc . listify isIEDocLike+ ]+ $ hsModule+ where+ only :: a -> Bool+ only _ = True+ isDocD :: LHsDecl GhcPs -> Bool+ isDocD = \case+ L _ DocD {} -> True+ _ -> False+ isIEDocLike :: LIE GhcPs -> Bool+ isIEDocLike = \case+ L _ IEGroup {} -> True+ L _ IEDoc {} -> True+ L _ IEDocNamed {} -> True+ _ -> False -- | Pretty-print a 'CommentStream'. showCommentStream :: CommentStream -> String@@ -96,17 +141,16 @@ where comment = L l . Comment atomsBefore . removeConseqBlanks . fmap dropTrailing $- if "{-" `L.isPrefixOf` s- then case NE.nonEmpty (lines s) of- Nothing -> s :| []- Just (x :| xs) ->- let getIndent y =- if all isSpace y- then startIndent- else length (takeWhile isSpace y)- n = minimum (startIndent : fmap getIndent xs)- in x :| (drop n <$> xs)- else s :| []+ case NE.nonEmpty (lines s) of+ Nothing -> s :| []+ Just (x :| xs) ->+ let getIndent y =+ if all isSpace y+ then startIndent+ else length (takeWhile isSpace y)+ n = minimum (startIndent : fmap getIndent xs)+ commentPrefix = if "{-" `L.isPrefixOf` s then "" else "-- "+ in x :| ((commentPrefix <>) . drop n <$> xs) (atomsBefore, ls') = case dropWhile ((< commentLine) . fst) ls of [] -> (False, [])@@ -182,20 +226,21 @@ else go' ls [] xs -- | Get a 'String' from 'GHC.AnnotationComment'.-unAnnotationComment :: GHC.AnnotationComment -> Maybe String+unAnnotationComment :: GHC.AnnotationComment -> String unAnnotationComment = \case- GHC.AnnDocCommentNext _ -> Nothing -- @-- |@- GHC.AnnDocCommentPrev _ -> Nothing -- @-- ^@- GHC.AnnDocCommentNamed _ -> Nothing -- @-- $@- GHC.AnnDocSection _ _ -> Nothing -- @-- *@- GHC.AnnDocOptions s -> Just s- GHC.AnnLineComment s -> Just s- GHC.AnnBlockComment s -> Just s--liftMaybe :: GenLocated l (Maybe a) -> Maybe (GenLocated l a)-liftMaybe = \case- L _ Nothing -> Nothing- L l (Just a) -> Just (L l a)+ GHC.AnnDocCommentNext s -> dashPrefix s -- @-- |@+ GHC.AnnDocCommentPrev s -> dashPrefix s -- @-- ^@+ GHC.AnnDocCommentNamed s -> dashPrefix s -- @-- $@+ GHC.AnnDocSection _ s -> dashPrefix s -- @-- *@+ GHC.AnnDocOptions s -> s+ GHC.AnnLineComment s -> s+ GHC.AnnBlockComment s -> s+ where+ dashPrefix s = "--" <> spaceIfNecessary <> s+ where+ spaceIfNecessary = case s of+ c : _ | c /= ' ' -> " "+ _ -> "" -- | Remove consecutive blank lines. removeConseqBlanks :: NonEmpty String -> NonEmpty String
src/Ormolu/Printer/Meat/Declaration/Value.hs view
@@ -248,17 +248,22 @@ endOfPats placement = case endOfPats of- Nothing -> blockPlacement placer grhssGRHSs- Just spn ->- if onTheSameLine spn grhssSpan- then blockPlacement placer grhssGRHSs- else Normal+ Just spn+ | any guardNeedsLineBreak grhssGRHSs+ || not (onTheSameLine spn grhssSpan) ->+ Normal+ _ -> blockPlacement placer grhssGRHSs+ guardNeedsLineBreak :: Located (GRHS GhcPs body) -> Bool+ guardNeedsLineBreak (L _ (GRHS _ guardLStmts _)) = case guardLStmts of+ [] -> False+ [L l _] -> not (isOneLineSpan l)+ _ -> True p_body = do let groupStyle = if isCase style && hasGuards then RightArrow else EqualSign- sep newline (located' (p_grhs' placer render groupStyle)) grhssGRHSs+ sep breakpoint (located' (p_grhs' placer render groupStyle)) grhssGRHSs p_where = do let whereIsEmpty = eqEmptyLocalBinds (unLoc grhssLocalBinds) unless (eqEmptyLocalBinds (unLoc grhssLocalBinds)) $ do
src/Ormolu/Utils/IO.hs view
@@ -3,11 +3,13 @@ module Ormolu.Utils.IO ( writeFileUtf8, readFileUtf8,+ getContentsUtf8, ) where import Control.Exception (throwIO) import Control.Monad.IO.Class+import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Text (Text) import qualified Data.Text.Encoding as TE@@ -20,4 +22,13 @@ -- | Read an entire file strictly into a 'Text' using UTF8 and -- ignoring native line ending conventions. readFileUtf8 :: MonadIO m => FilePath -> m Text-readFileUtf8 p = liftIO $ either throwIO pure . TE.decodeUtf8' =<< B.readFile p+readFileUtf8 p = liftIO (B.readFile p) >>= decodeUtf8++-- | Read stdin as UTF8-encoded 'Text' value.+getContentsUtf8 :: MonadIO m => m Text+getContentsUtf8 = liftIO B.getContents >>= decodeUtf8++-- | A helper function for decoding a strict 'ByteString' into 'Text'. It is+-- strict and fails immediately if decoding encounters a problem.+decodeUtf8 :: MonadIO m => ByteString -> m Text+decodeUtf8 = liftIO . either throwIO pure . TE.decodeUtf8'