on master help sign in
47 files changed+299−106
since splitsnapshots
unifiedsplit
.buildkite/pipeline.yml+2−2 .github/workflows/binaries.yml+39 .github/workflows/build.sh+7 CHANGELOG.md+21−2 README.md+20−10 app/Main.hs+23−13 data/examples/declaration/signature/type/infix-promoted-type-constructor-out.hs−1 data/examples/declaration/signature/type/unrelated-out.hs+4 data/examples/declaration/signature/type/unrelated.hs+4 data/examples/declaration/value/function/implicit-params-out.hs+1 data/examples/declaration/value/function/implicit-params.hs+2 data/examples/import/merging-0-out.hs+3 data/examples/import/merging-0.hs+4 data/examples/import/merging-1-out.hs+2 data/examples/import/merging-1.hs+3 data/examples/import/merging-2-out.hs+2 data/examples/import/merging-2.hs+4 data/examples/import/qualified-post-out.hs+1−1 data/examples/import/qualified-prelude-out.hs+1−1 data/examples/import/simple-out.hs+2−5 data/examples/other/argument-comment-out.hs−1 data/examples/other/comment-glued-together-out.hs+7 data/examples/other/comment-glued-together.hs+6 data/examples/other/cpp/separation-2b-out.hs−1 default.nix+7−10 expected-failures/Agda.txt+1−1 expected-failures/esqueleto.txt+1−1 expected-failures/hlint.txt+11 expected-failures/idris.txt−4 expected-failures/intero.txt+1−1 expected-failures/leksah.txt+1−1 expected-failures/pandoc.txt+1−1 expected-failures/postgrest.txt+5 expected-failures/purescript.txt+2−2 expected-failures/tensorflow.txt+7 nix/nixpkgs/default.nix+7−3 ormolu.cabal+4−4 shell.nix+3−1 src/Ormolu/Diff.hs+3−3 src/Ormolu/Imports.hs+67−21 src/Ormolu/Printer/Comments.hs+2−2 src/Ormolu/Printer/Meat/Declaration.hs+14−4 src/Ormolu/Printer/Meat/Declaration.hs-boot−1 src/Ormolu/Printer/Meat/Declaration/Value.hs-boot−4 src/Ormolu/Printer/Meat/Module.hs+2−2 src/Ormolu/Processing/Preprocess.hs+1−2 src/Ormolu/Utils.hs+1−1
.buildkite/pipeline.yml+2−2
@@ -3,9 +3,9 @@ steps:
33 command: |
44 nix-build --keep-going --no-out-link --argstr ormoluCompiler ghc865
55 timeout: 100
6− - label: Build and test with GHC 8.8.3
6+ - label: Build and test with GHC 8.8.4
77 command: |
8− nix-build --keep-going --no-out-link --argstr ormoluCompiler ghc883
8+ nix-build --keep-going --no-out-link --argstr ormoluCompiler ghc884
99 timeout: 100
1010 - label: Build and test with GHC 8.10.1
1111 command: |
.github/workflows/binaries.ymlnew file+39
@@ -0,0 +1,39 @@
1+name: Build binaries
2+on:
3+ release:
4+ types: [published]
5+jobs:
6+ build:
7+ strategy:
8+ fail-fast: false
9+ matrix:
10+ os: [ubuntu-latest, macOS-latest, windows-latest]
11+ runs-on: ${{ matrix.os }}
12+ steps:
13+ - uses: actions/checkout@v2
14+ - uses: actions/setup-haskell@v1
15+ if: runner.os != 'Linux'
16+ with:
17+ ghc-version: '8.10.1'
18+ cabal-version: '3.2'
19+ - name: Build Linux binary
20+ if: runner.os == 'Linux'
21+ run: |
22+ docker run \
23+ -v $(pwd):/mnt -w /mnt \
24+ utdemir/ghc-musl:v14-ghc8101 \
25+ bash .github/workflows/build.sh
26+ - name: Build ${{ runner.os }} binary
27+ if: runner.os != 'Linux'
28+ shell: bash
29+ run: |
30+ cabal build exe:ormolu
31+ cp $(find dist-newstyle \( -name ormolu -o -name ormolu.exe \) -type f) ormolu
32+ - uses: actions/upload-release-asset@v1
33+ env:
34+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+ with:
36+ upload_url: ${{ github.event.release.upload_url }}
37+ asset_path: ormolu
38+ asset_name: ormolu-${{ runner.os }}
39+ asset_content_type: application/octet-stream
.github/workflows/build.shnew file+7
@@ -0,0 +1,7 @@
1+set -e
2+
3+cabal update
4+cabal build exe:ormolu --enable-executable-static --ghc-options="-split-sections"
5+
6+cp $(find dist-newstyle -name ormolu -type f) .
7+strip -s ormolu
CHANGELOG.md+21−2
@@ -1,8 +1,27 @@
11## Unreleased
22
3+* Ormolu no longer overwrites already formatted files. [PR
4+ 649](https://github.com/tweag/ormolu/pull/649).
5+
6+## Ormolu 0.1.2.0
7+
8+* Fixed the bug when comments in different styles got glued together after
9+ formatting. [Issue 589](https://github.com/tweag/ormolu/issues/589).
10+
11+* Added `-i` as a shortcut for `--mode inplace`. [Issue
12+ 467](https://github.com/tweag/ormolu/issues/467).
13+
14+* Improved grouping of top-level declarations. [Issue
15+ 466](https://github.com/tweag/ormolu/issues/466).
16+
17+## Ormolu 0.1.1.0
18+
319* Imports in a import lists are now normalized: duplicate imports are
420 combined/eliminated intelligently.
521
22+* Import declarations that can be merged are now automatically merged.
23+ [Issue 414](https://github.com/tweag/ormolu/issues/414).
24+
625* The magic comments for disabling and enabling Ormolu now can encompass any
726 fragment of code provided that the remaining code after exclusion of the
827 disabled part is still syntactically correct. [Issue
@@ -23,8 +42,8 @@
2342* Blank lines between definitions in `let` and `while` bindings are now
2443 preserved. [Issue 554](https://github.com/tweag/ormolu/issues/554).
2544
26−* Fixed the bug when type applications glued to TH splices that followed
27− them. [Issue 613](https://github.com/tweag/ormolu/issues/613).
45+* Fixed the bug when type applications stuck to the `$` of TH splices that
46+ followed them. [Issue 613](https://github.com/tweag/ormolu/issues/613).
2847
2948* Improved region formatting so that indented fragments—such as definitions
3049 inside of `where` clauses—can be formatted. [Issue
README.md+20−10
@@ -22,7 +22,7 @@ the following goals in mind:
2222* That formatting style aims to result in minimal diffs while still
2323 remaining very close to “conventional” Haskell formatting people use.
2424* Choose a style compatible with modern dialects of Haskell. As new Haskell
25− extensions enter broad use, we may change the style to accomodate them.
25+ extensions enter broad use, we may change the style to accommodate them.
2626* Idempotence: formatting already formatted code doesn't change it.
2727* Be well-tested and robust to the point that it can be used in large
2828 projects without exposing unfortunate, disappointing bugs here and there.
@@ -49,7 +49,8 @@ resolver: lts-16.0
4949packages:
5050- '.'
5151
52−$ stack build
52+$ stack build # to build
53+$ stack install # to install
5354```
5455
5556To use Ormolu directly from GitHub with Nix, this snippet may come in handy:
@@ -129,14 +130,6 @@ independent top-level definitions.
129130 criterion than just being valid Haskell modules.
130131* Various minor idempotence issues, most of them are related to comments.
131132
132−## Editor integration
133−
134−We know of the following editor integrations:
135−
136−* [Emacs][emacs-package]
137−* [VS Code][vs-code-plugin]
138−* vim: [neoformat][neoformat], [vim-ormolu][vim-ormolu]
139−
140133## Running on Hackage
141134
142135It's possible to try Ormolu on arbitrary packages from Hackage. For that
@@ -151,6 +144,22 @@ also contain formatted `.hs` files for inspection and original inputs with
151144`.hs-original` extension (those are with CPP dropped, exactly what is fed
152145into Ormolu).
153146
147+## Editor integration
148+
149+We know of the following editor integrations:
150+
151+* [Emacs][emacs-package]
152+* [VS Code][vs-code-plugin]
153+* vim: [neoformat][neoformat], [vim-ormolu][vim-ormolu]
154+
155+## Arch Linux
156+
157+To install Ormolu on Arch Linux, one can use [the package on AUR][aur]:
158+
159+```console
160+yay -S ormolu
161+```
162+
154163## Contributing
155164
156165See [CONTRIBUTING.md][contributing].
@@ -169,3 +178,4 @@ Copyright © 2018–present Tweag I/O
169178[vs-code-plugin]: https://marketplace.visualstudio.com/items?itemName=sjurmillidahl.ormolu-vscode
170179[vim-ormolu]: https://github.com/sdiehl/vim-ormolu
171180[neoformat]: https://github.com/sbdchd/neoformat
181+[aur]: https://aur.archlinux.org/packages/ormolu
app/Main.hs+23−13
@@ -8,6 +8,7 @@ module Main (main) where
88
99import Control.Exception (SomeException, displayException, try)
1010import Control.Monad
11+import Data.Bool (bool)
1112import Data.Either (lefts)
1213import Data.List (intercalate, sort)
1314import qualified Data.Text.IO as TIO
@@ -60,15 +61,19 @@ formatOne mode config = \case
6061 -- 101 is different from all the other exit codes we already use.
6162 exitWith (ExitFailure 101)
6263 Just inputFile -> do
63− r <- ormoluFile config inputFile
64+ originalInput <- TIO.readFile inputFile
65+ formattedInput <- ormoluFile config inputFile
6466 case mode of
6567 Stdout ->
66− TIO.putStr r
67− InPlace ->
68− TIO.writeFile inputFile r
68+ TIO.putStr formattedInput
69+ InPlace -> do
70+ -- Only write when the contents have changed, in order to avoid
71+ -- updating the modified timestamp if the file was already correctly
72+ -- formatted.
73+ when (formattedInput /= originalInput) $
74+ TIO.writeFile inputFile formattedInput
6975 Check -> do
70− r' <- TIO.readFile inputFile
71− when (r /= r') $
76+ when (formattedInput /= originalInput) $
7277 -- 100 is different to all the other exit code that are emitted
7378 -- either from an 'OrmoluException' or from 'error' and
7479 -- 'notImplemented'.
@@ -134,13 +139,18 @@ optsParserInfo =
134139optsParser :: Parser Opts
135140optsParser =
136141 Opts
137− <$> (option parseMode . mconcat)
138− [ long "mode",
139− short 'm',
140− metavar "MODE",
141− value Stdout,
142− help "Mode of operation: 'stdout' (default), 'inplace', or 'check'"
143− ]
142+ <$> ( (fmap (bool Stdout InPlace) . switch . mconcat)
143+ [ short 'i',
144+ help "A shortcut for --mode inplace"
145+ ]
146+ <|> (option parseMode . mconcat)
147+ [ long "mode",
148+ short 'm',
149+ metavar "MODE",
150+ value Stdout,
151+ help "Mode of operation: 'stdout' (default), 'inplace', or 'check'"
152+ ]
153+ )
144154 <*> configParser
145155 <*> (many . strArgument . mconcat)
146156 [ metavar "FILE",
data/examples/declaration/signature/type/infix-promoted-type-constructor-out.hs−1
@@ -1,3 +1,2 @@
11fun1 :: Def ('[Ref s (Stored Uint32), IBool] 'T.:-> IBool)
2−
32fun2 :: Def ('[Ref s (Stored Uint32), IBool] ':-> IBool)
data/examples/declaration/signature/type/unrelated-out.hsnew file+4
@@ -0,0 +1,4 @@
1+clientFunc1 :: SomeType1
2+clientFunc2 :: SomeType2
3+clientFunc3 :: SomeType3
4+clientFunc1 :<|> clientFunc2 :<|> clientFunc3 = hoistClient foo bar baz
data/examples/declaration/signature/type/unrelated.hsnew file+4
@@ -0,0 +1,4 @@
1+clientFunc1 :: SomeType1
2+clientFunc2 :: SomeType2
3+clientFunc3 :: SomeType3
4+clientFunc1 :<|> clientFunc2 :<|> clientFunc3 = hoistClient foo bar baz
data/examples/declaration/value/function/implicit-params-out.hs+1
@@ -1,6 +1,7 @@
11{-# LANGUAGE ImplicitParams #-}
22
33sortBy :: (a -> a -> Bool) -> [a] -> [a]
4+sortBy = undefined
45
56sort :: (?cmp :: a -> a -> Bool) => [a] -> [a]
67sort = sortBy ?cmp
data/examples/declaration/value/function/implicit-params.hs+2
@@ -1,6 +1,8 @@
11{-# LANGUAGE ImplicitParams #-}
22sortBy :: (a -> a -> Bool) -> [a] -> [a]
33
4+sortBy = undefined
5+
46sort :: (?cmp :: a -> a -> Bool) => [a] -> [a]
57sort = sortBy ?cmp
68
data/examples/import/merging-0-out.hsnew file+3
@@ -0,0 +1,3 @@
1+import Foo
2+import Foo (bar, foo)
3+import Foo as F
data/examples/import/merging-0.hsnew file+4
@@ -0,0 +1,4 @@
1+import Foo
2+import Foo (foo)
3+import Foo (bar)
4+import Foo as F
data/examples/import/merging-1-out.hsnew file+2
@@ -0,0 +1,2 @@
1+import "bar" Foo (bar)
2+import "foo" Foo (baz, foo)
data/examples/import/merging-1.hsnew file+3
@@ -0,0 +1,3 @@
1+import "foo" Foo (foo)
2+import "bar" Foo (bar)
3+import "foo" Foo (baz)
data/examples/import/merging-2-out.hsnew file+2
@@ -0,0 +1,2 @@
1+import Foo hiding (bar4, foo2)
2+import qualified Foo (bar3, foo1)
data/examples/import/merging-2.hsnew file+4
@@ -0,0 +1,4 @@
1+import qualified Foo (foo1)
2+import Foo hiding (foo2)
3+import qualified Foo (bar3)
4+import Foo hiding (bar4)
data/examples/import/qualified-post-out.hs+1−1
@@ -1,5 +1,5 @@
11{-# LANGUAGE ImportQualifiedPost #-}
22
3−import Data.Text qualified as T
43import Data.Text qualified (a, b, c)
54import Data.Text qualified hiding (a, b, c)
5+import Data.Text qualified as T
data/examples/import/qualified-prelude-out.hs+1−1
@@ -1,4 +1,4 @@
11module P where
22
3−import qualified Prelude
43import Prelude hiding (id, (.))
4+import qualified Prelude
data/examples/import/simple-out.hs+2−5
@@ -1,8 +1,5 @@
11import Data.Text
2−import Data.Text
3−import qualified Data.Text as T
4−import qualified Data.Text (a, b, c)
5−import Data.Text (a, b, c)
6−import Data.Text hiding (a, b, c)
72import Data.Text (a, b, c)
83import Data.Text hiding (a, b, c)
4+import qualified Data.Text (a, b, c)
5+import qualified Data.Text as T
data/examples/other/argument-comment-out.hs−1
@@ -9,7 +9,6 @@ foo ::
99 -- | Foo
1010 Int ->
1111 Int
12−
1312foo ::
1413 Foo a =>
1514 -- | Foo
data/examples/other/comment-glued-together-out.hsnew file+7
@@ -0,0 +1,7 @@
1+module Main (main) where
2+
3+-- | Foo.
4+
5+-- Bar
6+main :: IO ()
7+main = return ()
data/examples/other/comment-glued-together.hsnew file+6
@@ -0,0 +1,6 @@
1+module Main (main) where
2+
3+{- | Foo. -}
4+-- Bar
5+main :: IO ()
6+main = return ()
data/examples/other/cpp/separation-2b-out.hs−1
@@ -1,5 +1,4 @@
11ffff, ffffffff :: Natural
2−
32#ifdef HASKELL_ZIP_DEV_MODE
43ffff = 200
54ffffffff = 5000
default.nix+7−10
@@ -1,5 +1,6 @@
1−{ pkgs ? (import ./nix/nixpkgs)
2−, ormoluCompiler ? "ghc883" # the shell doesn't work with ghc8101 yet
1+{ pkgs ? (import ./nix/nixpkgs { inherit system; })
2+, system ? builtins.currentSystem
3+, ormoluCompiler ? "ghc884" # the shell doesn't work with ghc8101 yet
34}:
45
56let
@@ -16,12 +17,6 @@ let
1617 };
1718 ormoluOverlay = self: super: {
1819 "ormolu" = super.callCabal2nixWithOptions "ormolu" source "-fdev" { };
19− "ghc-lib-parser" = pkgs.haskell.lib.dontHaddock
20− (super.callHackageDirect {
21− pkg = "ghc-lib-parser";
22− ver = "8.10.1.20200412";
23− sha256 = "sha256-EjMzp8xRT3xVFKDI1fAfopkylGB0hv35Av2+uZeETRU=";
24− } {});
2520 };
2621 ormolize = import ./nix/ormolize {
2722 inherit pkgs;
@@ -33,14 +28,16 @@ let
3328 "distributed-process"
3429 "esqueleto"
3530 "fay"
31+ "hlint"
3632 "idris"
3733 "intero"
3834 "leksah"
39− "lens"
4035 "pandoc"
4136 "pipes"
37+ "postgrest"
4238 "purescript"
43− ];
39+ "tensorflow"
40+ ] ++ (if ormoluCompiler == "ghc8101" then [] else ["lens"]);
4441 ormolizedPackages = doCheck:
4542 pkgs.lib.mapAttrs (name: p: ormolize {
4643 package = p;
expected-failures/Agda.txt+1−1
@@ -1,5 +1,5 @@
11Formatting is not idempotent:
2− dist/build/Agda/Syntax/Parser/Lexer.hs<rendered>:1597:17
2+ dist/build/Agda/Syntax/Parser/Lexer.hs<rendered>:1602:17
33 before: " = (check_ac"
44 after: " =\n "
55Please, consider reporting the bug.
expected-failures/esqueleto.txt+1−1
@@ -3,5 +3,5 @@ The GHC parser (in Haddock mode) failed:
33 lexical error in string/character literal at character 's'
44
55The GHC parser (in Haddock mode) failed:
6− test/PostgreSQL/Test.hs:510:9-41
6+ test/PostgreSQL/Test.hs:562:9-41
77 parse error on input `-- | Check the result is not null'
expected-failures/hlint.txtnew file+11
@@ -0,0 +1,11 @@
1+Formatting is not idempotent:
2+ src/Extension.hs<rendered>:19:22
3+ before: "DoRec , -} Recursive"
4+ after: "DoRec , -}\n "
5+Please, consider reporting the bug.
6+
7+Formatting is not idempotent:
8+ src/Hint/Bracket.hs<rendered>:249:50
9+ before: "\" x y [r]) {ideaSpan"
10+ after: "\" x y [r])\n "
11+Please, consider reporting the bug.
expected-failures/idris.txt−4
@@ -2,10 +2,6 @@ The GHC parser (in Haddock mode) failed:
22 src/Idris/Parser.hs:1052:1
33 parse error on input `@'
44
5−AST of input and AST of formatted code differ.
6− at src/Idris/Parser/Data.hs:281:1-29
7−Please, consider reporting the bug.
8−
95The GHC parser (in Haddock mode) failed:
106 src/Idris/Parser/Expr.hs:75:1
117 parse error on input `@'
expected-failures/intero.txt+1−1
@@ -1,5 +1,5 @@
11Formatting is not idempotent:
2− src/InteractiveUI.hs<rendered>:3757:33
2+ src/InteractiveUI.hs<rendered>:3748:33
33 before: "text \"Try\" <+> doWha"
44 after: "text \"Try\"\n "
55Please, consider reporting the bug.
expected-failures/leksah.txt+1−1
@@ -1,5 +1,5 @@
11Formatting is not idempotent:
2− src/IDE/Pane/Modules.hs<rendered>:1189:7
2+ src/IDE/Pane/Modules.hs<rendered>:1184:7
33 before: "cr\n -- show"
44 after: "cr\n in -- show"
55Please, consider reporting the bug.
expected-failures/pandoc.txt+1−1
@@ -3,7 +3,7 @@ The GHC parser (in Haddock mode) failed:
33 parse error on input `import'
44
55Formatting is not idempotent:
6− src/Text/Pandoc/Readers/Vimwiki.hs<rendered>:615:19
6+ src/Text/Pandoc/Readers/Vimwiki.hs<rendered>:613:19
77 before: ".softbreak <$ endlin"
88 after: ".softbreak\n <$ en"
99Please, consider reporting the bug.
expected-failures/postgrest.txtnew file+5
@@ -0,0 +1,5 @@
1+Formatting is not idempotent:
2+ src/PostgREST/DbRequestBuilder.hs<rendered>:149:24
3+ before: " || -- clients"
4+ after: " || ( origin ="
5+Please, consider reporting the bug.
expected-failures/purescript.txt+2−2
@@ -1,10 +1,10 @@
11The GHC parser (in Haddock mode) failed:
2− src/Language/PureScript/AST/Declarations.hs:478:1-17
2+ src/Language/PureScript/AST/Declarations.hs:479:1-17
33 Invalid type signature: pattern ValueDecl :: ...
44Perhaps you meant to use PatternSynonyms?
55
66The GHC parser (in Haddock mode) failed:
7− src/Language/PureScript/AST/SourcePos.hs:101:1-22
7+ src/Language/PureScript/AST/SourcePos.hs:99:1-22
88 Invalid type signature: pattern NullSourceSpan :: ...
99Perhaps you meant to use PatternSynonyms?
1010
expected-failures/tensorflow.txtnew file+7
@@ -0,0 +1,7 @@
1+The GHC parser (in Haddock mode) failed:
2+ tensorflow-mnist/app/Main.hs:50:13-14
3+ parse error on input `->'
4+
5+The GHC parser (in Haddock mode) failed:
6+ tensorflow-ops/tests/RegressionTest.hs:3:1-6
7+ parse error on input `import'
nix/nixpkgs/default.nix+7−3
@@ -1,9 +1,13 @@
1+{ system ? builtins.currentSystem }:
12let
2− rev = "807ca93fadd5197c2260490de0c76e500562dc05";
3− sha256 = "10yq8bnls77fh3pk5chkkb1sv5lbdgyk1rr2v9xn71rr1k2x563p";
3+ rev = "32b46dd897ab2143a609988a04d87452f0bbef59";
4+ sha256 = "1gzfrpjnr1bz9zljsyg3a4zrhk8r927sz761mrgcg56dwinkhpjk";
45 nixpkgs = builtins.fetchTarball {
56 url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
67 inherit sha256;
78 };
8− pkgs = import nixpkgs { config.allowUnfree = true; };
9+ pkgs = import nixpkgs {
10+ inherit system;
11+ config.allowUnfree = true;
12+ };
913in pkgs
ormolu.cabal+4−4
@@ -1,10 +1,10 @@
11cabal-version: 1.18
22name: ormolu
3−version: 0.1.0.0
3+version: 0.1.2.0
44license: BSD3
55license-file: LICENSE.md
66maintainer: Mark Karpov <mark.karpov@tweag.io>
7−tested-with: ghc ==8.6.5 ghc ==8.8.3 ghc ==8.10.1
7+tested-with: ghc ==8.6.5 ghc ==8.8.4 ghc ==8.10.1
88homepage: https://github.com/tweag/ormolu
99bug-reports: https://github.com/tweag/ormolu/issues
1010synopsis: A formatter for Haskell source code
@@ -119,7 +119,7 @@ library
119119 base >=4.12 && <5.0,
120120 bytestring >=0.2 && <0.11,
121121 containers >=0.5 && <0.7,
122− dlist >=0.8 && <0.9,
122+ dlist >=0.8 && <2.0,
123123 exceptions >=0.6 && <0.11,
124124 ghc-lib-parser >=8.10 && <8.11,
125125 mtl >=2.0 && <3.0,
@@ -144,7 +144,7 @@ executable ormolu
144144 base >=4.12 && <5.0,
145145 ghc-lib-parser >=8.10 && <8.11,
146146 gitrev >=1.3 && <1.4,
147− optparse-applicative >=0.14 && <0.16,
147+ optparse-applicative >=0.14 && <0.17,
148148 ormolu -any,
149149 text >=0.2 && <1.3
150150
shell.nix+3−1
@@ -1,3 +1,5 @@
1−{ pkgs ? (import ./nix/nixpkgs) }:
1+{ pkgs ? (import ./nix/nixpkgs { inherit system; })
2+, system ? builtins.currentSystem
3+}:
24
35(import ./default.nix { inherit pkgs; }).dev.ormoluShell
src/Ormolu/Diff.hs+3−3
@@ -14,7 +14,7 @@ import Data.Text (Text)
1414import qualified Data.Text as T
1515import qualified FastString as GHC
1616import GHC
17−import Ormolu.Imports (sortImports)
17+import Ormolu.Imports (normalizeImports)
1818import Ormolu.Parser.CommentStream
1919import Ormolu.Parser.Result
2020import Ormolu.Utils
@@ -47,8 +47,8 @@ diffParseResult
4747 } =
4848 matchIgnoringSrcSpans cstream0 cstream1
4949 <> matchIgnoringSrcSpans
50− hs0 {hsmodImports = sortImports (hsmodImports hs0)}
51− hs1 {hsmodImports = sortImports (hsmodImports hs1)}
50+ hs0 {hsmodImports = normalizeImports (hsmodImports hs0)}
51+ hs1 {hsmodImports = normalizeImports (hsmodImports hs1)}
5252
5353-- | Compare two values for equality disregarding differences in 'SrcSpan's
5454-- and the ordering of import lists.
src/Ormolu/Imports.hs+67−21
@@ -4,48 +4,94 @@
44
55-- | Manipulations on import lists.
66module Ormolu.Imports
7− ( sortImports,
7+ ( normalizeImports,
88 )
99where
1010
1111import Data.Bifunctor
1212import Data.Char (isAlphaNum)
1313import Data.Function (on)
14−import Data.Generics (gcompare)
1514import Data.List (foldl', nubBy, sortBy, sortOn)
1615import Data.Map.Strict (Map)
1716import qualified Data.Map.Strict as M
17+import FastString (FastString)
1818import GHC hiding (GhcPs, IE)
1919import GHC.Hs.Extension
2020import GHC.Hs.ImpExp (IE (..))
2121import Ormolu.Utils (notImplemented, showOutputable)
2222
23−-- | Sort imports by module name. This also sorts and normalizes explicit
24−-- import lists for each declaration.
25−sortImports :: [LImportDecl GhcPs] -> [LImportDecl GhcPs]
26−sortImports = sortBy compareIdecl . fmap (fmap sortImportLists)
23+-- | Sort and normalize imports.
24+normalizeImports :: [LImportDecl GhcPs] -> [LImportDecl GhcPs]
25+normalizeImports =
26+ fmap snd
27+ . M.toAscList
28+ . M.fromListWith combineImports
29+ . fmap (\x -> (importId x, g x))
2730 where
28− sortImportLists :: ImportDecl GhcPs -> ImportDecl GhcPs
29− sortImportLists = \case
30− ImportDecl {..} ->
31+ g (L l ImportDecl {..}) =
32+ L
33+ l
3134 ImportDecl
3235 { ideclHiding = second (fmap normalizeLies) <$> ideclHiding,
3336 ..
3437 }
35− XImportDecl x -> noExtCon x
38+ g _ = notImplemented "XImportDecl"
3639
37−-- | Compare two @'LImportDecl' 'GhcPs'@ things.
38−compareIdecl :: LImportDecl GhcPs -> LImportDecl GhcPs -> Ordering
39−compareIdecl (L _ m0) (L _ m1) =
40− case (isPrelude n0, isPrelude n1) of
41− (False, False) -> n0 `compare` n1
42− (True, False) -> GT
43− (False, True) -> LT
44− (True, True) -> m0 `gcompare` m1
40+-- | Combine two import declarations. It should be assumed that 'ImportId's
41+-- are equal.
42+combineImports ::
43+ LImportDecl GhcPs ->
44+ LImportDecl GhcPs ->
45+ LImportDecl GhcPs
46+combineImports (L lx ImportDecl {..}) (L _ y) =
47+ L
48+ lx
49+ ImportDecl
50+ { ideclHiding = case (ideclHiding, GHC.ideclHiding y) of
51+ (Just (hiding, L l' xs), Just (_, L _ ys)) ->
52+ Just (hiding, (L l' (normalizeLies (xs ++ ys))))
53+ _ -> Nothing,
54+ ..
55+ }
56+combineImports _ _ = notImplemented "XImportDecl"
57+
58+-- | Import id, a collection of all things that justify having a separate
59+-- import entry. This is used for merging of imports. If two imports have
60+-- the same 'ImportId' they can be merged.
61+data ImportId = ImportId
62+ { importIsPrelude :: Bool,
63+ importIdName :: ModuleName,
64+ importPkgQual :: Maybe FastString,
65+ importSource :: Bool,
66+ importSafe :: Bool,
67+ importQualified :: Bool,
68+ importImplicit :: Bool,
69+ importAs :: Maybe ModuleName,
70+ importHiding :: Maybe Bool
71+ }
72+ deriving (Eq, Ord)
73+
74+-- | Obtain an 'ImportId' for a given import.
75+importId :: LImportDecl GhcPs -> ImportId
76+importId (L _ ImportDecl {..}) =
77+ ImportId
78+ { importIsPrelude = isPrelude,
79+ importIdName = moduleName,
80+ importPkgQual = sl_fs <$> ideclPkgQual,
81+ importSource = ideclSource,
82+ importSafe = ideclSafe,
83+ importQualified = case ideclQualified of
84+ QualifiedPre -> True
85+ QualifiedPost -> True
86+ NotQualified -> False,
87+ importImplicit = ideclImplicit,
88+ importAs = unLoc <$> ideclAs,
89+ importHiding = fst <$> ideclHiding
90+ }
4591 where
46− n0 = unLoc (ideclName m0)
47− n1 = unLoc (ideclName m1)
48− isPrelude = (== "Prelude") . moduleNameString
92+ isPrelude = moduleNameString moduleName == "Prelude"
93+ moduleName = unLoc ideclName
94+importId _ = notImplemented "XImportDecl"
4995
5096-- | Normalize a collection of import\/export items.
5197normalizeLies :: [LIE GhcPs] -> [LIE GhcPs]
src/Ormolu/Printer/Comments.hs+2−2
@@ -154,6 +154,7 @@ needsNewlineBefore ::
154154 -- | Last printed comment span
155155 Maybe SpanMark ->
156156 Bool
157+needsNewlineBefore _ (Just (HaddockSpan _ _)) = True
157158needsNewlineBefore l mlastMark =
158159 case spanMarkSpan <$> mlastMark of
159160 Nothing -> False
@@ -238,8 +239,7 @@ commentFollowsElt ref mnSpn meSpn mlastMark (L l comment) =
238239 -- lexemes in front of it and goes right after the previous comment.
239240 not (hasAtomsBefore comment)
240241 && ( case mlastMark of
241− Just (HaddockSpan _ spn) ->
242− srcSpanEndLine spn + 1 == srcSpanStartLine l
242+ Just (HaddockSpan _ _) -> False
243243 Just (CommentSpan spn) ->
244244 srcSpanEndLine spn + 1 == srcSpanStartLine l
245245 _ -> False
src/Ormolu/Printer/Meat/Declaration.hs+14−4
@@ -85,9 +85,6 @@ isDocumented = any (isHaddock . unLoc)
8585 isHaddock _ = False
8686
8787-- | Group relevant declarations together.
88−--
89−-- Add a declaration to a group iff it is relevant to either the first or
90−-- the preceding declaration in the group.
9188groupDecls :: [LHsDecl GhcPs] -> [NonEmpty (LHsDecl GhcPs)]
9289groupDecls [] = []
9390groupDecls (l@(L _ DocNext) : xs) =
@@ -100,7 +97,8 @@ groupDecls (header : xs) =
10097 let (grp, rest) = flip span (zip (header : xs) xs) $ \(previous, current) ->
10198 let relevantToHdr = groupedDecls header current
10299 relevantToPrev = groupedDecls previous current
103− in relevantToHdr || relevantToPrev
100+ isDeclSeries = declSeries previous current
101+ in isDeclSeries || relevantToHdr || relevantToPrev
104102 in (header :| map snd grp) : groupDecls (map snd rest)
105103
106104p_hsDecl :: FamilyStyle -> HsDecl GhcPs -> R ()
@@ -197,6 +195,18 @@ groupedDecls (L l_x x') (L l_y y') =
197195 (_, DocPrev) -> True
198196 _ -> False
199197
198+-- | Detect declaration series that should not have blanks between them.
199+declSeries ::
200+ LHsDecl GhcPs ->
201+ LHsDecl GhcPs ->
202+ Bool
203+declSeries (L _ x) (L _ y) =
204+ case (x, y) of
205+ ( SigD NoExtField (TypeSig NoExtField _ _),
206+ SigD NoExtField (TypeSig NoExtField _ _)
207+ ) -> True
208+ _ -> False
209+
200210intersects :: Ord a => [a] -> [a] -> Bool
201211intersects a b = go (sort a) (sort b)
202212 where
src/Ormolu/Printer/Meat/Declaration.hs-boot−1
@@ -9,5 +9,4 @@ import Ormolu.Printer.Combinators
99import Ormolu.Printer.Meat.Common
1010
1111p_hsDecls :: FamilyStyle -> [LHsDecl GhcPs] -> R ()
12−
1312p_hsDeclsRespectGrouping :: FamilyStyle -> [LHsDecl GhcPs] -> R ()
src/Ormolu/Printer/Meat/Declaration/Value.hs-boot−4
@@ -11,11 +11,7 @@ import GHC
1111import Ormolu.Printer.Combinators
1212
1313p_valDecl :: HsBindLR GhcPs GhcPs -> R ()
14−
1514p_pat :: Pat GhcPs -> R ()
16−
1715p_hsExpr :: HsExpr GhcPs -> R ()
18−
1916p_hsSplice :: HsSplice GhcPs -> R ()
20−
2117p_stringLit :: String -> R ()
src/Ormolu/Printer/Meat/Module.hs+2−2
@@ -11,7 +11,7 @@ where
1111import Control.Monad
1212import qualified Data.Text as T
1313import GHC
14−import Ormolu.Imports
14+import Ormolu.Imports (normalizeImports)
1515import Ormolu.Parser.CommentStream
1616import Ormolu.Parser.Pragma
1717import Ormolu.Parser.Shebang
@@ -69,7 +69,7 @@ p_hsModule mstackHeader shebangs pragmas qualifiedPost HsModule {..} = do
6969 txt "where"
7070 newline
7171 newline
72− forM_ (sortImports hsmodImports) (located' (p_hsmodImport qualifiedPost))
72+ forM_ (normalizeImports hsmodImports) (located' (p_hsmodImport qualifiedPost))
7373 newline
7474 switchLayout (getLoc <$> hsmodDecls) $ do
7575 p_hsDecls Free hsmodDecls
src/Ormolu/Processing/Preprocess.hs+1−2
@@ -11,8 +11,7 @@ where
1111import Control.Monad
1212import Data.Char (isSpace)
1313import qualified Data.List as L
14−import Data.Maybe (isJust)
15−import Data.Maybe (maybeToList)
14+import Data.Maybe (isJust, maybeToList)
1615import FastString
1716import Ormolu.Config (RegionDeltas (..))
1817import Ormolu.Parser.Shebang (isShebang)
src/Ormolu/Utils.hs+1−1
@@ -22,8 +22,8 @@ where
2222
2323import Data.Char (isSpace)
2424import Data.List (dropWhileEnd)
25−import qualified Data.List.NonEmpty as NE
2625import Data.List.NonEmpty (NonEmpty (..))
26+import qualified Data.List.NonEmpty as NE
2727import Data.Maybe (fromMaybe)
2828import Data.Text (Text)
2929import qualified Data.Text as T